Protocol date validation (#1247)

This commit is contained in:
Matt 2025-12-08 17:46:34 +00:00 • committed by GitHub
commit fbf5fc9b02
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 65 additions and 47 deletions

View file

@ -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
})

View file

@ -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,
@ -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, {
@ -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'
}
});
@ -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'
}
});
@ -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'
}
});
@ -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'
}
});
@ -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'
}
});
@ -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'
}
});
@ -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'
}
});
@ -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'
}
});
@ -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',
@ -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
@ -905,16 +910,16 @@ 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
@ -922,13 +927,13 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
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/);
});
});
});
@ -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'
}
});
@ -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);
@ -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
}
});
@ -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);
@ -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
}
});
@ -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);
@ -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
@ -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,
@ -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,
@ -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)
});
@ -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'
}
});
@ -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'
}
});
@ -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'
}
});
@ -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'
}
});
@ -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'
}
});
@ -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'
}
});
@ -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'
}
});