diff --git a/examples/server/src/customProtocolVersion.ts b/examples/server/src/customProtocolVersion.ts new file mode 100644 index 000000000..992337dc0 --- /dev/null +++ b/examples/server/src/customProtocolVersion.ts @@ -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 => ({ + 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(', ')}`); +}); diff --git a/packages/client/src/client/client.ts b/packages/client/src/client/client.ts index 9c2c87290..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'; @@ -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}`); } 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 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 ca43272cf..99a88de70 100644 --- a/packages/server/src/server/server.ts +++ b/packages/server/src/server/server.ts @@ -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, diff --git a/packages/server/src/server/streamableHttp.ts b/packages/server/src/server/streamableHttp.ts index ae8bad97e..f98ab126e 100644 --- a/packages/server/src/server/streamableHttp.ts +++ b/packages/server/src/server/streamableHttp.ts @@ -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;