Skip to content

Commit 96eb971

Browse files
authored
Merge branch 'main' into feat-fetch-transport
2 parents cf268c8 + 4d6c3b8 commit 96eb971

File tree

3 files changed

+68
-50
lines changed

3 files changed

+68
-50
lines changed

src/server/streamableHttp.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -761,19 +761,32 @@ export class StreamableHTTPServerTransport implements Transport {
761761
return true;
762762
}
763763

764+
/**
765+
* Validates the MCP-Protocol-Version header on incoming requests.
766+
*
767+
* For initialization: Version negotiation handles unknown versions gracefully
768+
* (server responds with its supported version).
769+
*
770+
* For subsequent requests with MCP-Protocol-Version header:
771+
* - Accept if in supported list
772+
* - 400 if unsupported
773+
*
774+
* For HTTP requests without the MCP-Protocol-Version header:
775+
* - Accept and default to the version negotiated at initialization
776+
*/
764777
private validateProtocolVersion(req: IncomingMessage, res: ServerResponse): boolean {
765-
let protocolVersion = req.headers['mcp-protocol-version'] ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
778+
let protocolVersion = req.headers['mcp-protocol-version'];
766779
if (Array.isArray(protocolVersion)) {
767780
protocolVersion = protocolVersion[protocolVersion.length - 1];
768781
}
769782

770-
if (!SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
783+
if (protocolVersion !== undefined && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
771784
res.writeHead(400).end(
772785
JSON.stringify({
773786
jsonrpc: '2.0',
774787
error: {
775788
code: -32000,
776-
message: `Bad Request: Unsupported protocol version (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(', ')})`
789+
message: `Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(', ')})`
777790
},
778791
id: null
779792
})

test/server/streamableHttp.test.ts

Lines changed: 51 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,21 @@ const TEST_MESSAGES = {
5656
method: 'initialize',
5757
params: {
5858
clientInfo: { name: 'test-client', version: '1.0' },
59-
protocolVersion: '2025-03-26',
59+
protocolVersion: '2025-11-25',
6060
capabilities: {}
6161
},
62+
id: 'init-1'
63+
} as JSONRPCMessage,
6264

65+
// Initialize message with an older protocol version for backward compatibility tests
66+
initializeOldVersion: {
67+
jsonrpc: '2.0',
68+
method: 'initialize',
69+
params: {
70+
clientInfo: { name: 'test-client', version: '1.0' },
71+
protocolVersion: '2025-06-18',
72+
capabilities: {}
73+
},
6374
id: 'init-1'
6475
} as JSONRPCMessage,
6576

@@ -99,8 +110,7 @@ async function sendPostRequest(
99110

100111
if (sessionId) {
101112
headers['mcp-session-id'] = sessionId;
102-
// After initialization, include the protocol version header
103-
headers['mcp-protocol-version'] = '2025-03-26';
113+
headers['mcp-protocol-version'] = '2025-11-25';
104114
}
105115

106116
return fetch(baseUrl, {
@@ -461,7 +471,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
461471
headers: {
462472
Accept: 'text/event-stream',
463473
'mcp-session-id': sessionId,
464-
'mcp-protocol-version': '2025-03-26'
474+
'mcp-protocol-version': '2025-11-25'
465475
}
466476
});
467477

@@ -502,7 +512,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
502512
headers: {
503513
Accept: 'text/event-stream',
504514
'mcp-session-id': sessionId,
505-
'mcp-protocol-version': '2025-03-26'
515+
'mcp-protocol-version': '2025-11-25'
506516
}
507517
});
508518

@@ -534,7 +544,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
534544
headers: {
535545
Accept: 'text/event-stream',
536546
'mcp-session-id': sessionId,
537-
'mcp-protocol-version': '2025-03-26'
547+
'mcp-protocol-version': '2025-11-25'
538548
}
539549
});
540550

@@ -546,7 +556,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
546556
headers: {
547557
Accept: 'text/event-stream',
548558
'mcp-session-id': sessionId,
549-
'mcp-protocol-version': '2025-03-26'
559+
'mcp-protocol-version': '2025-11-25'
550560
}
551561
});
552562

@@ -565,7 +575,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
565575
headers: {
566576
Accept: 'application/json',
567577
'mcp-session-id': sessionId,
568-
'mcp-protocol-version': '2025-03-26'
578+
'mcp-protocol-version': '2025-11-25'
569579
}
570580
});
571581

