Skip to content

feat(compat): registerTool/registerPrompt accept raw Zod shape, auto-wrap with z.object()#1901

Open
felixweinberger wants to merge 13 commits intomainfrom
fweinberger/v2-bc-register-rawshape
Open

feat(compat): registerTool/registerPrompt accept raw Zod shape, auto-wrap with z.object()#1901
felixweinberger wants to merge 13 commits intomainfrom
fweinberger/v2-bc-register-rawshape

Conversation

@felixweinberger
Copy link
Copy Markdown
Contributor

@felixweinberger felixweinberger commented Apr 15, 2026

Part of the v2 backwards-compatibility series — see reviewer guide.

v2 requires StandardSchema objects (e.g. z.object({...})) for inputSchema. v1 accepted raw shapes {x: z.string()}. This auto-wraps raw shapes

Motivation and Context

v2 requires StandardSchema objects (e.g. z.object({...})) for inputSchema. v1 accepted raw shapes {x: z.string()}. This auto-wraps raw shapes

v1 vs v2 pattern & evidence

v1 pattern:

`registerTool('x', {inputSchema: {a: z.string()}}, cb)`

v2-native:

`registerTool('x', {inputSchema: z.object({a: z.string()})}, cb)`

Evidence: ~70% of typical server migration LOC was wrapping shapes. Took multiple OSS repos to zero.

How Has This Been Tested?

  • packages/server/test/server/mcp.compat.test.ts — 3 cases
  • Integration: validated bump-only against 5 OSS repos via the v2-bc-integration validation branch
  • pnpm typecheck:all && pnpm lint:all && pnpm test:all green

Breaking Changes

None — additive @deprecated shim.

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added or updated documentation as needed

Additional context

Stacks on: C1

@felixweinberger felixweinberger added the v2-bc v2 backwards-compatibility series label Apr 15, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 15, 2026

🦋 Changeset detected

Latest commit: b5854c1

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

This PR includes changesets to release 6 packages
Name Type
@modelcontextprotocol/core Patch
@modelcontextprotocol/server Patch
@modelcontextprotocol/node Patch
@modelcontextprotocol/express Patch
@modelcontextprotocol/fastify Patch
@modelcontextprotocol/hono Patch

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

@felixweinberger felixweinberger added this to the v2.0.0-bc milestone Apr 15, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 15, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@1901

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@1901

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@1901

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@1901

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@1901

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@1901

commit: b5854c1

@felixweinberger felixweinberger force-pushed the fweinberger/v2-bc-register-rawshape branch from 182ec53 to 9b606ab Compare April 16, 2026 09:36
@felixweinberger felixweinberger force-pushed the fweinberger/v2-bc-register-rawshape branch 2 times, most recently from 2972af1 to a1bf55a Compare April 16, 2026 16:11
@felixweinberger felixweinberger marked this pull request as ready for review April 16, 2026 16:59
@felixweinberger felixweinberger requested a review from a team as a code owner April 16, 2026 16:59
Comment thread packages/core/src/util/standardSchema.ts Outdated
Comment thread packages/server/src/server/mcp.ts
@felixweinberger felixweinberger marked this pull request as draft April 16, 2026 18:55
@felixweinberger felixweinberger force-pushed the fweinberger/v2-bc-register-rawshape branch from a1bf55a to 27e4ddf Compare April 16, 2026 19:57
@felixweinberger
Copy link
Copy Markdown
Contributor Author

@claude review

Comment thread .changeset/register-rawshape-compat.md Outdated
Comment thread packages/server/src/server/mcp.ts
@felixweinberger
Copy link
Copy Markdown
Contributor Author

@claude review

Comment thread packages/core/src/util/standardSchema.ts Outdated
@felixweinberger felixweinberger marked this pull request as ready for review April 17, 2026 10:11
Comment thread packages/core/src/util/standardSchema.ts Outdated
Comment thread packages/server/test/server/mcp.compat.test.ts Outdated
@felixweinberger felixweinberger marked this pull request as draft April 17, 2026 10:45
@felixweinberger
Copy link
Copy Markdown
Contributor Author

