From bd344643396e3952830bec5154c8826c1cb45106 Mon Sep 17 00:00:00 2001 From: lucarlig Date: Fri, 18 Sep 2026 17:10:24 +0100 Subject: [PATCH] fix(server): accept metadata on stateful initialize Route initialize requests by their negotiated session-era protocol version even when optional request metadata is present. Add an integration regression test for the Python SDK 2.x request shape.\n\nCloses #506. Signed-off-by: lucarlig --- .../servers/typescript/everything-server.ts | 20 ++++++++---- src/scenarios/server/all-scenarios.test.ts | 31 +++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/examples/servers/typescript/everything-server.ts b/examples/servers/typescript/everything-server.ts index c76c1f56..0bd3c8bd 100644 --- a/examples/servers/typescript/everything-server.ts +++ b/examples/servers/typescript/everything-server.ts @@ -1284,13 +1284,21 @@ app.post('/mcp', async (req, res) => { const meta = params._meta; const metaVersion = meta?.['io.modelcontextprotocol/protocolVersion']; - // A request that carries no `_meta` and names a legacy session-era revision - // in the header is legacy traffic; it is served by the session path below - // instead of being rejected for missing per-request metadata. + // An initialize request negotiates its version in params and may carry the + // optional RequestParams `_meta` field. Route session-era versions through + // the stateful transport even when that metadata is present. + const isLegacyInitializeRequest = + isInitializeRequest(body) && + typeof params.protocolVersion === 'string' && + LEGACY_SESSION_PROTOCOL_VERSIONS.includes(params.protocolVersion); + + // A non-initialize request that carries no `_meta` and names a legacy + // session-era revision in the header is also legacy traffic. const isLegacySessionEraRequest = - meta === undefined && - reqVersion !== undefined && - LEGACY_SESSION_PROTOCOL_VERSIONS.includes(reqVersion); + isLegacyInitializeRequest || + (meta === undefined && + reqVersion !== undefined && + LEGACY_SESSION_PROTOCOL_VERSIONS.includes(reqVersion)); if (!sessionId && (reqVersion || meta) && !isLegacySessionEraRequest) { // Missing Transport Header Validation Check diff --git a/src/scenarios/server/all-scenarios.test.ts b/src/scenarios/server/all-scenarios.test.ts index bce7845f..7e312860 100644 --- a/src/scenarios/server/all-scenarios.test.ts +++ b/src/scenarios/server/all-scenarios.test.ts @@ -177,6 +177,37 @@ describe('Server Scenarios', () => { expect(nonFailures.length).toBe(checks.length); } + it('accepts request metadata on a stateful initialize', async () => { + const response = await fetch(serverUrl, { + method: 'POST', + headers: { + Accept: 'application/json, text/event-stream', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 1, + method: 'initialize', + params: { + protocolVersion: '2025-11-25', + capabilities: {}, + clientInfo: { name: 'metadata-test', version: '1.0.0' }, + _meta: {} + } + }) + }); + + const sessionId = response.headers.get('mcp-session-id'); + expect(response.status).toBe(200); + expect(sessionId).toBeTruthy(); + expect(await response.text()).toContain('"protocolVersion":"2025-11-25"'); + + await fetch(serverUrl, { + method: 'DELETE', + headers: { 'mcp-session-id': sessionId! } + }); + }); + for (const scenarioName of scenarios) { it(`${scenarioName}`, async () => { await expectScenarioToPass(scenarioName);