feat: allow overriding protocol versions in Client, Server, and Transport (#1448)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Matt 2026-02-03 14:59:12 +00:00 • committed by GitHub
commit 160902eb2e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 119 additions and 9 deletions

View file

@ -0,0 +1,66 @@
/**
* Example: Custom Protocol Version Support
*
* This demonstrates how to support protocol versions not yet in the SDK.
* First version in the list is used as fallback when client requests
* an unsupported version.
*
* Run with: pnpm tsx src/customProtocolVersion.ts
*/
import { randomUUID } from 'node:crypto';
import { createServer } from 'node:http';
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
import type { CallToolResult } from '@modelcontextprotocol/server';
import { McpServer, SUPPORTED_PROTOCOL_VERSIONS } from '@modelcontextprotocol/server';
// Add support for a newer protocol version (first in list is fallback)
const CUSTOM_VERSIONS = ['2026-01-01', ...SUPPORTED_PROTOCOL_VERSIONS];
const server = new McpServer(
{ name: 'custom-protocol-server', version: '1.0.0' },
{
supportedProtocolVersions: CUSTOM_VERSIONS,
capabilities: { tools: {} }
}
);
// Register a tool that shows the protocol configuration
server.registerTool(
'get-protocol-info',
{
title: 'Protocol Info',
description: 'Returns protocol version configuration',
inputSchema: {}
},
async (): Promise<CallToolResult> => ({
content: [
{
type: 'text',
text: JSON.stringify({ supportedVersions: CUSTOM_VERSIONS }, null, 2)
}
]
})
);
// Create transport - server passes versions automatically during connect()
const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID()
});
await server.connect(transport);
// Simple HTTP server
const PORT = process.env.MCP_PORT ? Number.parseInt(process.env.MCP_PORT, 10) : 3000;
createServer(async (req, res) => {
if (req.url === '/mcp') {
await transport.handleRequest(req, res);
} else {
res.writeHead(404).end('Not Found');
}
}).listen(PORT, () => {
console.log(`MCP server with custom protocol versions on port ${PORT}`);
console.log(`Supported versions: ${CUSTOM_VERSIONS.join(', ')}`);
});

View file

