Skip to content

Commit 160902e

Browse files
mattzcareyclaude
andauthored
feat: allow overriding protocol versions in Client, Server, and Transport (#1448)
Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent 92f5ce5 commit 160902e

File tree

6 files changed

+119
-9
lines changed

6 files changed

+119
-9
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Example: Custom Protocol Version Support
3+
*
4+
* This demonstrates how to support protocol versions not yet in the SDK.
5+
* First version in the list is used as fallback when client requests
6+
* an unsupported version.
7+
*
8+
* Run with: pnpm tsx src/customProtocolVersion.ts
9+
*/
10+
11+
import { randomUUID } from 'node:crypto';
12+
import { createServer } from 'node:http';
13+
14+
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
15+
import type { CallToolResult } from '@modelcontextprotocol/server';
16+
import { McpServer, SUPPORTED_PROTOCOL_VERSIONS } from '@modelcontextprotocol/server';
17+
18+
// Add support for a newer protocol version (first in list is fallback)
19+
const CUSTOM_VERSIONS = ['2026-01-01', ...SUPPORTED_PROTOCOL_VERSIONS];
20+
21+
const server = new McpServer(
22+
{ name: 'custom-protocol-server', version: '1.0.0' },
23+
{
24+
supportedProtocolVersions: CUSTOM_VERSIONS,
25+
capabilities: { tools: {} }
26+
}
27+
);
28+
29+
// Register a tool that shows the protocol configuration
30+
server.registerTool(
31+
'get-protocol-info',
32+
{
33+
title: 'Protocol Info',
34+
description: 'Returns protocol version configuration',
35+
inputSchema: {}
36+
},
37+
async (): Promise<CallToolResult> => ({
38+
content: [
39+
{
40+
type: 'text',
41+
text: JSON.stringify({ supportedVersions: CUSTOM_VERSIONS }, null, 2)
42+
}
43+
]
44+
})
45+
);
46+
47+
// Create transport - server passes versions automatically during connect()
48+
const transport = new NodeStreamableHTTPServerTransport({
49+
sessionIdGenerator: () => randomUUID()
50+
});
51+
52+
await server.connect(transport);
53+
54+
// Simple HTTP server
55+
const PORT = process.env.MCP_PORT ? Number.parseInt(process.env.MCP_PORT, 10) : 3000;
56+
57+
createServer(async (req, res) => {
58+
if (req.url === '/mcp') {
59+
await transport.handleRequest(req, res);
60+
} else {
61+
res.writeHead(404).end('Not Found');
62+
}
63+
}).listen(PORT, () => {
64+
console.log(`MCP server with custom protocol versions on port ${PORT}`);
65+
console.log(`Supported versions: ${CUSTOM_VERSIONS.join(', ')}`);
66+
});

packages/client/src/client/client.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ import {
6060
mergeCapabilities,
6161
Protocol,
6262
ReadResourceResultSchema,
63-
safeParse,
64-
SUPPORTED_PROTOCOL_VERSIONS
63+
safeParse
6564
} from '@modelcontextprotocol/core';
6665

6766
import { ExperimentalClientTasks } from '../experimental/tasks/client.js';
@@ -476,7 +475,7 @@ export class Client<
476475
{
477476
method: 'initialize',
478477
params: {
479-
protocolVersion: LATEST_PROTOCOL_VERSION,
478+
protocolVersion: this._supportedProtocolVersions[0] ?? LATEST_PROTOCOL_VERSION,
480479
capabilities: this._capabilities,
481480
clientInfo: this._clientInfo
482481
}
@@ -489,7 +488,7 @@ export class Client<
489488
throw new Error(`Server sent invalid initialize result: ${result}`);
490489
}
491490

492-
if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) {
491+
if (!this._supportedProtocolVersions.includes(result.protocolVersion)) {
493492
throw new Error(`Server's protocol version is not supported: ${result.protocolVersion}`);
494493
}
495494

packages/core/src/shared/protocol.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
ListTasksResultSchema,
4747
McpError,
4848
RELATED_TASK_META_KEY,
49+
SUPPORTED_PROTOCOL_VERSIONS,
4950
TaskStatusNotificationSchema
5051
} from '../types/types.js';
5152
import type { AnySchema, SchemaOutput } from '../util/zodCompat.js';
@@ -63,6 +64,14 @@ export type ProgressCallback = (progress: Progress) => void;
6364
* Additional initialization options.
6465
*/
6566
export type ProtocolOptions = {
67+
/**
68+
* Protocol versions supported. First version is preferred (sent by client,
69+
* used as fallback by server). Passed to transport during connect().
70+
*
71+
* @default SUPPORTED_PROTOCOL_VERSIONS
72+
*/
73+
supportedProtocolVersions?: string[];
74+
6675
/**
6776
* Whether to restrict emitted requests to only those that the remote side has indicated that they can handle, through their advertised capabilities.
6877
*
@@ -342,6 +351,8 @@ export abstract class Protocol<SendRequestT extends Request, SendNotificationT e
342351

343352
private _requestResolvers: Map<RequestId, (response: JSONRPCResultResponse | Error) => void> = new Map();
344353

354+
protected _supportedProtocolVersions: string[];
355+
345356
/**
346357
* Callback for when the connection is closed for any reason.
347358
*
@@ -367,6 +378,8 @@ export abstract class Protocol<SendRequestT extends Request, SendNotificationT e
367378
fallbackNotificationHandler?: (notification: Notification) => Promise<void>;
368379

369380
constructor(private _options?: ProtocolOptions) {
381+
this._supportedProtocolVersions = _options?.supportedProtocolVersions ?? SUPPORTED_PROTOCOL_VERSIONS;
382+
370383
this.setNotificationHandler('notifications/cancelled', notification => {
371384
this._oncancel(notification);
372385
});
@@ -633,6 +646,9 @@ export abstract class Protocol<SendRequestT extends Request, SendNotificationT e
633646
}
634647
};
635648

649+
// Pass supported protocol versions to transport for header validation
650+
transport.setSupportedProtocolVersions?.(this._supportedProtocolVersions);
651+
636652
await this._transport.start();
637653
}
638654

packages/core/src/shared/transport.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,10 @@ export interface Transport {
125125
* Sets the protocol version used for the connection (called when the initialize response is received).
126126
*/
127127
setProtocolVersion?: (version: string) => void;
128+
129+
/**
130+
* Sets the supported protocol versions for header validation (called during connect).
131+
* This allows the server to pass its supported versions to the transport.
132+
*/
133+
setSupportedProtocolVersions?: (versions: string[]) => void;
128134
}