@@ -759,7 +769,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
759769
headers: {
760770
Accept: 'text/event-stream',
761771
'mcp-session-id': sessionId,
762-
'mcp-protocol-version': '2025-03-26'
772+
'mcp-protocol-version': '2025-11-25'
763773
}
764774
});
765775

@@ -797,7 +807,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
797807
method: 'DELETE',
798808
headers: {
799809
'mcp-session-id': tempSessionId || '',
800-
'mcp-protocol-version': '2025-03-26'
810+
'mcp-protocol-version': '2025-11-25'
801811
}
802812
});
803813

@@ -816,7 +826,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
816826
method: 'DELETE',
817827
headers: {
818828
'mcp-session-id': 'invalid-session-id',
819-
'mcp-protocol-version': '2025-03-26'
829+
'mcp-protocol-version': '2025-11-25'
820830
}
821831
});
822832

@@ -870,15 +880,12 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
870880

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

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

879-
// Spy on console.warn to verify warning is logged
880-
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
881-
882889
// Send request with different but supported protocol version
883890
const response = await fetch(baseUrl, {
884891
method: 'POST',
@@ -893,11 +900,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
893900

894901
// Request should still succeed
895902
expect(response.status).toBe(200);
896-
897-
warnSpy.mockRestore();
898903
});
899904

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

903908
// GET request with unsupported protocol version
@@ -906,30 +911,30 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
906911
headers: {
907912
Accept: 'text/event-stream',
908913
'mcp-session-id': sessionId,
909-
'mcp-protocol-version': 'invalid-version'
914+
'mcp-protocol-version': '1999-01-01' // Unsupported version
910915
}
911916
});
912917

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

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

921926
// DELETE request with unsupported protocol version
922927
const response = await fetch(baseUrl, {
923928
method: 'DELETE',
924929
headers: {
925930
'mcp-session-id': sessionId,
926-
'mcp-protocol-version': 'invalid-version'
931+
'mcp-protocol-version': '1999-01-01' // Unsupported version
927932
}
928933
});
929934

930935
expect(response.status).toBe(400);
931936
const errorData = await response.json();
932-
expectErrorResponse(errorData, -32000, /Bad Request: Unsupported protocol version \(supported versions: .+\)/);
937+
expectErrorResponse(errorData, -32000, /Bad Request: Unsupported protocol version/);
933938
});
934939
});
935940
});
@@ -1326,7 +1331,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
13261331
headers: {
13271332
Accept: 'text/event-stream',
13281333
'mcp-session-id': sessionId,
1329-
'mcp-protocol-version': '2025-03-26'
1334+
'mcp-protocol-version': '2025-11-25'
13301335
}
13311336
});
13321337

@@ -1371,7 +1376,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
13711376
headers: {
13721377
Accept: 'text/event-stream',
13731378
'mcp-session-id': sessionId,
1374-
'mcp-protocol-version': '2025-03-26'
1379+
'mcp-protocol-version': '2025-11-25'
13751380
}
13761381
});
13771382
expect(sseResponse.status).toBe(200);
@@ -1405,7 +1410,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
14051410
headers: {
14061411
Accept: 'text/event-stream',
14071412
'mcp-session-id': sessionId,
1408-
'mcp-protocol-version': '2025-03-26',
1413+
'mcp-protocol-version': '2025-11-25',
14091414
'last-event-id': firstEventId
14101415
}
14111416
});
@@ -1429,7 +1434,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
14291434
headers: {
14301435
Accept: 'text/event-stream',
14311436
'mcp-session-id': sessionId,
1432-
'mcp-protocol-version': '2025-03-26'
1437+
'mcp-protocol-version': '2025-11-25'
14331438
}
14341439
});
14351440
expect(sseResponse.status).toBe(200);
@@ -1462,7 +1467,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
14621467
headers: {
14631468
Accept: 'text/event-stream',
14641469
'mcp-session-id': sessionId,
1465-
'mcp-protocol-version': '2025-03-26',
1470+
'mcp-protocol-version': '2025-11-25',
14661471
'last-event-id': lastEventId
14671472
}
14681473
});
@@ -1566,7 +1571,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
15661571
method: 'GET',
15671572
headers: {
15681573
Accept: 'text/event-stream',
1569-
'mcp-protocol-version': '2025-03-26'
1574+
'mcp-protocol-version': '2025-11-25'
15701575
}
15711576
});
15721577
expect(stream1.status).toBe(200);
@@ -1576,7 +1581,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
15761581
method: 'GET',
15771582
headers: {
15781583
Accept: 'text/event-stream',
1579-
'mcp-protocol-version': '2025-03-26'
1584+
'mcp-protocol-version': '2025-11-25'
15801585
}
15811586
});
15821587
expect(stream2.status).toBe(409); // Conflict - only one stream allowed
@@ -1693,12 +1698,12 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
16931698
baseUrl = result.baseUrl;
16941699
mcpServer = result.mcpServer;
16951700

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

