Skip to content

Commit b2326af

Browse files
fix(server): handle undefined arguments for tools with all optional params (#1404)
Co-authored-by: Felix Weinberger <3823880+felixweinberger@users.noreply.github.com> Co-authored-by: Felix Weinberger <fweinberger@anthropic.com>
1 parent dcf708d commit b2326af

File tree

2 files changed

+71
-1
lines changed

2 files changed

+71
-1
lines changed

packages/server/src/server/mcp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ export class McpServer {
271271
// If that fails, use the schema directly (for union/intersection/etc)
272272
const inputObj = normalizeObjectSchema(tool.inputSchema);
273273
const schemaToParse = inputObj ?? (tool.inputSchema as AnySchema);
274-
const parseResult = await safeParseAsync(schemaToParse, args);
274+
const parseResult = await safeParseAsync(schemaToParse, args ?? {});
275275
if (!parseResult.success) {
276276
const error = 'error' in parseResult ? parseResult.error : 'Unknown error';
277277
const errorMessage = getParseErrorMessage(error);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Regression test for https://github.com/modelcontextprotocol/typescript-sdk/issues/400
3+
*
4+
* When a tool has all optional parameters, some LLM models call the tool without
5+
* providing an `arguments` field. This test verifies that undefined arguments are
6+
* handled correctly by defaulting to an empty object.
7+
*/
8+
9+
import { Client } from '@modelcontextprotocol/client';
10+
import { CallToolResultSchema, InMemoryTransport } from '@modelcontextprotocol/core';
11+
import { McpServer } from '@modelcontextprotocol/server';
12+
import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers';
13+
import { zodTestMatrix } from '@modelcontextprotocol/test-helpers';
14+
15+
describe.each(zodTestMatrix)('Issue #400: $zodVersionLabel', (entry: ZodMatrixEntry) => {
16+
const { z } = entry;
17+
18+
test('should accept undefined arguments when all tool params are optional', async () => {
19+
const mcpServer = new McpServer({
20+
name: 'test server',
21+
version: '1.0'
22+
});
23+
const client = new Client({
24+
name: 'test client',
25+
version: '1.0'
26+
});
27+
28+
mcpServer.registerTool(
29+
'optional-params-tool',
30+
{
31+
inputSchema: {
32+
limit: z.number().optional(),
33+
offset: z.number().optional()
34+
}
35+
},
36+
async ({ limit, offset }) => ({
37+
content: [
38+
{
39+
type: 'text',
40+
text: `limit: ${limit ?? 'default'}, offset: ${offset ?? 'default'}`
41+
}
42+
]
43+
})
44+
);
45+
46+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
47+
48+
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
49+
50+
// Call tool without arguments (arguments is undefined)
51+
const result = await client.request(
52+
{
53+
method: 'tools/call',
54+
params: {
55+
name: 'optional-params-tool'
56+
// arguments is intentionally omitted (undefined)
57+
}
58+
},
59+
CallToolResultSchema
60+
);
61+
62+
expect(result.isError).toBeUndefined();
63+
expect(result.content).toEqual([
64+
{
65+
type: 'text',
66+
text: 'limit: default, offset: default'
67+
}
68+
]);
69+
});
70+
});

0 commit comments

Comments
 (0)