@claude review

Comment thread packages/server/src/server/mcp.ts
Comment thread packages/server/src/server/mcp.ts Outdated
…spy; cover normalizeRawShapeSchema passthrough/undefined
@felixweinberger felixweinberger force-pushed the fweinberger/v2-bc-register-rawshape branch from 688b282 to 1af9ed2 Compare April 17, 2026 12:42
@felixweinberger
Copy link
Copy Markdown
Contributor Author

@claude review

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1af9ed2 (test-only) looks good and the two open nits on the overload asymmetry and InferRawShape optionality are non-blocking from my side — deferring to a human for sign-off on the new public API surface (the @deprecated overloads and the ZodRawShape/InferRawShape/Legacy*Callback exports).

Extended reasoning...

Overview

This PR adds v1-compat raw-Zod-shape support to registerTool/registerPrompt: a new normalizeRawShapeSchema helper in @modelcontextprotocol/core that detects { field: z.string() } records and wraps them with z.object(), plus @deprecated overloads on McpServer.registerTool/registerPrompt and four new exported types (ZodRawShape, InferRawShape, LegacyToolCallback, LegacyPromptCallback). Across five review rounds the substantive issues — empty-shape crash, non-Zod-field over-acceptance, changeset/JSDoc inconsistency, missing outputSchema coverage — were all addressed. The latest commit (1af9ed2) is test-only: drops the now-vestigial console.warn spies, adds passthrough/undefined unit tests for normalizeRawShapeSchema, and adds an end-to-end tools/call test. Two 🟡 nits remain open (wrapped-input + raw-output overload gap; InferRawShape not applying ? to optional keys); both are explicitly low-impact and the author appears to have intentionally left them.

Security risks

None identified. The change is purely a schema-normalization shim on the registration path; no auth, transport, or untrusted-input handling is touched. The auto-wrap only fires on values that pass a Zod-specific structural check (_def / vendor === 'zod'), so there's no new injection or prototype-pollution surface.

Level of scrutiny

Moderate-to-high. The runtime change is small and well-tested, but this grows the public API of McpServer — the primary user-facing class — with new overloads and exported types, and REVIEW.md's guiding principles put the burden of proof on additions and ask that every new export be intentional. The PR is part of a coordinated 22-PR v2-bc series with its own reviewer guide and OSS-repo validation, which is exactly the kind of context a human maintainer tracking the series should sign off on rather than a per-PR bot.

