From 6ff83077fd94809eb6475658507ca297d6a08e8f Mon Sep 17 00:00:00 2001 From: Sahil Singh Date: Thu, 20 Aug 2026 14:59:49 +0530 Subject: [PATCH] fix(api): reset mcp sse singleton on new client initialize (#10690) (#10772) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution! --- open-sse/mcp-server/httpTransport.ts | 18 +++++ .../mcp-sse-singleton-reset-10772.test.ts | 81 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 tests/unit/mcp-sse-singleton-reset-10772.test.ts diff --git a/open-sse/mcp-server/httpTransport.ts b/open-sse/mcp-server/httpTransport.ts index ab742858c6..d8826c738c 100644 --- a/open-sse/mcp-server/httpTransport.ts +++ b/open-sse/mcp-server/httpTransport.ts @@ -284,12 +284,30 @@ export async function handleMcpStreamableHTTP(request: Request): Promise { + if (request.method === "POST") { + try { + const body = await request.clone().json(); + const isInitialize = Array.isArray(body) + ? body.some((req: RpcRequest) => req?.method === "initialize") + : (body as RpcRequest)?.method === "initialize"; + + if (isInitialize) { + console.log("[MCP] New client initialize detected, resetting SSE singleton..."); + closeSseTransport(); + } + } catch (err) {} + } const { transport } = ensureSseServer(); try { diff --git a/tests/unit/mcp-sse-singleton-reset-10772.test.ts b/tests/unit/mcp-sse-singleton-reset-10772.test.ts new file mode 100644 index 0000000000..24250cb7ca --- /dev/null +++ b/tests/unit/mcp-sse-singleton-reset-10772.test.ts @@ -0,0 +1,81 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const mod = await import("../../open-sse/mcp-server/httpTransport.ts"); + +function initializeRequest(id: number): Request { + return new Request("http://localhost/api/mcp/sse", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json, text/event-stream", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + method: "initialize", + id, + params: { + protocolVersion: "2025-03-26", + capabilities: {}, + clientInfo: { name: "test-client", version: "1.0.0" }, + }, + }), + }); +} + +function batchedInitializeRequest(id: number): Request { + return new Request("http://localhost/api/mcp/sse", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json, text/event-stream", + }, + body: JSON.stringify([ + { + jsonrpc: "2.0", + method: "initialize", + id, + params: { + protocolVersion: "2025-03-26", + capabilities: {}, + clientInfo: { name: "test-client", version: "1.0.0" }, + }, + }, + ]), + }); +} + +// ── Regression #10690 / PR #10772: a second `initialize` POST must reset the +// SSE singleton instead of failing with 400 ("already initialized"). ───────── + +test("handleMcpSSE: a second POST initialize after a successful first one does not return 400", async () => { + mod.shutdownMcpHttp(); + + const firstRes = await mod.handleMcpSSE(initializeRequest(1)); + assert.notEqual(firstRes.status, 400, "first initialize should not fail"); + + const secondRes = await mod.handleMcpSSE(initializeRequest(2)); + assert.notEqual( + secondRes.status, + 400, + "a second client initialize must reset the SSE singleton instead of returning 400" + ); + + mod.shutdownMcpHttp(); +}); + +test("handleMcpSSE: a batched (array) JSON-RPC body containing initialize also resets the singleton", async () => { + mod.shutdownMcpHttp(); + + const firstRes = await mod.handleMcpSSE(initializeRequest(1)); + assert.notEqual(firstRes.status, 400, "first initialize should not fail"); + + const batchedRes = await mod.handleMcpSSE(batchedInitializeRequest(2)); + assert.notEqual( + batchedRes.status, + 400, + "a batched initialize entry must also trigger the SSE singleton reset" + ); + + mod.shutdownMcpHttp(); +});