Skip to content

Commit 2198620

Browse files
committed
Return protocol errors for invalid tool arguments
1 parent 5fc42e9 commit 2198620

2 files changed

Lines changed: 53 additions & 3 deletions

File tree

packages/server/src/server/mcp.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,8 @@ export class McpServer {
208208
await this.validateToolOutput(tool, result, request.params.name);
209209
return result;
210210
} catch (error) {
211-
if (error instanceof ProtocolError && error.code === ProtocolErrorCode.UrlElicitationRequired) {
212-
throw error; // Return the error to the caller without wrapping in CallToolResult
211+
if (error instanceof ProtocolError) {
212+
throw error; // Return protocol-level errors to the caller without wrapping in CallToolResult
213213
}
214214
return this.createToolError(error instanceof Error ? error.message : String(error));
215215
}

packages/server/test/server/mcp.compat.test.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import type { JSONRPCMessage } from '@modelcontextprotocol/core';
22
import { InMemoryTransport, isStandardSchema, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core';
33
import { describe, expect, expectTypeOf, it, vi } from 'vitest';
44
import * as z from 'zod/v4';
5+
56
import { McpServer } from '../../src/index.js';
6-
import type { InferRawShape } from '../../src/server/mcp.js';
77
import { completable } from '../../src/server/completable.js';
8+
import type { InferRawShape } from '../../src/server/mcp.js';
89

910
describe('registerTool/registerPrompt accept raw Zod shape (auto-wrapped)', () => {
1011
it('registerTool accepts a raw shape for inputSchema and auto-wraps it', () => {
@@ -119,6 +120,55 @@ describe('registerTool/registerPrompt accept raw Zod shape (auto-wrapped)', () =
119120

120121
await server.close();
121122
});
123+
124+
it('returns a protocol error for invalid tool arguments', async () => {
125+
const server = new McpServer({ name: 't', version: '1.0.0' });
126+
127+
const handler = vi.fn(async () => ({
128+
content: [{ type: 'text' as const, text: 'should not run' }]
129+
}));
130+
server.registerTool('echo', { inputSchema: { x: z.number() } }, handler);
131+
132+
const [client, srv] = InMemoryTransport.createLinkedPair();
133+
await server.connect(srv);
134+
await client.start();
135+
136+
const responses: JSONRPCMessage[] = [];
137+
client.onmessage = m => responses.push(m);
138+
139+
await client.send({
140+
jsonrpc: '2.0',
141+
id: 1,
142+
method: 'initialize',
143+
params: {
144+
protocolVersion: LATEST_PROTOCOL_VERSION,
145+
capabilities: {},
146+
clientInfo: { name: 'c', version: '1.0.0' }
147+
}
148+
} as JSONRPCMessage);
149+
await client.send({ jsonrpc: '2.0', method: 'notifications/initialized' } as JSONRPCMessage);
150+
await client.send({
151+
jsonrpc: '2.0',
152+
id: 2,
153+
method: 'tools/call',
154+
params: { name: 'echo', arguments: { x: 'not-a-number' } }
155+
} as JSONRPCMessage);
156+
157+
await vi.waitFor(() => expect(responses.some(r => 'id' in r && r.id === 2)).toBe(true));
158+
159+
expect(handler).not.toHaveBeenCalled();
160+
const response = responses.find(r => 'id' in r && r.id === 2) as {
161+
error?: { code: number; message: string };
162+
result?: unknown;
163+
};
164+
expect(response.result).toBeUndefined();
165+
expect(response.error).toMatchObject({
166+
code: -32_602,
167+
message: expect.stringContaining('Invalid arguments for tool echo')
168+
});
169+
170+
await server.close();
171+
});
122172
});
123173

124174
describe('InferRawShape', () => {

0 commit comments

Comments
 (0)