Other factors

  • The new Legacy* types are exported from mcp.ts but (per the author's earlier reply) intentionally not re-exported from packages/server/src/index.ts; a human should confirm that's the desired surface.
  • Migration docs (docs/migration.md / migration-SKILL.md) still say raw shapes are unsupported; the author has batched those updates into #1910 — reasonable, but another reason a human should be in the loop on the series.
  • Test coverage is now solid: unit tests for the detector/normalizer (incl. empty shape, non-Zod rejection, passthrough, undefined) and integration tests for inputSchema/outputSchema/argsSchema plus an e2e tools/call.

KKonstantinov and others added 2 commits April 24, 2026 17:42
v1 accepted any zod schema (AnySchema). v2 narrowed to StandardSchemaWithJSON,
which requires ~standard.jsonSchema. completable() itself only needs validate
(to type the callback's value param); JSON-Schema generation happens at the
outer registerPrompt argsSchema level, not per-field. Widening to
StandardSchemaV1 restores the v1 surface (any Standard Schema lib, zod >=3.24).

Adds raw-shape registerPrompt test with a completable() field.
@felixweinberger felixweinberger marked this pull request as ready for review April 27, 2026 10:21
Comment thread packages/server/src/server/completable.ts
Copy link
Copy Markdown
Contributor

@KKonstantinov KKonstantinov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, small nits only.

  • Both migration docs (the skill and general doc) state: "Raw object shapes are no longer accepted." This PR makes them accepted again. The changeset text is accurate, but the migration docs are not updated. Per REVIEW.md's "Documentation & Changesets" recurring catch, this is a flag.

Comment thread packages/core/src/util/standardSchema.ts Outdated
Comment thread packages/core/src/util/standardSchema.ts Outdated
felixweinberger and others added 2 commits April 27, 2026 12:19
…normalizeRawShapeSchema input; preserve optional in InferRawShape

- Move isZodSchema/isZodRawShape/normalizeRawShapeSchema from standardSchema.ts to a new zodCompat.ts so standardSchema.ts is Standard-Schema-spec only.
- normalizeRawShapeSchema now throws TypeError for inputs that are neither a raw shape nor a Standard Schema, instead of silently returning them.
- InferRawShape now uses z.infer<z.ZodObject<S>> so .optional() fields produce ?: keys.
- Changeset mentions the completable() constraint widening.
Comment thread packages/core/test/util/standardSchema.test.ts Outdated
Comment thread packages/core/src/util/zodCompat.ts Outdated
…malizeRawShapeSchema guard to require ~standard.jsonSchema
Comment thread packages/core/src/util/zodCompat.ts Outdated
…ble error

isZodSchema previously matched on `_def` / `~standard.vendor === 'zod'`,
both of which Zod v3 schemas also satisfy. A v3 raw shape would pass
isZodRawShape, get wrapped by v4's z.object(), and crash deep inside
zod when listing or calling the tool.

Now detect v4 via the `_zod` property (absent on v3), and add a
dedicated runtime guard in normalizeRawShapeSchema that throws a clear
TypeError when v3 fields are seen, telling the user to import from
zod/v4 or wrap the shape themselves.
@felixweinberger felixweinberger force-pushed the fweinberger/v2-bc-register-rawshape branch from 068ff56 to a6b25ee Compare April 27, 2026 13:42
Comment thread packages/core/src/util/zodCompat.ts Outdated
Comment thread packages/core/src/util/zodCompat.ts
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b5854c1 addresses both remaining nits (plain-object prototype check + null guard), and I have nothing further — leaving final sign-off to a human since this adds public overloads/types and widens the completable() constraint.

Extended reasoning...

Overview

This PR adds a v1-compat shim so registerTool/registerPrompt accept raw Zod shapes ({ field: z.string() }) and auto-wrap them with z.object(). It introduces packages/core/src/util/zodCompat.ts (isZodRawShape, normalizeRawShapeSchema), adds @deprecated overloads + new exported types (ZodRawShape, InferRawShape, LegacyToolCallback, LegacyPromptCallback) in mcp.ts, and widens the completable() generic constraint from StandardSchemaWithJSON to StandardSchemaV1. Tests live in zodCompat.test.ts and mcp.compat.test.ts.

Status of prior feedback

Every issue I raised across the review rounds has been addressed, most recently in b5854c1: isZodRawShape now requires a plain-object prototype (rejects [], Date, Map, etc., with tests), and the v3-detection branch in normalizeRawShapeSchema now null-guards before Object.values() (with a test asserting the helpful TypeError fires). The earlier rounds covered the empty-shape case, Zod-only narrowing, v3 detection, isStandardSchemaWithJSON guard, changeset wording, test-file split, outputSchema coverage, and InferRawShape optionality — all resolved. KKonstantinov's two asks (separate zodCompat module; runtime guard for invalid pass-through) are also applied.

Security risks

None identified. This is schema-handling glue for developer-supplied tool/prompt definitions; no auth, transport, or untrusted-input parsing changes.

Level of scrutiny

Moderate-to-high: it expands the public API surface of @modelcontextprotocol/server (new overloads and exported types), relaxes a public generic constraint on completable(), and is one of ~22 coordinated v2-bc PRs whose docs land separately in #1910. Per REVIEW.md ("Burden of proof is on addition", "Every new export is intentional"), API-surface additions warrant maintainer sign-off rather than bot approval.

Other factors

KKonstantinov already engaged on this PR and his asks were addressed; final approval should come from him or another maintainer. Migration-doc updates are intentionally deferred to #1910. The one remaining optional suggestion (re-exporting StandardSchemaV1 from core/public) was explicitly flagged non-blocking and is fine to skip.

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

Labels

v2-bc v2 backwards-compatibility series

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants