Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-init-protocol-version-mismatch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modelcontextprotocol/server': patch
---

fix(server): reject initialize when MCP-Protocol-Version header disagrees with body protocolVersion
15 changes: 15 additions & 0 deletions packages/server/src/server/streamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,21 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
this.onerror?.(new Error('Invalid Request: Only one initialization request is allowed'));
return this.createJsonErrorResponse(400, -32_600, 'Invalid Request: Only one initialization request is allowed');
}
// When the MCP-Protocol-Version header is present on initialize, it must agree
// with the body's params.protocolVersion. The spec does not require this check,
// but a silent header/body mismatch lets middleware misconfiguration through
// (the body wins) and routes responses to a version the client never asked for.
// See issue #2108.
const headerProtocolVersion = req.headers.get('mcp-protocol-version');
if (headerProtocolVersion !== null) {
const initRequest = messages.find(element => isInitializeRequest(element));
const bodyProtocolVersion = initRequest?.params.protocolVersion;
if (bodyProtocolVersion !== undefined && headerProtocolVersion !== bodyProtocolVersion) {
const error = `Bad Request: MCP-Protocol-Version header (${headerProtocolVersion}) does not match initialize body protocolVersion (${bodyProtocolVersion})`;
this.onerror?.(new Error(error));
return this.createJsonErrorResponse(400, -32_600, error);
}
}
this.sessionId = this.sessionIdGenerator?.();
this._initialized = true;

Expand Down
62 changes: 62 additions & 0 deletions packages/server/test/server/streamableHttp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,68 @@ describe('Zod v4', () => {
const errorData = await response.json();
expectErrorResponse(errorData, -32_000, /Unsupported protocol version/);
});

it('should reject initialize when MCP-Protocol-Version header disagrees with body (header older)', async () => {
const request = new Request('http://localhost/mcp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json, text/event-stream',
'mcp-protocol-version': '2025-03-26'
},
body: JSON.stringify(TEST_MESSAGES.initialize)
});

const response = await transport.handleRequest(request);

expect(response.status).toBe(400);
const errorData = await response.json();
expectErrorResponse(errorData, -32_600, /MCP-Protocol-Version header.*does not match initialize body protocolVersion/);
});

it('should reject initialize when MCP-Protocol-Version header disagrees with body (header newer)', async () => {
const request = new Request('http://localhost/mcp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json, text/event-stream',
'mcp-protocol-version': '2025-11-25'
},
body: JSON.stringify(TEST_MESSAGES.initializeOldVersion)
});

const response = await transport.handleRequest(request);

expect(response.status).toBe(400);
const errorData = await response.json();
expectErrorResponse(errorData, -32_600, /MCP-Protocol-Version header.*does not match initialize body protocolVersion/);
});

it('should accept initialize when MCP-Protocol-Version header matches body', async () => {
const request = new Request('http://localhost/mcp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json, text/event-stream',
'mcp-protocol-version': '2025-11-25'
},
body: JSON.stringify(TEST_MESSAGES.initialize)
});

const response = await transport.handleRequest(request);

expect(response.status).toBe(200);
expect(response.headers.get('mcp-session-id')).toBeDefined();
});

it('should accept initialize when MCP-Protocol-Version header is absent (existing behavior)', async () => {
const request = createRequest('POST', TEST_MESSAGES.initialize);

const response = await transport.handleRequest(request);

expect(response.status).toBe(200);
expect(response.headers.get('mcp-session-id')).toBeDefined();
});
});

describe('HTTPServerTransport - start() method', () => {
Expand Down
Loading