From 43b851d9f8d368a75c19889dd09388ffee763506 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 27 Sep 2025 12:02:11 -0400 Subject: [PATCH 1/5] * In completable.ts - add isCompletable function, a runtime type guard to detect Completable-wrapped Zod types * In mcp.test.ts - Added tests - "should not advertise support for completion when a resource template without a complete callback is defined" - "should not advertise support for completion when a prompt without a completable argument is defined" * In mcp.ts - in handlePromptCompletion method, - fix warning "Invalid 'instanceof' check: 'field' has type that is not related to 'Completable'" by using the new isCompletable function from completable.ts - in setResourceRequestHandlers method - remove call to setCompletionRequestHandler - supporting resources does not automatically mean that resource template completion is supported - in setPromptRequestHandlers method - remove call to setCompletionRequestHandler - supporting prompts does not automatically mean that prompt argument completion is supported - in _createRegisteredResourceTemplate method - check if the resource template being registered has a complete callback, - if so, call setCompletionRequestHandler - in _createRegisteredPrompt method - check if any argument of the prompt has a Completable schema - if so, call setCompletionRequestHandler --- src/server/completable.ts | 7 ++++ src/server/mcp.test.ts | 84 +++++++++++++++++++++++++++++++++++++++ src/server/mcp.ts | 30 +++++++++++--- 3 files changed, 115 insertions(+), 6 deletions(-) diff --git a/src/server/completable.ts b/src/server/completable.ts index 652eaf72e..08504741c 100644 --- a/src/server/completable.ts +++ b/src/server/completable.ts @@ -61,6 +61,13 @@ export class Completable extends ZodType< }; } +// Runtime type guard to detect Completable-wrapped Zod types across versions +export function isCompletable(value: unknown): value is Completable { + if (value === null || typeof value !== "object") return false; + const obj = value as { _def?: { typeName?: unknown } }; + return obj._def?.typeName === McpZodTypeKind.Completable; +} + /** * Wraps a Zod type to provide autocompletion capabilities. Useful for, e.g., prompt arguments in MCP. */ diff --git a/src/server/mcp.test.ts b/src/server/mcp.test.ts index d9142702f..3ea2af7fc 100644 --- a/src/server/mcp.test.ts +++ b/src/server/mcp.test.ts @@ -2479,6 +2479,47 @@ describe("resource()", () => { ).rejects.toThrow(/Resource test:\/\/nonexistent not found/); }); + +/*** + * Test: Registering a resource template with a complete callback should update server capabilities to advertise support for completion + */ + test("should not advertise support for completion when a resource template without a complete callback is defined", async () => { + const mcpServer = new McpServer({ + name: "test server", + version: "1.0", + }); + const client = new Client({ + name: "test client", + version: "1.0", + }); + + mcpServer.resource( + "test", + new ResourceTemplate("test://resource/{category}", { + list: undefined, + }), + async () => ({ + contents: [ + { + uri: "test://resource/test", + text: "Test content", + }, + ], + }), + ); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + await Promise.all([ + client.connect(clientTransport), + mcpServer.server.connect(serverTransport), + ]); + + expect(client.getServerCapabilities()).not.toMatchObject({ completions: {} }) + }) + + /*** * Test: Registering a resource template with a complete callback should update server capabilities to advertise support for completion */ @@ -3440,6 +3481,49 @@ describe("prompt()", () => { expect(client.getServerCapabilities()).toMatchObject({ completions: {} }) }) + + /*** + * Test: Registering a prompt with a completable argument should update server capabilities to advertise support for completion + */ + test("should not advertise support for completion when a prompt without a completable argument is defined", async () => { + const mcpServer = new McpServer({ + name: "test server", + version: "1.0", + }); + const client = new Client({ + name: "test client", + version: "1.0", + }); + + mcpServer.prompt( + "test-prompt", + { + name: z.string() + }, + async ({ name }) => ({ + messages: [ + { + role: "assistant", + content: { + type: "text", + text: `Hello ${name}`, + }, + }, + ], + }), + ); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + await Promise.all([ + client.connect(clientTransport), + mcpServer.server.connect(serverTransport), + ]); + + expect(client.getServerCapabilities()).not.toMatchObject({ completions: {} }) + }) + /*** * Test: Prompt Argument Completion */ diff --git a/src/server/mcp.ts b/src/server/mcp.ts index ac4880c99..cf3c9928a 100644 --- a/src/server/mcp.ts +++ b/src/server/mcp.ts @@ -43,7 +43,7 @@ import { ToolAnnotations, LoggingMessageNotification, } from "../types.js"; -import { Completable, CompletableDef } from "./completable.js"; +import { CompletableDef, isCompletable } from "./completable.js"; import { UriTemplate, Variables } from "../shared/uriTemplate.js"; import { RequestHandlerExtra } from "../shared/protocol.js"; import { Transport } from "../shared/transport.js"; @@ -290,7 +290,7 @@ export class McpServer { } const field = prompt.argsSchema.shape[request.params.argument.name]; - if (!(field instanceof Completable)) { + if (!isCompletable(field)) { return EMPTY_COMPLETION_RESULT; } @@ -439,8 +439,6 @@ export class McpServer { }, ); - this.setCompletionRequestHandler(); - this._resourceHandlersInitialized = true; } @@ -523,8 +521,6 @@ export class McpServer { }, ); - this.setCompletionRequestHandler(); - this._promptHandlersInitialized = true; } @@ -731,6 +727,16 @@ export class McpServer { }, }; this._registeredResourceTemplates[name] = registeredResourceTemplate; + + // If the resource template has any completion callbacks, enable completions capability + try { + const variableNames = template.uriTemplate.variableNames; + const hasCompleter = Array.isArray(variableNames) && variableNames.some((v) => !!template.completeCallback(v)); + if (hasCompleter) { + this.setCompletionRequestHandler(); + } + } catch { /* empty */ } + return registeredResourceTemplate; } @@ -764,6 +770,18 @@ export class McpServer { }, }; this._registeredPrompts[name] = registeredPrompt; + + // If any argument uses a Completable schema, enable completions capability + if (argsSchema) { + const hasCompletable = Object.values(argsSchema).some((field) => { + const inner: unknown = field instanceof ZodOptional ? field._def?.innerType : field; + return isCompletable(inner); + }); + if (hasCompletable) { + this.setCompletionRequestHandler(); + } + } + return registeredPrompt; } From 93542080b5eb916f14d801f4de8516b59550c83e Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 27 Sep 2025 12:24:17 -0400 Subject: [PATCH 2/5] * In mcp.test.ts - Fix comment indent in new tests --- src/server/mcp.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/server/mcp.test.ts b/src/server/mcp.test.ts index 3ea2af7fc..8f932a7b3 100644 --- a/src/server/mcp.test.ts +++ b/src/server/mcp.test.ts @@ -2480,9 +2480,9 @@ describe("resource()", () => { }); -/*** - * Test: Registering a resource template with a complete callback should update server capabilities to advertise support for completion - */ + /*** + * Test: Registering a resource template with a complete callback should update server capabilities to advertise support for completion + */ test("should not advertise support for completion when a resource template without a complete callback is defined", async () => { const mcpServer = new McpServer({ name: "test server", @@ -3482,9 +3482,9 @@ describe("prompt()", () => { }) - /*** - * Test: Registering a prompt with a completable argument should update server capabilities to advertise support for completion - */ + /*** + * Test: Registering a prompt with a completable argument should update server capabilities to advertise support for completion + */ test("should not advertise support for completion when a prompt without a completable argument is defined", async () => { const mcpServer = new McpServer({ name: "test server", From 9f6b29e9bd717fda530f8fcaeac16486e9dd827f Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 27 Sep 2025 12:25:44 -0400 Subject: [PATCH 3/5] * In mcp.test.ts - Fix comment indent in new tests --- src/server/mcp.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server/mcp.test.ts b/src/server/mcp.test.ts index 8f932a7b3..8587aa806 100644 --- a/src/server/mcp.test.ts +++ b/src/server/mcp.test.ts @@ -2480,9 +2480,9 @@ describe("resource()", () => { }); - /*** - * Test: Registering a resource template with a complete callback should update server capabilities to advertise support for completion - */ + /*** + * Test: Registering a resource template with a complete callback should update server capabilities to advertise support for completion + */ test("should not advertise support for completion when a resource template without a complete callback is defined", async () => { const mcpServer = new McpServer({ name: "test server", From 74af8d91c6b67c42b048b3fe93de69713b991344 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 27 Sep 2025 12:27:04 -0400 Subject: [PATCH 4/5] * In mcp.test.ts - Fix comments new tests --- src/server/mcp.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/mcp.test.ts b/src/server/mcp.test.ts index 8587aa806..009981e19 100644 --- a/src/server/mcp.test.ts +++ b/src/server/mcp.test.ts @@ -2481,7 +2481,7 @@ describe("resource()", () => { /*** - * Test: Registering a resource template with a complete callback should update server capabilities to advertise support for completion + * Test: Registering a resource template without a complete callback should not update server capabilities to advertise support for completion */ test("should not advertise support for completion when a resource template without a complete callback is defined", async () => { const mcpServer = new McpServer({ @@ -3483,7 +3483,7 @@ describe("prompt()", () => { /*** - * Test: Registering a prompt with a completable argument should update server capabilities to advertise support for completion + * Test: Registering a prompt without a completable argument should not update server capabilities to advertise support for completion */ test("should not advertise support for completion when a prompt without a completable argument is defined", async () => { const mcpServer = new McpServer({ From a45b6841b77575cd3b1a8c8c99e3bb7db2460bc8 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 27 Sep 2025 12:30:34 -0400 Subject: [PATCH 5/5] * In mcp.ts - remove unnecessary try/catch --- src/server/mcp.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/server/mcp.ts b/src/server/mcp.ts index cf3c9928a..9f3fa0795 100644 --- a/src/server/mcp.ts +++ b/src/server/mcp.ts @@ -729,13 +729,11 @@ export class McpServer { this._registeredResourceTemplates[name] = registeredResourceTemplate; // If the resource template has any completion callbacks, enable completions capability - try { - const variableNames = template.uriTemplate.variableNames; - const hasCompleter = Array.isArray(variableNames) && variableNames.some((v) => !!template.completeCallback(v)); - if (hasCompleter) { - this.setCompletionRequestHandler(); - } - } catch { /* empty */ } + const variableNames = template.uriTemplate.variableNames; + const hasCompleter = Array.isArray(variableNames) && variableNames.some((v) => !!template.completeCallback(v)); + if (hasCompleter) { + this.setCompletionRequestHandler(); + } return registeredResourceTemplate; }