1701-
// Send a tool call request with OLD protocol version
1706+
// Send a tool call request with the same OLD protocol version
17021707
const toolCallRequest: JSONRPCMessage = {
17031708
jsonrpc: '2.0',
17041709
id: 100,
@@ -1933,12 +1938,12 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
19331938
return { content: [{ type: 'text', text: 'Done' }] };
19341939
});
19351940

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

1941-
// Call the tool with OLD protocol version
1946+
// Call the tool with the same OLD protocol version
19421947
const toolCallRequest: JSONRPCMessage = {
19431948
jsonrpc: '2.0',
19441949
id: 200,
@@ -2010,7 +2015,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
20102015
'Content-Type': 'application/json',
20112016
Accept: 'text/event-stream, application/json',
20122017
'mcp-session-id': sessionId,
2013-
'mcp-protocol-version': '2025-03-26'
2018+
'mcp-protocol-version': '2025-11-25'
20142019
},
20152020
body: JSON.stringify(toolCallRequest)
20162021
});
@@ -2281,8 +2286,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
22812286

22822287
// Verify we received the notification that was sent while disconnected
22832288
expect(allText).toContain('Missed while disconnected');
2284-
});
2285-
}, 10000);
2289+
}, 10000);
2290+
});
22862291

22872292
// Test onsessionclosed callback
22882293
describe('StreamableHTTPServerTransport onsessionclosed callback', () => {
@@ -2308,7 +2313,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
23082313
method: 'DELETE',
23092314
headers: {
23102315
'mcp-session-id': tempSessionId || '',
2311-
'mcp-protocol-version': '2025-03-26'
2316+
'mcp-protocol-version': '2025-11-25'
23122317
}
23132318
});
23142319

@@ -2368,7 +2373,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
23682373
method: 'DELETE',
23692374
headers: {
23702375
'mcp-session-id': 'invalid-session-id',
2371-
'mcp-protocol-version': '2025-03-26'
2376+
'mcp-protocol-version': '2025-11-25'
23722377
}
23732378
});
23742379

@@ -2416,7 +2421,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
24162421
method: 'DELETE',
24172422
headers: {
24182423
'mcp-session-id': sessionId1 || '',
2419-
'mcp-protocol-version': '2025-03-26'
2424+
'mcp-protocol-version': '2025-11-25'
24202425
}
24212426
});
24222427

@@ -2429,7 +2434,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
24292434
method: 'DELETE',
24302435
headers: {
24312436
'mcp-session-id': sessionId2 || '',
2432-
'mcp-protocol-version': '2025-03-26'
2437+
'mcp-protocol-version': '2025-11-25'
24332438
}
24342439
});
24352440

@@ -2528,7 +2533,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
25282533
method: 'DELETE',
25292534
headers: {
25302535
'mcp-session-id': tempSessionId || '',
2531-
'mcp-protocol-version': '2025-03-26'
2536+
'mcp-protocol-version': '2025-11-25'
25322537
}
25332538
});
25342539

@@ -2589,7 +2594,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
25892594
method: 'DELETE',
25902595
headers: {
25912596
'mcp-session-id': tempSessionId || '',
2592-
'mcp-protocol-version': '2025-03-26'
2597+
'mcp-protocol-version': '2025-11-25'
25932598
}
25942599
});
25952600

@@ -2633,7 +2638,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
26332638
method: 'DELETE',
26342639
headers: {
26352640
'mcp-session-id': tempSessionId || '',
2636-
'mcp-protocol-version': '2025-03-26'
2641+
'mcp-protocol-version': '2025-11-25'
26372642
}
26382643
});
26392644

test/types.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ describe('Types', () => {
178178
annotations: {
179179
audience: ['user'],
180180
priority: 0.5,
181-
lastModified: new Date().toISOString()
181+
lastModified: mockDate
182182
}
183183
};
184184

0 commit comments

Comments
 (0)