Skip to content
Merged
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
19 changes: 16 additions & 3 deletions src/server/streamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -794,19 +794,32 @@ export class StreamableHTTPServerTransport implements Transport {
return true;
}

/**
* Validates the MCP-Protocol-Version header on incoming requests.
*
* For initialization: Version negotiation handles unknown versions gracefully
* (server responds with its supported version).
*
* For subsequent requests with MCP-Protocol-Version header:
* - Accept if in supported list
* - 400 if unsupported
*
* For HTTP requests without the MCP-Protocol-Version header:
* - Accept and default to the version negotiated at initialization
*/
private validateProtocolVersion(req: IncomingMessage, res: ServerResponse): boolean {
let protocolVersion = req.headers['mcp-protocol-version'] ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
let protocolVersion = req.headers['mcp-protocol-version'];
if (Array.isArray(protocolVersion)) {
protocolVersion = protocolVersion[protocolVersion.length - 1];
}

if (!SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
if (protocolVersion !== undefined && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
res.writeHead(400).end(
JSON.stringify({
jsonrpc: '2.0',
error: {
code: -32000,
message: `Bad Request: Unsupported protocol version (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(', ')})`
message: `Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(', ')})`
},
id: null
})
Expand Down
93 changes: 49 additions & 44 deletions test/server/streamableHttp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,21 @@ const TEST_MESSAGES = {
method: 'initialize',
params: {
clientInfo: { name: 'test-client', version: '1.0' },
protocolVersion: '2025-03-26',
protocolVersion: '2025-11-25',
capabilities: {}
},
id: 'init-1'
} as JSONRPCMessage,

// Initialize message with an older protocol version for backward compatibility tests
initializeOldVersion: {
jsonrpc: '2.0',
method: 'initialize',
params: {
clientInfo: { name: 'test-client', version: '1.0' },
protocolVersion: '2025-06-18',
capabilities: {}
},
id: 'init-1'
} as JSONRPCMessage,

Expand Down Expand Up @@ -98,8 +109,7 @@ async function sendPostRequest(

if (sessionId) {
headers['mcp-session-id'] = sessionId;
// After initialization, include the protocol version header
headers['mcp-protocol-version'] = '2025-03-26';
headers['mcp-protocol-version'] = '2025-11-25';
}

return fetch(baseUrl, {
Expand Down Expand Up @@ -460,7 +470,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
headers: {
Accept: 'text/event-stream',
'mcp-session-id': sessionId,
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand Down Expand Up @@ -501,7 +511,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
headers: {
Accept: 'text/event-stream',
'mcp-session-id': sessionId,
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand Down Expand Up @@ -533,7 +543,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
headers: {
Accept: 'text/event-stream',
'mcp-session-id': sessionId,
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand All @@ -545,7 +555,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
headers: {
Accept: 'text/event-stream',
'mcp-session-id': sessionId,
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand All @@ -564,7 +574,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
headers: {
Accept: 'application/json',
'mcp-session-id': sessionId,
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand Down Expand Up @@ -758,7 +768,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
headers: {
Accept: 'text/event-stream',
'mcp-session-id': sessionId,
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand Down Expand Up @@ -796,7 +806,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
method: 'DELETE',
headers: {
'mcp-session-id': tempSessionId || '',
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand All @@ -815,7 +825,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
method: 'DELETE',
headers: {
'mcp-session-id': 'invalid-session-id',
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand Down Expand Up @@ -869,15 +879,12 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {

expect(response.status).toBe(400);
const errorData = await response.json();
expectErrorResponse(errorData, -32000, /Bad Request: Unsupported protocol version \(supported versions: .+\)/);
expectErrorResponse(errorData, -32000, /Bad Request: Unsupported protocol version: .+ \(supported versions: .+\)/);
});

it('should accept when protocol version differs from negotiated version', async () => {
sessionId = await initializeServer();

// Spy on console.warn to verify warning is logged
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

// Send request with different but supported protocol version
const response = await fetch(baseUrl, {
method: 'POST',
Expand All @@ -892,11 +899,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {

// Request should still succeed
expect(response.status).toBe(200);

warnSpy.mockRestore();
});

it('should handle protocol version validation for GET requests', async () => {
it('should reject unsupported protocol version on GET requests', async () => {
sessionId = await initializeServer();

// GET request with unsupported protocol version
Expand All @@ -905,30 +910,30 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
headers: {
Accept: 'text/event-stream',
'mcp-session-id': sessionId,
'mcp-protocol-version': 'invalid-version'
'mcp-protocol-version': '1999-01-01' // Unsupported version
}
});

expect(response.status).toBe(400);
const errorData = await response.json();
expectErrorResponse(errorData, -32000, /Bad Request: Unsupported protocol version \(supported versions: .+\)/);
expectErrorResponse(errorData, -32000, /Bad Request: Unsupported protocol version/);
});

it('should handle protocol version validation for DELETE requests', async () => {
it('should reject unsupported protocol version on DELETE requests', async () => {
sessionId = await initializeServer();

// DELETE request with unsupported protocol version
const response = await fetch(baseUrl, {
method: 'DELETE',
headers: {
'mcp-session-id': sessionId,
'mcp-protocol-version': 'invalid-version'
'mcp-protocol-version': '1999-01-01' // Unsupported version
}
});

expect(response.status).toBe(400);
const errorData = await response.json();
expectErrorResponse(errorData, -32000, /Bad Request: Unsupported protocol version \(supported versions: .+\)/);
expectErrorResponse(errorData, -32000, /Bad Request: Unsupported protocol version/);
});
});
});
Expand Down Expand Up @@ -1325,7 +1330,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
headers: {
Accept: 'text/event-stream',
'mcp-session-id': sessionId,
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand Down Expand Up @@ -1370,7 +1375,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
headers: {
Accept: 'text/event-stream',
'mcp-session-id': sessionId,
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});
expect(sseResponse.status).toBe(200);
Expand Down Expand Up @@ -1404,7 +1409,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
headers: {
Accept: 'text/event-stream',
'mcp-session-id': sessionId,
'mcp-protocol-version': '2025-03-26',
'mcp-protocol-version': '2025-11-25',
'last-event-id': firstEventId
}
});
Expand All @@ -1428,7 +1433,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
headers: {
Accept: 'text/event-stream',
'mcp-session-id': sessionId,
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});
expect(sseResponse.status).toBe(200);
Expand Down Expand Up @@ -1461,7 +1466,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
headers: {
Accept: 'text/event-stream',
'mcp-session-id': sessionId,
'mcp-protocol-version': '2025-03-26',
'mcp-protocol-version': '2025-11-25',
'last-event-id': lastEventId
}
});
Expand Down Expand Up @@ -1565,7 +1570,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
method: 'GET',
headers: {
Accept: 'text/event-stream',
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});
expect(stream1.status).toBe(200);
Expand All @@ -1575,7 +1580,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
method: 'GET',
headers: {
Accept: 'text/event-stream',
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});
expect(stream2.status).toBe(409); // Conflict - only one stream allowed
Expand Down Expand Up @@ -1692,12 +1697,12 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
baseUrl = result.baseUrl;
mcpServer = result.mcpServer;

// Initialize to get session ID
const initResponse = await sendPostRequest(baseUrl, TEST_MESSAGES.initialize);
// Initialize with OLD protocol version to get session ID
const initResponse = await sendPostRequest(baseUrl, TEST_MESSAGES.initializeOldVersion);
sessionId = initResponse.headers.get('mcp-session-id') as string;
expect(sessionId).toBeDefined();

// Send a tool call request with OLD protocol version
// Send a tool call request with the same OLD protocol version
const toolCallRequest: JSONRPCMessage = {
jsonrpc: '2.0',
id: 100,
Expand Down Expand Up @@ -1932,12 +1937,12 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
return { content: [{ type: 'text', text: 'Done' }] };
});

// Initialize to get session ID
const initResponse = await sendPostRequest(baseUrl, TEST_MESSAGES.initialize);
// Initialize with OLD protocol version to get session ID
const initResponse = await sendPostRequest(baseUrl, TEST_MESSAGES.initializeOldVersion);
sessionId = initResponse.headers.get('mcp-session-id') as string;
expect(sessionId).toBeDefined();

// Call the tool with OLD protocol version
// Call the tool with the same OLD protocol version
const toolCallRequest: JSONRPCMessage = {
jsonrpc: '2.0',
id: 200,
Expand Down Expand Up @@ -2009,7 +2014,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
'Content-Type': 'application/json',
Accept: 'text/event-stream, application/json',
'mcp-session-id': sessionId,
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
},
body: JSON.stringify(toolCallRequest)
});
Expand Down Expand Up @@ -2307,7 +2312,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
method: 'DELETE',
headers: {
'mcp-session-id': tempSessionId || '',
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand Down Expand Up @@ -2367,7 +2372,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
method: 'DELETE',
headers: {
'mcp-session-id': 'invalid-session-id',
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand Down Expand Up @@ -2415,7 +2420,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
method: 'DELETE',
headers: {
'mcp-session-id': sessionId1 || '',
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand All @@ -2428,7 +2433,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
method: 'DELETE',
headers: {
'mcp-session-id': sessionId2 || '',
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand Down Expand Up @@ -2527,7 +2532,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
method: 'DELETE',
headers: {
'mcp-session-id': tempSessionId || '',
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand Down Expand Up @@ -2588,7 +2593,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
method: 'DELETE',
headers: {
'mcp-session-id': tempSessionId || '',
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand Down Expand Up @@ -2632,7 +2637,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
method: 'DELETE',
headers: {
'mcp-session-id': tempSessionId || '',
'mcp-protocol-version': '2025-03-26'
'mcp-protocol-version': '2025-11-25'
}
});

Expand Down
Loading