@ -60,8 +60,7 @@ import {
mergeCapabilities,
Protocol,
ReadResourceResultSchema,
safeParse,
SUPPORTED_PROTOCOL_VERSIONS
safeParse
} from '@modelcontextprotocol/core';
import { ExperimentalClientTasks } from '../experimental/tasks/client.js';
@ -476,7 +475,7 @@ export class Client<
{
method: 'initialize',
params: {
protocolVersion: LATEST_PROTOCOL_VERSION,
protocolVersion: this._supportedProtocolVersions[0] ?? LATEST_PROTOCOL_VERSION,
capabilities: this._capabilities,
clientInfo: this._clientInfo
}
@ -489,7 +488,7 @@ export class Client<
throw new Error(`Server sent invalid initialize result: ${result}`);
}
if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) {
if (!this._supportedProtocolVersions.includes(result.protocolVersion)) {
throw new Error(`Server's protocol version is not supported: ${result.protocolVersion}`);
}

View file

@ -46,6 +46,7 @@ import {
ListTasksResultSchema,
McpError,
RELATED_TASK_META_KEY,
SUPPORTED_PROTOCOL_VERSIONS,
TaskStatusNotificationSchema
} from '../types/types.js';
import type { AnySchema, SchemaOutput } from '../util/zodCompat.js';
@ -63,6 +64,14 @@ export type ProgressCallback = (progress: Progress) => void;
* Additional initialization options.
*/
export type ProtocolOptions = {
/**
* Protocol versions supported. First version is preferred (sent by client,
* used as fallback by server). Passed to transport during connect().
*
* @default SUPPORTED_PROTOCOL_VERSIONS
*/
supportedProtocolVersions?: string[];
/**
* Whether to restrict emitted requests to only those that the remote side has indicated that they can handle, through their advertised capabilities.
*
@ -342,6 +351,8 @@ export abstract class Protocol<SendRequestT extends Request, SendNotificationT e
private _requestResolvers: Map<RequestId, (response: JSONRPCResultResponse | Error) => void> = new Map();
protected _supportedProtocolVersions: string[];
/**
* Callback for when the connection is closed for any reason.
*
@ -367,6 +378,8 @@ export abstract class Protocol<SendRequestT extends Request, SendNotificationT e
fallbackNotificationHandler?: (notification: Notification) => Promise<void>;
constructor(private _options?: ProtocolOptions) {
this._supportedProtocolVersions = _options?.supportedProtocolVersions ?? SUPPORTED_PROTOCOL_VERSIONS;
this.setNotificationHandler('notifications/cancelled', notification => {
this._oncancel(notification);
});
@ -633,6 +646,9 @@ export abstract class Protocol<SendRequestT extends Request, SendNotificationT e
}
};
// Pass supported protocol versions to transport for header validation
transport.setSupportedProtocolVersions?.(this._supportedProtocolVersions);
await this._transport.start();
}

View file

@ -125,4 +125,10 @@ export interface Transport {
* Sets the protocol version used for the connection (called when the initialize response is received).
*/
setProtocolVersion?: (version: string) => void;
/**
* Sets the supported protocol versions for header validation (called during connect).
* This allows the server to pass its supported versions to the transport.
*/
setSupportedProtocolVersions?: (versions: string[]) => void;
}

View file

@ -51,8 +51,7 @@ import {
McpError,
mergeCapabilities,
Protocol,
safeParse,
SUPPORTED_PROTOCOL_VERSIONS
safeParse
} from '@modelcontextprotocol/core';
import { ExperimentalServerTasks } from '../experimental/tasks/server.js';
@ -435,7 +434,9 @@ export class Server<
this._clientCapabilities = request.params.capabilities;
this._clientVersion = request.params.clientInfo;
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion) ? requestedVersion : LATEST_PROTOCOL_VERSION;
const protocolVersion = this._supportedProtocolVersions.includes(requestedVersion)
? requestedVersion
: (this._supportedProtocolVersions[0] ?? LATEST_PROTOCOL_VERSION);
return {
protocolVersion,

View file

@ -142,6 +142,18 @@ export interface WebStandardStreamableHTTPServerTransportOptions {
* client reconnection timing for polling behavior.
*/
retryInterval?: number;
/**
* List of protocol versions that this transport will accept.
* Used to validate the mcp-protocol-version header in incoming requests.
*
* Note: When using Server.connect(), the server automatically passes its
* supportedProtocolVersions to the transport, so you typically don't need
* to set this option directly.
*
* @default SUPPORTED_PROTOCOL_VERSIONS
*/
supportedProtocolVersions?: string[];
}
/**
@ -220,6 +232,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
private _allowedOrigins?: string[];
private _enableDnsRebindingProtection: boolean;
private _retryInterval?: number;
private _supportedProtocolVersions: string[];
sessionId?: string;
onclose?: () => void;
@ -236,6 +249,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
this._allowedOrigins = options.allowedOrigins;
this._enableDnsRebindingProtection = options.enableDnsRebindingProtection ?? false;
this._retryInterval = options.retryInterval;
this._supportedProtocolVersions = options.supportedProtocolVersions ?? SUPPORTED_PROTOCOL_VERSIONS;
}
/**
@ -249,6 +263,14 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
this._started = true;
}
/**
* Sets the supported protocol versions for header validation.
* Called by the server during connect() to pass its supported versions.
*/
setSupportedProtocolVersions(versions: string[]): void {
this._supportedProtocolVersions = versions;
}
/**
* Helper to create a JSON error response
*/
@ -848,11 +870,11 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
private validateProtocolVersion(req: Request): Response | undefined {
const protocolVersion = req.headers.get('mcp-protocol-version');
if (protocolVersion !== null && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
if (protocolVersion !== null && !this._supportedProtocolVersions.includes(protocolVersion)) {
return this.createJsonErrorResponse(
400,
-32_000,
`Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(', ')})`
`Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${this._supportedProtocolVersions.join(', ')})`
);
}
return undefined;