From 1030c4086c728aedc6753b683e52ef5052c8e262 Mon Sep 17 00:00:00 2001 From: User Date: Sun, 22 Feb 2026 16:12:08 +0800 Subject: [PATCH 1/3] fix: throw ProtocolError as JSON-RPC error for tool not found Per MCP spec, calling a nonexistent tool should return a JSON-RPC Error (code -32602), not a JSON-RPC Result with isError: true. Previously, only UrlElicitationRequired errors were re-thrown; all other ProtocolErrors (including tool/disabled checks) were swallowed and wrapped in a CallToolResult. Now all ProtocolErrors propagate as JSON-RPC errors, which is the correct behavior per the specification. Fixes #1510 --- packages/server/src/server/mcp.ts | 5 +++-- test/integration/test/server/mcp.test.ts | 28 +++++++++--------------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/packages/server/src/server/mcp.ts b/packages/server/src/server/mcp.ts index 316074e2d..6f8e675e8 100644 --- a/packages/server/src/server/mcp.ts +++ b/packages/server/src/server/mcp.ts @@ -213,8 +213,9 @@ export class McpServer { await this.validateToolOutput(tool, result, request.params.name); return result; } catch (error) { - if (error instanceof ProtocolError && error.code === ProtocolErrorCode.UrlElicitationRequired) { - throw error; // Return the error to the caller without wrapping in CallToolResult + if (error instanceof ProtocolError) { + // Protocol errors should be returned as JSON-RPC errors, not wrapped in CallToolResult + throw error; } return this.createToolError(error instanceof Error ? error.message : String(error)); } diff --git a/test/integration/test/server/mcp.test.ts b/test/integration/test/server/mcp.test.ts index 091e4ac21..ce4072006 100644 --- a/test/integration/test/server/mcp.test.ts +++ b/test/integration/test/server/mcp.test.ts @@ -1837,25 +1837,17 @@ describe('Zod v4', () => { await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]); - const result = await client.request( - { - method: 'tools/call', - params: { - name: 'nonexistent-tool' - } - }, - CallToolResultSchema - ); - - expect(result.isError).toBe(true); - expect(result.content).toEqual( - expect.arrayContaining([ + await expect( + client.request( { - type: 'text', - text: expect.stringContaining('Tool nonexistent-tool not found') - } - ]) - ); + method: 'tools/call', + params: { + name: 'nonexistent-tool' + } + }, + CallToolResultSchema + ) + ).rejects.toThrow(/Tool nonexistent-tool not found/); }); /*** From 6b9d050535093a631b3c1249f3fde11733a39deb Mon Sep 17 00:00:00 2001 From: User Date: Thu, 26 Feb 2026 15:43:11 +0800 Subject: [PATCH 2/3] fix: move tool lookup errors outside try-catch to preserve validation error wrapping Tool not found/disabled errors should be JSON-RPC errors (thrown before try-catch), while validation errors from validateToolInput/Output should remain wrapped as CallToolResult with isError:true. --- packages/server/src/server/mcp.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/server/src/server/mcp.ts b/packages/server/src/server/mcp.ts index 6f8e675e8..71a5d65df 100644 --- a/packages/server/src/server/mcp.ts +++ b/packages/server/src/server/mcp.ts @@ -166,14 +166,16 @@ export class McpServer { ); this.server.setRequestHandler('tools/call', async (request, ctx): Promise => { + // Tool lookup errors are JSON-RPC errors, not tool errors + const tool = this._registeredTools[request.params.name]; + if (!tool) { + throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Tool ${request.params.name} not found`); + } + if (!tool.enabled) { + throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Tool ${request.params.name} disabled`); + } + try { - const tool = this._registeredTools[request.params.name]; - if (!tool) { - throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Tool ${request.params.name} not found`); - } - if (!tool.enabled) { - throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Tool ${request.params.name} disabled`); - } const isTaskRequest = !!request.params.task; const taskSupport = tool.execution?.taskSupport; @@ -213,9 +215,8 @@ export class McpServer { await this.validateToolOutput(tool, result, request.params.name); return result; } catch (error) { - if (error instanceof ProtocolError) { - // Protocol errors should be returned as JSON-RPC errors, not wrapped in CallToolResult - throw error; + if (error instanceof ProtocolError && error.code === ProtocolErrorCode.UrlElicitationRequired) { + throw error; // Return the error to the caller without wrapping in CallToolResult } return this.createToolError(error instanceof Error ? error.message : String(error)); } From 8178fb0dd6f4fe7124d5a7676cc754322d3e65eb Mon Sep 17 00:00:00 2001 From: User Date: Thu, 26 Feb 2026 16:03:04 +0800 Subject: [PATCH 3/3] style: fix prettier formatting in mcp.ts --- packages/server/src/server/mcp.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/server/src/server/mcp.ts b/packages/server/src/server/mcp.ts index 71a5d65df..a4bb8f846 100644 --- a/packages/server/src/server/mcp.ts +++ b/packages/server/src/server/mcp.ts @@ -176,7 +176,6 @@ export class McpServer { } try { - const isTaskRequest = !!request.params.task; const taskSupport = tool.execution?.taskSupport; const isTaskHandler = 'createTask' in (tool.handler as AnyToolHandler);