From 559650332c47f41391b7b5f9698bdb2bc4b96fec Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Mon, 2 Feb 2026 17:21:01 +0000 Subject: [PATCH 1/5] feat: allow overriding protocol versions in Client, Server, and Transport Adds options to configure protocol versions for cases where the SDK's default version list doesn't include versions needed by clients or servers. - Add `protocolVersion` and `supportedProtocolVersions` options to ClientOptions - Add `protocolVersion` and `supportedProtocolVersions` options to ServerOptions - Add `supportedProtocolVersions` option to WebStandardStreamableHTTPServerTransportOptions - Add example showing custom protocol version configuration with Hono This enables servers using older SDK versions to support newer protocol versions (like 2025-11-25) without waiting for SDK updates. --- .../server/src/honoCustomProtocolVersion.ts | 140 ++++++++++++++++++ packages/client/src/client/client.ts | 43 +++++- packages/server/src/server/server.ts | 42 +++++- packages/server/src/server/streamableHttp.ts | 21 ++- 4 files changed, 241 insertions(+), 5 deletions(-) create mode 100644 examples/server/src/honoCustomProtocolVersion.ts diff --git a/examples/server/src/honoCustomProtocolVersion.ts b/examples/server/src/honoCustomProtocolVersion.ts new file mode 100644 index 000000000..57e322760 --- /dev/null +++ b/examples/server/src/honoCustomProtocolVersion.ts @@ -0,0 +1,140 @@ +/** + * Example MCP server using Hono with custom protocol version support + * + * This example demonstrates how to configure custom protocol versions for servers + * that need to support newer protocol versions not yet in the SDK's default list. + * + * Use case: When a client (like Claude Code) uses a newer protocol version that + * your SDK version doesn't know about, you can add support for it by specifying + * custom supportedProtocolVersions. + * + * Run with: pnpm tsx src/honoCustomProtocolVersion.ts + */ + +import { serve } from '@hono/node-server'; +import type { CallToolResult } from '@modelcontextprotocol/server'; +import { + LATEST_PROTOCOL_VERSION, + McpServer, + SUPPORTED_PROTOCOL_VERSIONS, + WebStandardStreamableHTTPServerTransport +} from '@modelcontextprotocol/server'; +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import * as z from 'zod/v4'; + +// Define custom protocol versions +// This example shows how to add support for a hypothetical future version +// while maintaining backwards compatibility with all existing versions +const CUSTOM_SUPPORTED_VERSIONS = [ + '2026-01-01', // Hypothetical future version + ...SUPPORTED_PROTOCOL_VERSIONS // Include all default supported versions +]; + +// Create the MCP server with custom protocol version support +const server = new McpServer( + { + name: 'hono-custom-protocol-server', + version: '1.0.0' + }, + { + // Custom supported versions for protocol negotiation + supportedProtocolVersions: CUSTOM_SUPPORTED_VERSIONS, + // Fallback version when client requests an unsupported version + protocolVersion: LATEST_PROTOCOL_VERSION + } +); + +// Register a tool that reports the server's protocol version capabilities +server.registerTool( + 'get-protocol-info', + { + title: 'Protocol Info', + description: "Returns information about the server's protocol version support", + inputSchema: {} + }, + async (): Promise => { + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + latestVersion: LATEST_PROTOCOL_VERSION, + supportedVersions: CUSTOM_SUPPORTED_VERSIONS, + message: 'This server supports custom protocol versions including future versions' + }, + null, + 2 + ) + } + ] + }; + } +); + +// Register a simple echo tool +server.registerTool( + 'echo', + { + title: 'Echo Tool', + description: 'Echoes back the input message', + inputSchema: { message: z.string().describe('Message to echo') } + }, + async ({ message }): Promise => { + return { + content: [{ type: 'text', text: `Echo: ${message}` }] + }; + } +); + +// Create a stateless transport with custom protocol version support +// The transport also needs to know about supported versions to validate +// the mcp-protocol-version header in incoming requests +const transport = new WebStandardStreamableHTTPServerTransport({ + supportedProtocolVersions: CUSTOM_SUPPORTED_VERSIONS +}); + +// Create the Hono app +const app = new Hono(); + +// Enable CORS for all origins +app.use( + '*', + cors({ + origin: '*', + allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'], + allowHeaders: ['Content-Type', 'mcp-session-id', 'Last-Event-ID', 'mcp-protocol-version'], + exposeHeaders: ['mcp-session-id', 'mcp-protocol-version'] + }) +); + +// Health check endpoint +app.get('/health', c => + c.json({ + status: 'ok', + protocolVersions: { + latest: LATEST_PROTOCOL_VERSION, + supported: CUSTOM_SUPPORTED_VERSIONS + } + }) +); + +// MCP endpoint +app.all('/mcp', c => transport.handleRequest(c.req.raw)); + +// Start the server +const PORT = process.env.MCP_PORT ? Number.parseInt(process.env.MCP_PORT, 10) : 3000; + +await server.connect(transport); + +console.log(`Starting Hono MCP server with custom protocol version support on port ${PORT}`); +console.log(`Health check: http://localhost:${PORT}/health`); +console.log(`MCP endpoint: http://localhost:${PORT}/mcp`); +console.log(`\nSupported protocol versions:`); +for (const v of CUSTOM_SUPPORTED_VERSIONS) console.log(` - ${v}`); + +serve({ + fetch: app.fetch, + port: PORT +}); diff --git a/packages/client/src/client/client.ts b/packages/client/src/client/client.ts index 9c2c87290..67a61a57d 100644 --- a/packages/client/src/client/client.ts +++ b/packages/client/src/client/client.ts @@ -204,6 +204,41 @@ export type ClientOptions = ProtocolOptions & { * ``` */ listChanged?: ListChangedHandlers; + + /** + * The protocol version to request during initialization. + * + * @default LATEST_PROTOCOL_VERSION + * + * @example + * ```typescript + * const client = new Client( + * { name: 'my-client', version: '1.0.0' }, + * { + * protocolVersion: '2025-06-18' + * } + * ); + * ``` + */ + protocolVersion?: string; + + /** + * List of protocol versions that this client will accept from servers. + * If the server responds with a version not in this list, connection will fail. + * + * @default SUPPORTED_PROTOCOL_VERSIONS + * + * @example + * ```typescript + * const client = new Client( + * { name: 'my-client', version: '1.0.0' }, + * { + * supportedProtocolVersions: ['2025-11-25', '2025-06-18', '2025-03-26'] + * } + * ); + * ``` + */ + supportedProtocolVersions?: string[]; }; /** @@ -248,6 +283,8 @@ export class Client< private _listChangedDebounceTimers: Map> = new Map(); private _pendingListChangedConfig?: ListChangedHandlers; private _enforceStrictCapabilities: boolean; + private _protocolVersion: string; + private _supportedProtocolVersions: string[]; /** * Initializes this client with the given name and version information. @@ -260,6 +297,8 @@ export class Client< this._capabilities = options?.capabilities ?? {}; this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator(); this._enforceStrictCapabilities = options?.enforceStrictCapabilities ?? false; + this._protocolVersion = options?.protocolVersion ?? LATEST_PROTOCOL_VERSION; + this._supportedProtocolVersions = options?.supportedProtocolVersions ?? SUPPORTED_PROTOCOL_VERSIONS; // Store list changed config for setup after connection (when we know server capabilities) if (options?.listChanged) { @@ -476,7 +515,7 @@ export class Client< { method: 'initialize', params: { - protocolVersion: LATEST_PROTOCOL_VERSION, + protocolVersion: this._protocolVersion, capabilities: this._capabilities, clientInfo: this._clientInfo } @@ -489,7 +528,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}`); } diff --git a/packages/server/src/server/server.ts b/packages/server/src/server/server.ts index ca43272cf..f7221edb8 100644 --- a/packages/server/src/server/server.ts +++ b/packages/server/src/server/server.ts @@ -98,6 +98,42 @@ export type ServerOptions = ProtocolOptions & { * ``` */ jsonSchemaValidator?: jsonSchemaValidator; + + /** + * The fallback protocol version to use when the client requests an unsupported version. + * + * @default LATEST_PROTOCOL_VERSION + * + * @example + * ```typescript + * const server = new Server( + * { name: 'my-server', version: '1.0.0' }, + * { + * protocolVersion: '2025-06-18' + * } + * ); + * ``` + */ + protocolVersion?: string; + + /** + * List of protocol versions that this server supports. + * When a client requests a version in this list, it will be used. + * Otherwise, the server falls back to `protocolVersion`. + * + * @default SUPPORTED_PROTOCOL_VERSIONS + * + * @example + * ```typescript + * const server = new Server( + * { name: 'my-server', version: '1.0.0' }, + * { + * supportedProtocolVersions: ['2025-11-25', '2025-06-18', '2025-03-26'] + * } + * ); + * ``` + */ + supportedProtocolVersions?: string[]; }; /** @@ -137,6 +173,8 @@ export class Server< private _instructions?: string; private _jsonSchemaValidator: jsonSchemaValidator; private _experimental?: { tasks: ExperimentalServerTasks }; + private _protocolVersion: string; + private _supportedProtocolVersions: string[]; /** * Callback for when initialization has fully completed (i.e., the client has sent an `initialized` notification). @@ -154,6 +192,8 @@ export class Server< this._capabilities = options?.capabilities ?? {}; this._instructions = options?.instructions; this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator(); + this._protocolVersion = options?.protocolVersion ?? LATEST_PROTOCOL_VERSION; + this._supportedProtocolVersions = options?.supportedProtocolVersions ?? SUPPORTED_PROTOCOL_VERSIONS; this.setRequestHandler('initialize', request => this._oninitialize(request)); this.setNotificationHandler('notifications/initialized', () => this.oninitialized?.()); @@ -435,7 +475,7 @@ 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._protocolVersion; return { protocolVersion, diff --git a/packages/server/src/server/streamableHttp.ts b/packages/server/src/server/streamableHttp.ts index ae8bad97e..5338526b0 100644 --- a/packages/server/src/server/streamableHttp.ts +++ b/packages/server/src/server/streamableHttp.ts @@ -142,6 +142,21 @@ 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. + * + * @default SUPPORTED_PROTOCOL_VERSIONS + * + * @example + * ```typescript + * const transport = new WebStandardStreamableHTTPServerTransport({ + * supportedProtocolVersions: ['2025-11-25', '2025-06-18', '2025-03-26'] + * }); + * ``` + */ + supportedProtocolVersions?: string[]; } /** @@ -220,6 +235,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport { private _allowedOrigins?: string[]; private _enableDnsRebindingProtection: boolean; private _retryInterval?: number; + private _supportedProtocolVersions: string[]; sessionId?: string; onclose?: () => void; @@ -236,6 +252,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; } /** @@ -848,11 +865,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; From 74d938f896ddcb7e7d3096a07d8c10e049828e64 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 3 Feb 2026 10:50:45 +0000 Subject: [PATCH 2/5] refactor: simplify protocol version configuration Server now passes supportedProtocolVersions to transport during connect(), eliminating the need to configure versions in both places. - Add setSupportedProtocolVersions to Transport interface - Server.connect() calls transport.setSupportedProtocolVersions() - Replace Hono example with simpler Node.js http example Co-Authored-By: Claude Opus 4.5 --- examples/server/src/customProtocolVersion.ts | 76 ++++++++++ .../server/src/honoCustomProtocolVersion.ts | 140 ------------------ packages/core/src/shared/transport.ts | 6 + packages/server/src/server/server.ts | 12 +- packages/server/src/server/streamableHttp.ts | 19 ++- 5 files changed, 105 insertions(+), 148 deletions(-) create mode 100644 examples/server/src/customProtocolVersion.ts delete mode 100644 examples/server/src/honoCustomProtocolVersion.ts diff --git a/examples/server/src/customProtocolVersion.ts b/examples/server/src/customProtocolVersion.ts new file mode 100644 index 000000000..ce483a274 --- /dev/null +++ b/examples/server/src/customProtocolVersion.ts @@ -0,0 +1,76 @@ +/** + * Example: Custom Protocol Version Support + * + * This demonstrates how to support protocol versions not yet in the SDK. + * When a client (like Claude Code) uses a newer protocol version, you can + * add support for it without waiting for an SDK update. + * + * 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 { LATEST_PROTOCOL_VERSION, McpServer, SUPPORTED_PROTOCOL_VERSIONS } from '@modelcontextprotocol/server'; + +// Add support for a newer protocol version +// The server will pass these to the transport automatically during connect() +const CUSTOM_VERSIONS = ['2026-01-01', ...SUPPORTED_PROTOCOL_VERSIONS]; + +const server = new McpServer( + { name: 'custom-protocol-server', version: '1.0.0' }, + { + supportedProtocolVersions: CUSTOM_VERSIONS, + protocolVersion: LATEST_PROTOCOL_VERSION, // fallback for unsupported 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 => ({ + content: [ + { + type: 'text', + text: JSON.stringify( + { + supportedVersions: CUSTOM_VERSIONS, + fallbackVersion: LATEST_PROTOCOL_VERSION + }, + null, + 2 + ) + } + ] + }) +); + +// Create transport - no need to configure versions here, +// server passes them 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(', ')}`); +}); diff --git a/examples/server/src/honoCustomProtocolVersion.ts b/examples/server/src/honoCustomProtocolVersion.ts deleted file mode 100644 index 57e322760..000000000 --- a/examples/server/src/honoCustomProtocolVersion.ts +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Example MCP server using Hono with custom protocol version support - * - * This example demonstrates how to configure custom protocol versions for servers - * that need to support newer protocol versions not yet in the SDK's default list. - * - * Use case: When a client (like Claude Code) uses a newer protocol version that - * your SDK version doesn't know about, you can add support for it by specifying - * custom supportedProtocolVersions. - * - * Run with: pnpm tsx src/honoCustomProtocolVersion.ts - */ - -import { serve } from '@hono/node-server'; -import type { CallToolResult } from '@modelcontextprotocol/server'; -import { - LATEST_PROTOCOL_VERSION, - McpServer, - SUPPORTED_PROTOCOL_VERSIONS, - WebStandardStreamableHTTPServerTransport -} from '@modelcontextprotocol/server'; -import { Hono } from 'hono'; -import { cors } from 'hono/cors'; -import * as z from 'zod/v4'; - -// Define custom protocol versions -// This example shows how to add support for a hypothetical future version -// while maintaining backwards compatibility with all existing versions -const CUSTOM_SUPPORTED_VERSIONS = [ - '2026-01-01', // Hypothetical future version - ...SUPPORTED_PROTOCOL_VERSIONS // Include all default supported versions -]; - -// Create the MCP server with custom protocol version support -const server = new McpServer( - { - name: 'hono-custom-protocol-server', - version: '1.0.0' - }, - { - // Custom supported versions for protocol negotiation - supportedProtocolVersions: CUSTOM_SUPPORTED_VERSIONS, - // Fallback version when client requests an unsupported version - protocolVersion: LATEST_PROTOCOL_VERSION - } -); - -// Register a tool that reports the server's protocol version capabilities -server.registerTool( - 'get-protocol-info', - { - title: 'Protocol Info', - description: "Returns information about the server's protocol version support", - inputSchema: {} - }, - async (): Promise => { - return { - content: [ - { - type: 'text', - text: JSON.stringify( - { - latestVersion: LATEST_PROTOCOL_VERSION, - supportedVersions: CUSTOM_SUPPORTED_VERSIONS, - message: 'This server supports custom protocol versions including future versions' - }, - null, - 2 - ) - } - ] - }; - } -); - -// Register a simple echo tool -server.registerTool( - 'echo', - { - title: 'Echo Tool', - description: 'Echoes back the input message', - inputSchema: { message: z.string().describe('Message to echo') } - }, - async ({ message }): Promise => { - return { - content: [{ type: 'text', text: `Echo: ${message}` }] - }; - } -); - -// Create a stateless transport with custom protocol version support -// The transport also needs to know about supported versions to validate -// the mcp-protocol-version header in incoming requests -const transport = new WebStandardStreamableHTTPServerTransport({ - supportedProtocolVersions: CUSTOM_SUPPORTED_VERSIONS -}); - -// Create the Hono app -const app = new Hono(); - -// Enable CORS for all origins -app.use( - '*', - cors({ - origin: '*', - allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'], - allowHeaders: ['Content-Type', 'mcp-session-id', 'Last-Event-ID', 'mcp-protocol-version'], - exposeHeaders: ['mcp-session-id', 'mcp-protocol-version'] - }) -); - -// Health check endpoint -app.get('/health', c => - c.json({ - status: 'ok', - protocolVersions: { - latest: LATEST_PROTOCOL_VERSION, - supported: CUSTOM_SUPPORTED_VERSIONS - } - }) -); - -// MCP endpoint -app.all('/mcp', c => transport.handleRequest(c.req.raw)); - -// Start the server -const PORT = process.env.MCP_PORT ? Number.parseInt(process.env.MCP_PORT, 10) : 3000; - -await server.connect(transport); - -console.log(`Starting Hono MCP server with custom protocol version support on port ${PORT}`); -console.log(`Health check: http://localhost:${PORT}/health`); -console.log(`MCP endpoint: http://localhost:${PORT}/mcp`); -console.log(`\nSupported protocol versions:`); -for (const v of CUSTOM_SUPPORTED_VERSIONS) console.log(` - ${v}`); - -serve({ - fetch: app.fetch, - port: PORT -}); diff --git a/packages/core/src/shared/transport.ts b/packages/core/src/shared/transport.ts index 87608f124..3cddba482 100644 --- a/packages/core/src/shared/transport.ts +++ b/packages/core/src/shared/transport.ts @@ -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; } diff --git a/packages/server/src/server/server.ts b/packages/server/src/server/server.ts index f7221edb8..5b026ea79 100644 --- a/packages/server/src/server/server.ts +++ b/packages/server/src/server/server.ts @@ -31,7 +31,8 @@ import type { ServerRequest, ServerResult, ToolResultContent, - ToolUseContent + ToolUseContent, + Transport } from '@modelcontextprotocol/core'; import { AjvJsonSchemaValidator, @@ -503,6 +504,15 @@ export class Server< return this._capabilities; } + /** + * Connects to the given transport and passes supported protocol versions. + */ + override async connect(transport: Transport): Promise { + // Pass supported versions to transport for header validation + transport.setSupportedProtocolVersions?.(this._supportedProtocolVersions); + await super.connect(transport); + } + async ping() { return this.request({ method: 'ping' }, EmptyResultSchema); } diff --git a/packages/server/src/server/streamableHttp.ts b/packages/server/src/server/streamableHttp.ts index 5338526b0..f98ab126e 100644 --- a/packages/server/src/server/streamableHttp.ts +++ b/packages/server/src/server/streamableHttp.ts @@ -147,14 +147,11 @@ export interface WebStandardStreamableHTTPServerTransportOptions { * List of protocol versions that this transport will accept. * Used to validate the mcp-protocol-version header in incoming requests. * - * @default SUPPORTED_PROTOCOL_VERSIONS + * 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. * - * @example - * ```typescript - * const transport = new WebStandardStreamableHTTPServerTransport({ - * supportedProtocolVersions: ['2025-11-25', '2025-06-18', '2025-03-26'] - * }); - * ``` + * @default SUPPORTED_PROTOCOL_VERSIONS */ supportedProtocolVersions?: string[]; } @@ -266,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 */ From dd8e889ecd91efe70e05e56b0d312def5fadee01 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 3 Feb 2026 11:08:13 +0000 Subject: [PATCH 3/5] refactor: simplify to single supportedProtocolVersions option Remove protocolVersion option from both Client and Server. First version in supportedProtocolVersions is now used as: - Server: fallback when client requests unsupported version - Client: version sent to server during initialization Co-Authored-By: Claude Opus 4.5 --- examples/server/src/customProtocolVersion.ts | 22 ++++-------- packages/client/src/client/client.ts | 33 ++--------------- packages/server/src/server/server.ts | 38 +++----------------- 3 files changed, 14 insertions(+), 79 deletions(-) diff --git a/examples/server/src/customProtocolVersion.ts b/examples/server/src/customProtocolVersion.ts index ce483a274..992337dc0 100644 --- a/examples/server/src/customProtocolVersion.ts +++ b/examples/server/src/customProtocolVersion.ts @@ -2,8 +2,8 @@ * Example: Custom Protocol Version Support * * This demonstrates how to support protocol versions not yet in the SDK. - * When a client (like Claude Code) uses a newer protocol version, you can - * add support for it without waiting for an SDK update. + * First version in the list is used as fallback when client requests + * an unsupported version. * * Run with: pnpm tsx src/customProtocolVersion.ts */ @@ -13,17 +13,15 @@ import { createServer } from 'node:http'; import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; import type { CallToolResult } from '@modelcontextprotocol/server'; -import { LATEST_PROTOCOL_VERSION, McpServer, SUPPORTED_PROTOCOL_VERSIONS } from '@modelcontextprotocol/server'; +import { McpServer, SUPPORTED_PROTOCOL_VERSIONS } from '@modelcontextprotocol/server'; -// Add support for a newer protocol version -// The server will pass these to the transport automatically during connect() +// 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, - protocolVersion: LATEST_PROTOCOL_VERSION, // fallback for unsupported versions capabilities: { tools: {} } } ); @@ -40,21 +38,13 @@ server.registerTool( content: [ { type: 'text', - text: JSON.stringify( - { - supportedVersions: CUSTOM_VERSIONS, - fallbackVersion: LATEST_PROTOCOL_VERSION - }, - null, - 2 - ) + text: JSON.stringify({ supportedVersions: CUSTOM_VERSIONS }, null, 2) } ] }) ); -// Create transport - no need to configure versions here, -// server passes them automatically during connect() +// Create transport - server passes versions automatically during connect() const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() }); diff --git a/packages/client/src/client/client.ts b/packages/client/src/client/client.ts index 67a61a57d..5dc28149e 100644 --- a/packages/client/src/client/client.ts +++ b/packages/client/src/client/client.ts @@ -207,36 +207,11 @@ export type ClientOptions = ProtocolOptions & { /** * The protocol version to request during initialization. - * - * @default LATEST_PROTOCOL_VERSION - * - * @example - * ```typescript - * const client = new Client( - * { name: 'my-client', version: '1.0.0' }, - * { - * protocolVersion: '2025-06-18' - * } - * ); - * ``` - */ - protocolVersion?: string; - /** - * List of protocol versions that this client will accept from servers. - * If the server responds with a version not in this list, connection will fail. + * Protocol versions this client supports. First version is sent to server + * during initialization. Connection fails if server responds with unsupported version. * * @default SUPPORTED_PROTOCOL_VERSIONS - * - * @example - * ```typescript - * const client = new Client( - * { name: 'my-client', version: '1.0.0' }, - * { - * supportedProtocolVersions: ['2025-11-25', '2025-06-18', '2025-03-26'] - * } - * ); - * ``` */ supportedProtocolVersions?: string[]; }; @@ -283,7 +258,6 @@ export class Client< private _listChangedDebounceTimers: Map> = new Map(); private _pendingListChangedConfig?: ListChangedHandlers; private _enforceStrictCapabilities: boolean; - private _protocolVersion: string; private _supportedProtocolVersions: string[]; /** @@ -297,7 +271,6 @@ export class Client< this._capabilities = options?.capabilities ?? {}; this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator(); this._enforceStrictCapabilities = options?.enforceStrictCapabilities ?? false; - this._protocolVersion = options?.protocolVersion ?? LATEST_PROTOCOL_VERSION; this._supportedProtocolVersions = options?.supportedProtocolVersions ?? SUPPORTED_PROTOCOL_VERSIONS; // Store list changed config for setup after connection (when we know server capabilities) @@ -515,7 +488,7 @@ export class Client< { method: 'initialize', params: { - protocolVersion: this._protocolVersion, + protocolVersion: this._supportedProtocolVersions[0] ?? LATEST_PROTOCOL_VERSION, capabilities: this._capabilities, clientInfo: this._clientInfo } diff --git a/packages/server/src/server/server.ts b/packages/server/src/server/server.ts index 5b026ea79..5ad31a831 100644 --- a/packages/server/src/server/server.ts +++ b/packages/server/src/server/server.ts @@ -101,38 +101,10 @@ export type ServerOptions = ProtocolOptions & { jsonSchemaValidator?: jsonSchemaValidator; /** - * The fallback protocol version to use when the client requests an unsupported version. - * - * @default LATEST_PROTOCOL_VERSION - * - * @example - * ```typescript - * const server = new Server( - * { name: 'my-server', version: '1.0.0' }, - * { - * protocolVersion: '2025-06-18' - * } - * ); - * ``` - */ - protocolVersion?: string; - - /** - * List of protocol versions that this server supports. - * When a client requests a version in this list, it will be used. - * Otherwise, the server falls back to `protocolVersion`. + * Protocol versions this server supports. First version is used as fallback + * when client requests an unsupported version. * * @default SUPPORTED_PROTOCOL_VERSIONS - * - * @example - * ```typescript - * const server = new Server( - * { name: 'my-server', version: '1.0.0' }, - * { - * supportedProtocolVersions: ['2025-11-25', '2025-06-18', '2025-03-26'] - * } - * ); - * ``` */ supportedProtocolVersions?: string[]; }; @@ -174,7 +146,6 @@ export class Server< private _instructions?: string; private _jsonSchemaValidator: jsonSchemaValidator; private _experimental?: { tasks: ExperimentalServerTasks }; - private _protocolVersion: string; private _supportedProtocolVersions: string[]; /** @@ -193,7 +164,6 @@ export class Server< this._capabilities = options?.capabilities ?? {}; this._instructions = options?.instructions; this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator(); - this._protocolVersion = options?.protocolVersion ?? LATEST_PROTOCOL_VERSION; this._supportedProtocolVersions = options?.supportedProtocolVersions ?? SUPPORTED_PROTOCOL_VERSIONS; this.setRequestHandler('initialize', request => this._oninitialize(request)); @@ -476,7 +446,9 @@ export class Server< this._clientCapabilities = request.params.capabilities; this._clientVersion = request.params.clientInfo; - const protocolVersion = this._supportedProtocolVersions.includes(requestedVersion) ? requestedVersion : this._protocolVersion; + const protocolVersion = this._supportedProtocolVersions.includes(requestedVersion) + ? requestedVersion + : this._supportedProtocolVersions[0] ?? LATEST_PROTOCOL_VERSION; return { protocolVersion, From fc89b4a208f9e859f9237f2c7a9d3255e6160fce Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 3 Feb 2026 11:23:30 +0000 Subject: [PATCH 4/5] refactor: move supportedProtocolVersions to Protocol base class Consolidate the option in ProtocolOptions and handle transport version passing in Protocol.connect(). Client and Server inherit the protected _supportedProtocolVersions field. Co-Authored-By: Claude Opus 4.5 --- packages/client/src/client/client.ts | 15 +-------------- packages/core/src/shared/protocol.ts | 16 ++++++++++++++++ packages/server/src/server/server.ts | 25 ++----------------------- 3 files changed, 19 insertions(+), 37 deletions(-) diff --git a/packages/client/src/client/client.ts b/packages/client/src/client/client.ts index 5dc28149e..3175d7a47 100644 --- a/packages/client/src/client/client.ts +++ b/packages/client/src/client/client.ts @@ -60,8 +60,7 @@ import { mergeCapabilities, Protocol, ReadResourceResultSchema, - safeParse, - SUPPORTED_PROTOCOL_VERSIONS + safeParse } from '@modelcontextprotocol/core'; import { ExperimentalClientTasks } from '../experimental/tasks/client.js'; @@ -204,16 +203,6 @@ export type ClientOptions = ProtocolOptions & { * ``` */ listChanged?: ListChangedHandlers; - - /** - * The protocol version to request during initialization. - /** - * Protocol versions this client supports. First version is sent to server - * during initialization. Connection fails if server responds with unsupported version. - * - * @default SUPPORTED_PROTOCOL_VERSIONS - */ - supportedProtocolVersions?: string[]; }; /** @@ -258,7 +247,6 @@ export class Client< private _listChangedDebounceTimers: Map> = new Map(); private _pendingListChangedConfig?: ListChangedHandlers; private _enforceStrictCapabilities: boolean; - private _supportedProtocolVersions: string[]; /** * Initializes this client with the given name and version information. @@ -271,7 +259,6 @@ export class Client< this._capabilities = options?.capabilities ?? {}; this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator(); this._enforceStrictCapabilities = options?.enforceStrictCapabilities ?? false; - this._supportedProtocolVersions = options?.supportedProtocolVersions ?? SUPPORTED_PROTOCOL_VERSIONS; // Store list changed config for setup after connection (when we know server capabilities) if (options?.listChanged) { diff --git a/packages/core/src/shared/protocol.ts b/packages/core/src/shared/protocol.ts index f537aa86c..3d6c7d187 100644 --- a/packages/core/src/shared/protocol.ts +++ b/packages/core/src/shared/protocol.ts @@ -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 void> = new Map(); + protected _supportedProtocolVersions: string[]; + /** * Callback for when the connection is closed for any reason. * @@ -367,6 +378,8 @@ export abstract class Protocol Promise; 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 }; - private _supportedProtocolVersions: string[]; /** * Callback for when initialization has fully completed (i.e., the client has sent an `initialized` notification). @@ -164,7 +153,6 @@ export class Server< this._capabilities = options?.capabilities ?? {}; this._instructions = options?.instructions; this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator(); - this._supportedProtocolVersions = options?.supportedProtocolVersions ?? SUPPORTED_PROTOCOL_VERSIONS; this.setRequestHandler('initialize', request => this._oninitialize(request)); this.setNotificationHandler('notifications/initialized', () => this.oninitialized?.()); @@ -476,15 +464,6 @@ export class Server< return this._capabilities; } - /** - * Connects to the given transport and passes supported protocol versions. - */ - override async connect(transport: Transport): Promise { - // Pass supported versions to transport for header validation - transport.setSupportedProtocolVersions?.(this._supportedProtocolVersions); - await super.connect(transport); - } - async ping() { return this.request({ method: 'ping' }, EmptyResultSchema); } From 8aaee7e6378064f45c28db9b0f75c689d86ff7a1 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 3 Feb 2026 11:52:16 +0000 Subject: [PATCH 5/5] style: fix formatting --- packages/server/src/server/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/server/server.ts b/packages/server/src/server/server.ts index 59d948035..99a88de70 100644 --- a/packages/server/src/server/server.ts +++ b/packages/server/src/server/server.ts @@ -436,7 +436,7 @@ export class Server< const protocolVersion = this._supportedProtocolVersions.includes(requestedVersion) ? requestedVersion - : this._supportedProtocolVersions[0] ?? LATEST_PROTOCOL_VERSION; + : (this._supportedProtocolVersions[0] ?? LATEST_PROTOCOL_VERSION); return { protocolVersion,