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>
This commit is contained in:
suram 2026-01-24 03:36:40 +09:00 • committed by GitHub
commit b2326afea6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 71 additions and 1 deletions

View file

@ -271,7 +271,7 @@ export class McpServer {
// If that fails, use the schema directly (for union/intersection/etc)
const inputObj = normalizeObjectSchema(tool.inputSchema);
const schemaToParse = inputObj ?? (tool.inputSchema as AnySchema);
const parseResult = await safeParseAsync(schemaToParse, args);
const parseResult = await safeParseAsync(schemaToParse, args ?? {});
if (!parseResult.success) {
const error = 'error' in parseResult ? parseResult.error : 'Unknown error';
const errorMessage = getParseErrorMessage(error);

View file

@ -0,0 +1,70 @@
/**
* Regression test for https://github.com/modelcontextprotocol/typescript-sdk/issues/400
*
* When a tool has all optional parameters, some LLM models call the tool without
* providing an `arguments` field. This test verifies that undefined arguments are
* handled correctly by defaulting to an empty object.
*/
import { Client } from '@modelcontextprotocol/client';
import { CallToolResultSchema, InMemoryTransport } from '@modelcontextprotocol/core';
import { McpServer } from '@modelcontextprotocol/server';
import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers';
import { zodTestMatrix } from '@modelcontextprotocol/test-helpers';
describe.each(zodTestMatrix)('Issue #400: $zodVersionLabel', (entry: ZodMatrixEntry) => {
const { z } = entry;
test('should accept undefined arguments when all tool params are optional', async () => {
const mcpServer = new McpServer({
name: 'test server',
version: '1.0'
});
const client = new Client({
name: 'test client',
version: '1.0'
});
mcpServer.registerTool(
'optional-params-tool',
{
inputSchema: {
limit: z.number().optional(),
offset: z.number().optional()
}
},
async ({ limit, offset }) => ({
content: [
{
type: 'text',
text: `limit: ${limit ?? 'default'}, offset: ${offset ?? 'default'}`
}
]
})
);
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
// Call tool without arguments (arguments is undefined)
const result = await client.request(
{
method: 'tools/call',
params: {
name: 'optional-params-tool'
// arguments is intentionally omitted (undefined)
}
},
CallToolResultSchema
);
expect(result.isError).toBeUndefined();
expect(result.content).toEqual([
{
type: 'text',
text: 'limit: default, offset: default'
}
]);
});
});