Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions examples/server/src/customProtocolVersion.ts
Original file line number Diff line number Diff line change
@@ -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(', ')}`);
});
7 changes: 3 additions & 4 deletions packages/client/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ import {
mergeCapabilities,
Protocol,
ReadResourceResultSchema,
safeParse,
SUPPORTED_PROTOCOL_VERSIONS
safeParse
} from '@modelcontextprotocol/core';

import { ExperimentalClientTasks } from '../experimental/tasks/client.js';
Expand Down Expand Up @@ -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
}
Expand All @@ -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}`);
}

Expand Down
16 changes: 16 additions & 0 deletions packages/core/src/shared/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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.
*
Expand Down Expand Up @@ -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.
*
Expand All @@ -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);
});
Expand Down Expand Up @@ -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();
}

Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/shared/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
7 changes: 4 additions & 3 deletions packages/server/src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ import {
McpError,
mergeCapabilities,
Protocol,
safeParse,
SUPPORTED_PROTOCOL_VERSIONS
safeParse
} from '@modelcontextprotocol/core';

import { ExperimentalServerTasks } from '../experimental/tasks/server.js';
Expand Down Expand Up @@ -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,
Expand Down
26 changes: 24 additions & 2 deletions packages/server/src/server/streamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
}

/**
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

/**
Expand All @@ -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
*/
Expand Down Expand Up @@ -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;
Expand Down
Loading