Skip to content

Commit 6b3d326

Browse files
authored
Merge pull request #614 from modelcontextprotocol/ochafik/protocol-version
feat: implement MCP-Protocol-Version header requirement for HTTP transport
2 parents dd69efa + 33257f1 commit 6b3d326

File tree

10 files changed

+235
-21
lines changed

10 files changed

+235
-21
lines changed

src/client/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ export class Client<
165165

166166
this._serverCapabilities = result.capabilities;
167167
this._serverVersion = result.serverInfo;
168+
// HTTP transports must set the protocol version in each header after initialization.
169+
if (transport.setProtocolVersion) {
170+
transport.setProtocolVersion(result.protocolVersion);
171+
}
168172

169173
this._instructions = result.instructions;
170174

src/client/sse.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export class SSEClientTransport implements Transport {
6262
private _eventSourceInit?: EventSourceInit;
6363
private _requestInit?: RequestInit;
6464
private _authProvider?: OAuthClientProvider;
65+
private _protocolVersion?: string;
6566

6667
onclose?: () => void;
6768
onerror?: (error: Error) => void;
@@ -99,13 +100,18 @@ export class SSEClientTransport implements Transport {
99100
}
100101

101102
private async _commonHeaders(): Promise<HeadersInit> {
102-
const headers: HeadersInit = { ...this._requestInit?.headers };
103+
const headers = {
104+
...this._requestInit?.headers,
105+
} as HeadersInit & Record<string, string>;
103106
if (this._authProvider) {
104107
const tokens = await this._authProvider.tokens();
105108
if (tokens) {
106-
(headers as Record<string, string>)["Authorization"] = `Bearer ${tokens.access_token}`;
109+
headers["Authorization"] = `Bearer ${tokens.access_token}`;
107110
}
108111
}
112+
if (this._protocolVersion) {
113+
headers["mcp-protocol-version"] = this._protocolVersion;
114+
}
109115

110116
return headers;
111117
}
@@ -214,7 +220,7 @@ export class SSEClientTransport implements Transport {
214220

215221
try {
216222
const commonHeaders = await this._commonHeaders();
217-
const headers = new Headers({ ...commonHeaders, ...this._requestInit?.headers });
223+
const headers = new Headers(commonHeaders);
218224
headers.set("content-type", "application/json");
219225
const init = {
220226
...this._requestInit,
@@ -249,4 +255,8 @@ export class SSEClientTransport implements Transport {
249255
throw error;
250256
}
251257
}
258+
259+
setProtocolVersion(version: string): void {
260+
this._protocolVersion = version;
261+
}
252262
}

src/client/streamableHttp.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export class StreamableHTTPClientTransport implements Transport {
124124
private _authProvider?: OAuthClientProvider;
125125
private _sessionId?: string;
126126
private _reconnectionOptions: StreamableHTTPReconnectionOptions;
127+
private _protocolVersion?: string;
127128

128129
onclose?: () => void;
129130
onerror?: (error: Error) => void;
@@ -162,7 +163,7 @@ export class StreamableHTTPClientTransport implements Transport {
162163
}
163164

164165
private async _commonHeaders(): Promise<Headers> {
165-
const headers: HeadersInit = {};
166+
const headers: HeadersInit & Record<string, string> = {};
166167
if (this._authProvider) {
167168
const tokens = await this._authProvider.tokens();
168169
if (tokens) {
@@ -173,6 +174,9 @@ export class StreamableHTTPClientTransport implements Transport {
173174
if (this._sessionId) {
174175
headers["mcp-session-id"] = this._sessionId;
175176
}
177+
if (this._protocolVersion) {
178+
headers["mcp-protocol-version"] = this._protocolVersion;
179+
}
176180

177181
return new Headers(
178182
{ ...headers, ...this._requestInit?.headers }
@@ -516,4 +520,11 @@ export class StreamableHTTPClientTransport implements Transport {
516520
throw error;
517521
}
518522
}
523+
524+
setProtocolVersion(version: string): void {
525+
this._protocolVersion = version;
526+
}
527+
get protocolVersion(): string | undefined {
528+
return this._protocolVersion;
529+
}
519530
}

src/integration-tests/stateManagementStreamableHttp.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Client } from '../client/index.js';
55
import { StreamableHTTPClientTransport } from '../client/streamableHttp.js';
66
import { McpServer } from '../server/mcp.js';
77
import { StreamableHTTPServerTransport } from '../server/streamableHttp.js';
8-
import { CallToolResultSchema, ListToolsResultSchema, ListResourcesResultSchema, ListPromptsResultSchema } from '../types.js';
8+
import { CallToolResultSchema, ListToolsResultSchema, ListResourcesResultSchema, ListPromptsResultSchema, DEFAULT_NEGOTIATED_PROTOCOL_VERSION } from '../types.js';
99
import { z } from 'zod';
1010

1111
describe('Streamable HTTP Transport Session Management', () => {
@@ -211,6 +211,27 @@ describe('Streamable HTTP Transport Session Management', () => {
211211
// Clean up
212212
await transport.close();
213213
});
214+
215+
it('should set protocol version after connecting', async () => {
216+
// Create and connect a client
217+
const client = new Client({
218+
name: 'test-client',
219+
version: '1.0.0'
220+
});
221+
222+
const transport = new StreamableHTTPClientTransport(baseUrl);
223+
224+
// Verify protocol version is not set before connecting
225+
expect(transport.protocolVersion).toBeUndefined();
226+
227+
await client.connect(transport);
228+
229+
// Verify protocol version is set after connecting
230+
expect(transport.protocolVersion).toBe(DEFAULT_NEGOTIATED_PROTOCOL_VERSION);
231+
232+
// Clean up
233+
await transport.close();
234+
});
214235
});
215236

216237
describe('Stateful Mode', () => {

src/server/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,12 @@ export class Server<
251251
this._clientCapabilities = request.params.capabilities;
252252
this._clientVersion = request.params.clientInfo;
253253

254-
return {
255-
protocolVersion: SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion)
254+
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion)
256255
? requestedVersion
257-
: LATEST_PROTOCOL_VERSION,
256+
: LATEST_PROTOCOL_VERSION;
257+
258+
return {
259+
protocolVersion,
258260
capabilities: this.getCapabilities(),
259261
serverInfo: this._serverInfo,
260262
...(this._instructions && { instructions: this._instructions }),

src/server/sse.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ const MAXIMUM_MESSAGE_SIZE = "4mb";
1717
export class SSEServerTransport implements Transport {
1818
private _sseResponse?: ServerResponse;
1919
private _sessionId: string;
20-
2120
onclose?: () => void;
2221
onerror?: (error: Error) => void;
2322
onmessage?: (message: JSONRPCMessage, extra?: { authInfo?: AuthInfo }) => void;

0 commit comments

Comments
 (0)