Skip to content

Commit d683549

Browse files
committed
fix: return protocol errors for invalid tool args
1 parent 5fc42e9 commit d683549

2 files changed

Lines changed: 48 additions & 3 deletions

File tree

packages/server/src/server/mcp.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,11 @@ 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 (
212+
error instanceof ProtocolError &&
213+
(error.code === ProtocolErrorCode.UrlElicitationRequired || error.message.startsWith('Input validation error:'))
214+
) {
215+
throw error;
213216
}
214217
return this.createToolError(error instanceof Error ? error.message : String(error));
215218
}

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { JSONRPCMessage } from '@modelcontextprotocol/core';
2-
import { InMemoryTransport, isStandardSchema, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core';
2+
import { InMemoryTransport, isStandardSchema, LATEST_PROTOCOL_VERSION, ProtocolErrorCode } from '@modelcontextprotocol/core';
33
import { describe, expect, expectTypeOf, it, vi } from 'vitest';
44
import * as z from 'zod/v4';
55
import { McpServer } from '../../src/index.js';
@@ -119,6 +119,48 @@ describe('registerTool/registerPrompt accept raw Zod shape (auto-wrapped)', () =
119119

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

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

0 commit comments

Comments
 (0)