From 3a9a6c944becba59da151d96c4a875c930121d34 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 25 Apr 2025 09:16:28 -0400 Subject: [PATCH 1/5] Update protocol version * This fixes #378 * In types.ts - set LATEST_PROTOCOL_VERSION to "2025-03-26" - add "2025-03-26" to SUPPORTED_PROTOCOL_VERSIONS * In auth.test.ts - in test "returns metadata when discovery succeeds" - expect "MCP-Protocol-Version" to be "2025-03-26" --- src/client/auth.test.ts | 4 ++-- src/types.ts | 25 +++++++++++++------------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/client/auth.test.ts b/src/client/auth.test.ts index cc4717ec..629feab7 100644 --- a/src/client/auth.test.ts +++ b/src/client/auth.test.ts @@ -39,7 +39,7 @@ describe("OAuth Authorization", () => { const [url, options] = calls[0]; expect(url.toString()).toBe("https://auth.example.com/.well-known/oauth-authorization-server"); expect(options.headers).toEqual({ - "MCP-Protocol-Version": "2024-11-05" + "MCP-Protocol-Version": "2025-03-26" }); }); @@ -478,4 +478,4 @@ describe("OAuth Authorization", () => { ).rejects.toThrow("Dynamic client registration failed"); }); }); -}); \ No newline at end of file +}); diff --git a/src/types.ts b/src/types.ts index 17b485f8..a2b906e5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,8 +1,9 @@ import { z, ZodTypeAny } from "zod"; -export const LATEST_PROTOCOL_VERSION = "2024-11-05"; +export const LATEST_PROTOCOL_VERSION = "2025-03-26"; export const SUPPORTED_PROTOCOL_VERSIONS = [ LATEST_PROTOCOL_VERSION, + "2024-11-05", "2024-10-07", ]; @@ -754,11 +755,11 @@ export const PromptListChangedNotificationSchema = NotificationSchema.extend({ /* Tools */ /** * Additional properties describing a Tool to clients. - * - * NOTE: all properties in ToolAnnotations are **hints**. - * They are not guaranteed to provide a faithful description of + * + * NOTE: all properties in ToolAnnotations are **hints**. + * They are not guaranteed to provide a faithful description of * tool behavior (including descriptive properties like `title`). - * + * * Clients should never make tool use decisions based on ToolAnnotations * received from untrusted servers. */ @@ -771,7 +772,7 @@ export const ToolAnnotationsSchema = z /** * If true, the tool does not modify its environment. - * + * * Default: false */ readOnlyHint: z.optional(z.boolean()), @@ -779,19 +780,19 @@ export const ToolAnnotationsSchema = z /** * If true, the tool may perform destructive updates to its environment. * If false, the tool performs only additive updates. - * + * * (This property is meaningful only when `readOnlyHint == false`) - * + * * Default: true */ destructiveHint: z.optional(z.boolean()), /** - * If true, calling the tool repeatedly with the same arguments + * If true, calling the tool repeatedly with the same arguments * will have no additional effect on the its environment. - * + * * (This property is meaningful only when `readOnlyHint == false`) - * + * * Default: false */ idempotentHint: z.optional(z.boolean()), @@ -801,7 +802,7 @@ export const ToolAnnotationsSchema = z * entities. If false, the tool's domain of interaction is closed. * For example, the world of a web search tool is open, whereas that * of a memory tool is not. - * + * * Default: true */ openWorldHint: z.optional(z.boolean()), From 1e9d5d548a4f095c7398e506d9fa7f18b34b30a4 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 25 Apr 2025 09:28:16 -0400 Subject: [PATCH 2/5] Update protocol version * Added types.test.ts - "should have correct latest protocol version" - "should have correct supported protocol versions" --- src/types.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/types.test.ts diff --git a/src/types.test.ts b/src/types.test.ts new file mode 100644 index 00000000..bdb5ffb3 --- /dev/null +++ b/src/types.test.ts @@ -0,0 +1,17 @@ +import { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS} from "./types.js"; + +describe("Types", () => { + + test("should have correct latest protocol version", () => { + expect(LATEST_PROTOCOL_VERSION).toBeDefined(); + expect(LATEST_PROTOCOL_VERSION).toBe("2025-03-26"); + }); + test("should have correct supported protocol versions", () => { + expect(SUPPORTED_PROTOCOL_VERSIONS).toBeDefined(); + expect(SUPPORTED_PROTOCOL_VERSIONS).toBeInstanceOf(Array); + expect(SUPPORTED_PROTOCOL_VERSIONS).toContain(LATEST_PROTOCOL_VERSION); + expect(SUPPORTED_PROTOCOL_VERSIONS).toContain("2024-11-05"); + expect(SUPPORTED_PROTOCOL_VERSIONS).toContain("2024-10-07"); + }); + +}); From 84dc914231b91289401e446b16fb456b65c0a3e1 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 25 Apr 2025 09:29:29 -0400 Subject: [PATCH 3/5] Update protocol version * tidy types.test.ts --- src/types.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.test.ts b/src/types.test.ts index bdb5ffb3..0fbc003d 100644 --- a/src/types.test.ts +++ b/src/types.test.ts @@ -1,4 +1,4 @@ -import { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS} from "./types.js"; +import { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS } from "./types.js"; describe("Types", () => { From cada5a4266709351ef4259e8770e9b9df6e6d07f Mon Sep 17 00:00:00 2001 From: cliffhall Date: Mon, 28 Apr 2025 17:58:47 -0400 Subject: [PATCH 4/5] Add client/server version negotiation tests without mocks * In src/client/index.test.ts added tests: - "should connect new client to old, supported server version" - should negotiate version when client is old, and newer server supports its version" - "should throw when client is old, and server doesn't support its version" --- src/client/index.test.ts | 195 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) diff --git a/src/client/index.test.ts b/src/client/index.test.ts index 36dd6518..6e1c442f 100644 --- a/src/client/index.test.ts +++ b/src/client/index.test.ts @@ -165,6 +165,201 @@ test("should reject unsupported protocol version", async () => { expect(clientTransport.close).toHaveBeenCalled(); }); +test("should connect new client to old, supported server version", async () => { + const OLD_VERSION = SUPPORTED_PROTOCOL_VERSIONS[1]; + const server = new Server( + { + name: "test server", + version: "1.0", + }, + { + capabilities: { + resources: {}, + tools: {}, + }, + }, + ); + + server.setRequestHandler(InitializeRequestSchema, (_request) => ({ + protocolVersion: OLD_VERSION, + capabilities: { + resources: {}, + tools: {}, + }, + serverInfo: { + name: "old server", + version: "1.0", + }, + })); + + server.setRequestHandler(ListResourcesRequestSchema, () => ({ + resources: [], + })); + + server.setRequestHandler(ListToolsRequestSchema, () => ({ + tools: [], + })); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + const client = new Client( + { + name: "new client", + version: "1.0", + protocolVersion: LATEST_PROTOCOL_VERSION, + }, + { + capabilities: { + sampling: {}, + }, + enforceStrictCapabilities: true, + }, + ); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + // These should work + // Connection should succeed with the older version + expect(client.getServerVersion()).toEqual({ + name: "old server", + version: "1.0", + }); +}); + +test("should negotiate version when client is old, and newer server supports its version", async () => { + const OLD_VERSION = SUPPORTED_PROTOCOL_VERSIONS[1]; + const server = new Server( + { + name: "new server", + version: "1.0", + }, + { + capabilities: { + resources: {}, + tools: {}, + }, + }, + ); + + server.setRequestHandler(InitializeRequestSchema, (_request) => ({ + protocolVersion: LATEST_PROTOCOL_VERSION, + capabilities: { + resources: {}, + tools: {}, + }, + serverInfo: { + name: "new server", + version: "1.0", + }, + })); + + server.setRequestHandler(ListResourcesRequestSchema, () => ({ + resources: [], + })); + + server.setRequestHandler(ListToolsRequestSchema, () => ({ + tools: [], + })); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + const client = new Client( + { + name: "old client", + version: "1.0", + protocolVersion: OLD_VERSION, + }, + { + capabilities: { + sampling: {}, + }, + enforceStrictCapabilities: true, + }, + ); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + // These should work + // Connection should succeed with the older version + expect(client.getServerVersion()).toEqual({ + name: "new server", + version: "1.0", + }); +}); + +test("should throw when client is old, and server doesn't support its version", async () => { + const OLD_VERSION = SUPPORTED_PROTOCOL_VERSIONS[1]; + const FUTURE_VERSION = "FUTURE_VERSION"; + const server = new Server( + { + name: "new server", + version: "1.0", + }, + { + capabilities: { + resources: {}, + tools: {}, + }, + }, + ); + + server.setRequestHandler(InitializeRequestSchema, (_request) => ({ + protocolVersion: FUTURE_VERSION, + capabilities: { + resources: {}, + tools: {}, + }, + serverInfo: { + name: "new server", + version: "1.0", + }, + })); + + server.setRequestHandler(ListResourcesRequestSchema, () => ({ + resources: [], + })); + + server.setRequestHandler(ListToolsRequestSchema, () => ({ + tools: [], + })); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + const client = new Client( + { + name: "old client", + version: "1.0", + protocolVersion: OLD_VERSION, + }, + { + capabilities: { + sampling: {}, + }, + enforceStrictCapabilities: true, + }, + ); + + let closed = false; + clientTransport.onerror = () => {closed = true}; + + await Promise.all([ + expect(client.connect(clientTransport)).rejects.toThrow( + "Server's protocol version is not supported: FUTURE_VERSION" + ), + server.connect(serverTransport), + ]); + +}); + test("should respect server capabilities", async () => { const server = new Server( { From d11673fc2d39743b13719104042500cbb58278c1 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Mon, 28 Apr 2025 18:01:14 -0400 Subject: [PATCH 5/5] Add client/server version negotiation tests without mocks * In src/client/index.test.ts added tests: - "should connect new client to old, supported server version" - should negotiate version when client is old, and newer server supports its version" - "should throw when client is old, and server doesn't support its version" --- src/client/index.test.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/client/index.test.ts b/src/client/index.test.ts index 6e1c442f..5b4f332f 100644 --- a/src/client/index.test.ts +++ b/src/client/index.test.ts @@ -222,8 +222,6 @@ test("should connect new client to old, supported server version", async () => { server.connect(serverTransport), ]); - // These should work - // Connection should succeed with the older version expect(client.getServerVersion()).toEqual({ name: "old server", version: "1.0", @@ -287,8 +285,6 @@ test("should negotiate version when client is old, and newer server supports its server.connect(serverTransport), ]); - // These should work - // Connection should succeed with the older version expect(client.getServerVersion()).toEqual({ name: "new server", version: "1.0", @@ -348,9 +344,6 @@ test("should throw when client is old, and server doesn't support its version", }, ); - let closed = false; - clientTransport.onerror = () => {closed = true}; - await Promise.all([ expect(client.connect(clientTransport)).rejects.toThrow( "Server's protocol version is not supported: FUTURE_VERSION"