diff --git a/docs/migration/upgrade-to-v2.md b/docs/migration/upgrade-to-v2.md index 3353fa5cb7..56dbf76bb3 100644 --- a/docs/migration/upgrade-to-v2.md +++ b/docs/migration/upgrade-to-v2.md @@ -659,6 +659,13 @@ are still **accepted via `@deprecated` overloads** on `registerTool`/`registerPr (auto-wrapped with `z.object()`), and `completable()` accepts any `StandardSchemaV1`; prefer wrapping explicitly. Zod v4, ArkType, and Valibot all implement the spec. +> **Do not apply the v2 style backwards on v1:** on SDK v1, `server.tool()` +> (and `registerTool` before 1.22) expects the **raw shape**, not `z.object(...)`. +> A `ZodObject` there crashes `tools/list` on v1 ≤ 1.21 and silently publishes +> an empty schema (arguments stripped) via `server.tool()` on v1 ≤ 1.26. +> If you backport v2 examples to a v1 server, unwrap to the raw shape — see +> [Troubleshooting](../troubleshooting.md#mcp-error--32603-cannot-read-properties-of-null-reading-_def). + For **optional completable arguments**, apply `.optional()` to the _result_ of `completable()` — `completable(z.string(), cb).optional()`, not `completable(z.string().optional(), cb)`. v2 resolves completion metadata on the schema diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index d9e6e7824d..1db5b9f38f 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -167,6 +167,23 @@ The Resource Server helpers did not move there: `requireBearerAuth`, `mcpAuthMet HTTP SSE streams emit a `: keepalive` comment every 15 seconds by default so client body-idle timeouts and intermediaries do not terminate an otherwise idle connection. Configure the interval with `keepAliveMs` on the transport or `createMcpHandler`; set it to `0` to disable heartbeats. +## `MCP error -32603: Cannot read properties of null (reading '_def')` + +On SDK v1, `server.tool()` (and `registerTool` before 1.22) expects a **Zod raw shape** — `{ name: z.string() }` — not a `ZodObject`. Passing `z.object({ name: z.string() })` fails without ever naming the mistake: + +- v1 ≤ 1.21: `tools/list` crashes with this error. +- v1 ≤ 1.26 via `server.tool()`: no error anywhere, but the published schema is an empty `{"type":"object"}` — clients strip every argument and the handler receives none. +- v1 ≥ 1.28: registration throws a clear error, and `registerTool` has normalized `z.object()` since 1.22. + +Fix the call site — unwrap to the raw shape: + +```diff +- server.tool('greet', 'desc', z.object({ name: z.string() }), handler); ++ server.tool('greet', 'desc', { name: z.string() }, handler); +``` + +On v2 the direction reverses: `registerTool` takes schema objects (`z.object(...)`), with raw shapes only on deprecated overloads — see the [v1→v2 migration guide](./migration/upgrade-to-v2.md#standard-schema-objects-raw-shapes-deprecated). + ## Recap - Every heading on this page is the exact message you searched for.