Skip to content

Commit eaf4444

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

4 files changed

Lines changed: 116 additions & 173 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@modelcontextprotocol/server": patch
3+
---
4+
5+
Return protocol errors for invalid tool arguments instead of tool execution errors.

packages/server/src/server/mcp.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,13 @@ 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 ||
214+
(error.code === ProtocolErrorCode.InvalidParams &&
215+
error.message.startsWith('Input validation error: Invalid arguments for tool ')))
216+
) {
217+
throw error;
213218
}
214219
return this.createToolError(error instanceof Error ? error.message : String(error));
215220
}

test/integration/test/server/mcp.test.ts

Lines changed: 56 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,25 @@ function createLatch() {
2828
};
2929
}
3030

31+
async function expectInvalidParams(promise: Promise<unknown>, expectedMessages: Array<string | RegExp> = []) {
32+
let caught: unknown;
33+
try {
34+
await promise;
35+
} catch (error) {
36+
caught = error;
37+
}
38+
39+
expect(caught).toMatchObject({ code: ProtocolErrorCode.InvalidParams });
40+
const message = caught instanceof Error ? caught.message : String(caught);
41+
for (const expected of expectedMessages) {
42+
if (expected instanceof RegExp) {
43+
expect(message).toMatch(expected);
44+
} else {
45+
expect(message).toContain(expected);
46+
}
47+
}
48+
}
49+
3150
describe('Zod v4', () => {
3251
describe('McpServer', () => {
3352
/***
@@ -1212,25 +1231,18 @@ describe('Zod v4', () => {
12121231

12131232
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
12141233

1215-
const result = await client.request({
1216-
method: 'tools/call',
1217-
params: {
1218-
name: 'test',
1219-
arguments: {
1234+
await expectInvalidParams(
1235+
client.request({
1236+
method: 'tools/call',
1237+
params: {
12201238
name: 'test',
1221-
value: 'not a number'
1222-
}
1223-
}
1224-
});
1225-
1226-
expect(result.isError).toBe(true);
1227-
expect(result.content).toEqual(
1228-
expect.arrayContaining([
1229-
{
1230-
type: 'text',
1231-
text: expect.stringContaining('Input validation error: Invalid arguments for tool test')
1239+
arguments: {
1240+
name: 'test',
1241+
value: 'not a number'
1242+
}
12321243
}
1233-
])
1244+
}),
1245+
['Input validation error: Invalid arguments for tool test']
12341246
);
12351247
});
12361248

@@ -5149,22 +5161,15 @@ describe('Zod v4', () => {
51495161
await server.connect(serverTransport);
51505162
await client.connect(clientTransport);
51515163

5152-
const invalidTypeResult = await client.callTool({
5153-
name: 'union-test',
5154-
arguments: {
5155-
type: 'a',
5156-
value: 123
5157-
}
5158-
});
5159-
5160-
expect(invalidTypeResult.isError).toBe(true);
5161-
expect(invalidTypeResult.content).toEqual(
5162-
expect.arrayContaining([
5163-
expect.objectContaining({
5164-
type: 'text',
5165-
text: expect.stringContaining('Input validation error')
5166-
})
5167-
])
5164+
await expectInvalidParams(
5165+
client.callTool({
5166+
name: 'union-test',
5167+
arguments: {
5168+
type: 'a',
5169+
value: 123
5170+
}
5171+
}),
5172+
['Input validation error']
51685173
);
51695174
});
51705175
});
@@ -6407,40 +6412,26 @@ describe('Zod v4', () => {
64076412
await server.connect(serverTransport);
64086413
await client.connect(clientTransport);
64096414

6410-
const invalidTypeResult = await client.callTool({
6411-
name: 'union-test',
6412-
arguments: {
6413-
type: 'a',
6414-
value: 123
6415-
}
6416-
});
6417-
6418-
expect(invalidTypeResult.isError).toBe(true);
6419-
expect(invalidTypeResult.content).toEqual(
6420-
expect.arrayContaining([
6421-
expect.objectContaining({
6422-
type: 'text',
6423-
text: expect.stringContaining('Input validation error')
6424-
})
6425-
])
6415+
await expectInvalidParams(
6416+
client.callTool({
6417+
name: 'union-test',
6418+
arguments: {
6419+
type: 'a',
6420+
value: 123
6421+
}
6422+
}),
6423+
['Input validation error']
64266424
);
64276425

6428-
const invalidDiscriminatorResult = await client.callTool({
6429-
name: 'union-test',
6430-
arguments: {
6431-
type: 'c',
6432-
value: 'test'
6433-
}
6434-
});
6435-
6436-
expect(invalidDiscriminatorResult.isError).toBe(true);
6437-
expect(invalidDiscriminatorResult.content).toEqual(
6438-
expect.arrayContaining([
6439-
expect.objectContaining({
6440-
type: 'text',
6441-
text: expect.stringContaining('Input validation error')
6442-
})
6443-
])
6426+
await expectInvalidParams(
6427+
client.callTool({
6428+
name: 'union-test',
6429+
arguments: {
6430+
type: 'c',
6431+
value: 'test'
6432+
}
6433+
}),
6434+
['Input validation error']
64446435
);
64456436
});
64466437
});

0 commit comments

Comments
 (0)