modelcontextprotocol / modelcontextprotocol/typescript-sdk

Docs: passing z.object() as inputSchema on v1 fails silently (empty schema) or cryptically (tools/list crash) — not covered in troubleshooting or migration guide

Open Beginner friendly
#2,627 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

documentation enhancement good first issue P3 v1 v2
Dominant language
TypeScript
Stars
13.4k
Forks
2.2k
Avg merge
3d 15h
Merged PRs (30d)
4

Description

Summary

On SDK v1 (still latest on npm as of this writing — 1.30.0), passing a ZodObject (z.object({...})) where the API expects a Zod raw shape ({ field: z.string() }) produces failure modes that are either silent or cryptic, depending on the version and the API used. None of them point the developer at the actual mistake, and neither the troubleshooting page nor the v1→v2 migration guide mentions these symptoms.

I hit this while building scorm-mcp-server and lost the better part of a day on it: the server started fine, tools/list looked plausible, but every client silently stripped my tool arguments. I've since tested the behavior across v1 versions — full matrix below.

Reproduction matrix

Minimal repro: register one tool with inputSchema: z.object({ name: z.string() }) (instead of the raw shape { name: z.string() }), connect an in-memory client, call tools/list.

SDK version registerTool + z.object() server.tool() (positional) + z.object()
1.12.0 → 1.21.0 tools/list crashes: MCP error -32603: Cannot read properties of null (reading '_def') ⚠️ Silent: publishes an empty schema ({"type":"object"}, no properties) — clients strip all arguments; handler receives no args
1.22.0 → 1.26.0 ✅ normalized, works ⚠️ Silent empty schema (same as above)
1.28.0 → 1.30.0 ✅ normalized, works ✅ throws a clear error at registration
Repro script (run with any of the versions above)
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
import { z } from 'zod';

const server = new McpServer({ name: 't', version: '1.0.0' });

// WRONG on v1 (correct on v2): ZodObject instead of raw shape
server.tool('greet', 'desc', z.object({ name: z.string() }),
  async (args) => ({ content: [{ type: 'text', text: JSON.stringify(args) }] }));

const [ct, st] = InMemoryTransport.createLinkedPair();
const client = new Client({ name: 'c', version: '1.0.0' });
await Promise.all([server.connect(st), client.connect(ct)]);

const { tools } = await client.listTools();
console.log(JSON.stringify(tools[0].inputSchema));
// 1.12.0–1.26.0 → {"type":"object"}  (empty — no properties, no error anywhere)

Why this still matters even though recent versions behave better

  1. Older 1.x versions are massively deployed. Plenty of published servers pin ^1.x ranges resolved months ago; every tutorial written before mid-v1 shows raw shapes, while the v2 docs and README now show z.object() — so developers moving between examples get bitten in both directions.
  2. The failure modes never mention the cause. An empty {"type":"object"} schema looks like a client bug ("my client strips arguments"), and Cannot read properties of null (reading '_def') looks like an SDK internals bug. Neither error string is searchable back to "you passed a ZodObject where a raw shape was expected".
  3. The normalization added for z.object() still doesn't cover other Zod types — see #1643 (z.discriminatedUnion() silently dropped), so the general class of "wrong schema kind fails silently" is still live on latest.

Suggested fix (happy to PR either)

  1. Troubleshooting page: add an entry keyed on the two observable symptoms — "my tool's inputSchema is empty / clients don't send arguments" and "tools/list fails with reading '_def'" — explaining the raw-shape vs ZodObject distinction on v1 and which versions normalize it.
  2. Migration guide: the v1→v2 guide already documents the API shift (raw shape → Standard Schema), but a one-line warning that doing it backwards on v1 ≤1.21 crashes tools/list, and ≤1.26 silently empties the schema via server.tool(), would save people real debugging time.

I'm happy to open a PR with the docs wording if maintainers agree this belongs in the docs.


Environment used for the matrix: Node v22, zod 3.23.8, in-memory transport, each SDK version installed clean from npm.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked troubleshooting page and v1→v2 migration guide, then compare their existing schema guidance with the reported v1 behavior and reproduction matrix. Add troubleshooting entries for empty schemas and the tools/list crash, plus a concise migration warning covering raw shapes versus ZodObject and affected versions; done when both symptoms and the correct v1 usage are discoverable.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
documentation
Issue type
Documentation
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
85/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.