packages/server/src/server/server.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,7 @@ import {
5151
McpError,
5252
mergeCapabilities,
5353
Protocol,
54-
safeParse,
55-
SUPPORTED_PROTOCOL_VERSIONS
54+
safeParse
5655
} from '@modelcontextprotocol/core';
5756

5857
import { ExperimentalServerTasks } from '../experimental/tasks/server.js';
@@ -435,7 +434,9 @@ export class Server<
435434
this._clientCapabilities = request.params.capabilities;
436435
this._clientVersion = request.params.clientInfo;
437436

438-
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion) ? requestedVersion : LATEST_PROTOCOL_VERSION;
437+
const protocolVersion = this._supportedProtocolVersions.includes(requestedVersion)
438+
? requestedVersion
439+
: (this._supportedProtocolVersions[0] ?? LATEST_PROTOCOL_VERSION);
439440

440441
return {
441442
protocolVersion,

packages/server/src/server/streamableHttp.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,18 @@ export interface WebStandardStreamableHTTPServerTransportOptions {
142142
* client reconnection timing for polling behavior.
143143
*/
144144
retryInterval?: number;
145+
146+
/**
147+
* List of protocol versions that this transport will accept.
148+
* Used to validate the mcp-protocol-version header in incoming requests.
149+
*
150+
* Note: When using Server.connect(), the server automatically passes its
151+
* supportedProtocolVersions to the transport, so you typically don't need
152+
* to set this option directly.
153+
*
154+
* @default SUPPORTED_PROTOCOL_VERSIONS
155+
*/
156+
supportedProtocolVersions?: string[];
145157
}
146158

147159
/**
@@ -220,6 +232,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
220232
private _allowedOrigins?: string[];
221233
private _enableDnsRebindingProtection: boolean;
222234
private _retryInterval?: number;
235+
private _supportedProtocolVersions: string[];
223236

224237
sessionId?: string;
225238
onclose?: () => void;
@@ -236,6 +249,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
236249
this._allowedOrigins = options.allowedOrigins;
237250
this._enableDnsRebindingProtection = options.enableDnsRebindingProtection ?? false;
238251
this._retryInterval = options.retryInterval;
252+
this._supportedProtocolVersions = options.supportedProtocolVersions ?? SUPPORTED_PROTOCOL_VERSIONS;
239253
}
240254

241255
/**
@@ -249,6 +263,14 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
249263
this._started = true;
250264
}
251265

266+
/**
267+
* Sets the supported protocol versions for header validation.
268+
* Called by the server during connect() to pass its supported versions.
269+
*/
270+
setSupportedProtocolVersions(versions: string[]): void {
271+
this._supportedProtocolVersions = versions;
272+
}
273+
252274
/**
253275
* Helper to create a JSON error response
254276
*/
@@ -848,11 +870,11 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
848870
private validateProtocolVersion(req: Request): Response | undefined {
849871
const protocolVersion = req.headers.get('mcp-protocol-version');
850872

851-
if (protocolVersion !== null && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
873+
if (protocolVersion !== null && !this._supportedProtocolVersions.includes(protocolVersion)) {
852874
return this.createJsonErrorResponse(
853875
400,
854876
-32_000,
855-
`Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(', ')})`
877+
`Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${this._supportedProtocolVersions.join(', ')})`
856878
);
857879
}
858880
return undefined;

0 commit comments

Comments
 (0)