Skip to content

fix(server): auto-unwrap z.object() schemas in server.tool()#1603

Open
giulio-leone wants to merge 3 commits intomodelcontextprotocol:v1.xfrom
giulio-leone:fix/zod-object-schema-unwrap
Open

fix(server): auto-unwrap z.object() schemas in server.tool()#1603
giulio-leone wants to merge 3 commits intomodelcontextprotocol:v1.xfrom
giulio-leone:fix/zod-object-schema-unwrap

Conversation

@giulio-leone
Copy link

Problem

When z.object({...}) is passed as inputSchema to server.tool(), the SDK silently interprets it as ToolAnnotations instead of an input schema. The tool registers with empty parameters and arguments are stripped without any error.

This is because isZodRawShapeCompat() correctly identifies ZodObject instances as not raw shapes (they have internal _def/_zod properties, not field schemas as values). But the fallback branch at line 1026 unconditionally treats any remaining object as ToolAnnotations.

Root Cause

if (isZodRawShapeCompat(firstArg)) {
    inputSchema = rest.shift();       // z.object({...}) fails this check
} else if (typeof firstArg === 'object') {
    annotations = rest.shift();       // ← z.object({...}) silently lands here
}

Fix

Added extractZodObjectShape() that detects ZodObject schemas (both Zod v3 and v4) by checking for a .shape property whose values are Zod type instances. When detected, the raw shape is extracted and used as inputSchema.

Both forms now work identically:

// Raw shape (always worked)
server.tool('test', { message: z.string() }, handler);

// ZodObject (now also works)
server.tool('test', z.object({ message: z.string() }), handler);

Tests

  • Added test for z.object() auto-unwrap with schema verification and argument passing
  • Added test for z.object() combined with annotations
  • All 1551 tests pass (2 consecutive clean runs)

Fixes #1291

@giulio-leone giulio-leone requested a review from a team as a code owner February 28, 2026 02:19
@changeset-bot
Copy link

changeset-bot bot commented Feb 28, 2026

🦋 Changeset detected

Latest commit: d280069

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 28, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@modelcontextprotocol/sdk@1603

commit: 1f12bd3

When z.object({...}) is passed as inputSchema, the SDK would silently
interpret it as ToolAnnotations instead of an input schema, resulting in
the tool being registered with empty parameters. Arguments passed to the
tool would be silently stripped.

Added extractZodObjectShape() that detects ZodObject schemas (both Zod
v3 and v4) via their .shape property and extracts the raw shape for
proper registration.

Fixes modelcontextprotocol#1291
@giulio-leone giulio-leone force-pushed the fix/zod-object-schema-unwrap branch from 5f16615 to 4f3ac37 Compare February 28, 2026 14:50
The z.object() tests intentionally pass ZodObject schemas (instead of raw
shapes) to verify runtime auto-unwrapping. Since TypeScript's type system
doesn't support ZodObject as a valid argument, use 'as any' casts and
explicit callback parameter types to satisfy the type checker while
preserving the test's intent.
@giulio-leone
Copy link
Author

Friendly ping — CI is green and this is ready for review. Happy to address any feedback. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant