-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
Summary
When registering a tool using the TypeScript MCP server, passing a ZodObject as the inputSchema causes the SDK to normalize it into an empty object schema, resulting in all arguments being stripped before reaching the tool handler.
Only the shape-object form works:
inputSchema: {
message: z.string(),
}Using the more idiomatic Zod form:
inputSchema: z.object({ message: z.string() })silently breaks argument parsing.
This leads to confusing behavior: arguments are sent correctly by the client, received in the raw request, but the tool handler receives {}.
Steps to Reproduce
1. Register a tool using z.object(...)
server.tool(
'test.echo',
'Echo back the input',
z.object({
message: z.string().describe('Message to echo'),
}),
async (args) => {
console.log('ARGS:', args);
return { content: [{ type: 'text', text: args.message }] };
}
);2. Call it with a valid MCP request
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "test.echo",
"arguments": { "message": "A" }
}
}Actual Behavior
Inside the handler:
ARGS: {}
args.message === undefined
Debugging shows:
extra.request.params.argumentscontains{ message: "A" }as expected- but validation returns
{} - the normalized schema inside the SDK has an empty shape (
{})
The original ZodObject is correct (verified via safeParse), but the SDK abandons it during normalization.
Expected Behavior
Passing a ZodObject should work exactly like passing a shape object:
inputSchema: {
message: z.string(),
}
Handler should receive:
{ message: "A" }
Root Cause Analysis
The internal zod-compat normalization appears to only support the "shape object" format.
When a full ZodObject is passed:
- the normalization logic does not detect or extract its shape
- it constructs a new empty
ZodObject({}) - unknown keys are stripped
- all user arguments are silently removed
This results in {} being passed to tool handlers, with no warning or error.
This was tested with the recommended zod@3.24.1.
Proposed Fixes
Any of the following would resolve the issue:
Option A — Support ZodObject directly
Extract and use schema._def.shape() instead of discarding the schema.
Option B — Throw a clear error
For example:
inputSchema must be a Zod shape object (e.g. { message: z.string() }), not a ZodObject.
Option C — Auto-convert ZodObject to shape-based schema
Handle ZodObject input by converting it to the shape form expected by zod-compat.
Additional Notes
- The shape-object format works perfectly.
- Many developers naturally write
z.object(...), so rejecting it silently is a sharp edge. - The current behavior causes argument loss without any warning, making debugging extremely difficult.
PS: If helpful, I can provide a minimal reproduction repo or submit a PR addressing the root cause.