Skip to content

feat(compat): add /server/zod-schemas subpath re-exporting *Schema constants#1906

Draft
felixweinberger wants to merge 1 commit intomainfrom
fweinberger/v2-bc-zod-schemas-subpath
Draft

feat(compat): add /server/zod-schemas subpath re-exporting *Schema constants#1906
felixweinberger wants to merge 1 commit intomainfrom
fweinberger/v2-bc-zod-schemas-subpath

Conversation

@felixweinberger
Copy link
Copy Markdown
Contributor

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

v2 stopped exporting Zod schema constants (only TS types). Consumers using them for runtime validation at HTTP boundaries (token endpoints, OIDC discovery, custom transports) lose validators. This re-exports them under a @deprecated subpath.

Motivation and Context

v2 stopped exporting Zod schema constants (only TS types). Consumers using them for runtime validation at HTTP boundaries (token endpoints, OIDC discovery, custom transports) lose validators. This re-exports them under a @deprecated subpath.

v1 vs v2 pattern & evidence

v1 pattern:

`import { OAuthTokensSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'`

v2-native:

Use `specTypeSchema('CallToolRequest')` (see #1887) or import types only

Evidence: Hits any consumer doing runtime validation of HTTP responses or implementing a custom transport.

How Has This Been Tested?

  • Import-compiles test
  • 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. Removed in v3.

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: none

@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: e77d678

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

This PR includes changesets to release 5 packages
Name Type
@modelcontextprotocol/server Patch
@modelcontextprotocol/express Patch
@modelcontextprotocol/fastify Patch
@modelcontextprotocol/hono Patch
@modelcontextprotocol/node 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@1906

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: e77d678

@felixweinberger felixweinberger force-pushed the fweinberger/v2-bc-zod-schemas-subpath branch 2 times, most recently from 8c9a388 to ab8b2ed Compare April 16, 2026 09:38
@felixweinberger felixweinberger force-pushed the fweinberger/v2-bc-zod-schemas-subpath branch from ab8b2ed to 54f3063 Compare April 16, 2026 17:48
@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.

Beyond the inline nits: docs/migration-SKILL.md:101 still states Zod schemas "are no longer part of the public API" and points users at type guards instead — that prose now contradicts this subpath and should mention /zod-schemas as the v1-compat option. Also worth confirming whether @modelcontextprotocol/client should get the same subpath, since v1's types.js served both sides.

Extended reasoning...

Overview

Adds a new public subpath @modelcontextprotocol/server/zod-schemas that re-exports ~150 Zod schema constants (plus OAuth/auth schemas from core) as a v1→v2 compat shim. Touches core/package.json (new ./schemas export on the private core package), server/package.json exports map, a new zodSchemas.ts entry, tsdown/tsconfig/typedoc plumbing, a compat test, and a changeset.

Security risks

None — pure re-exports of existing internal constants; no new logic, auth, or I/O paths.

Level of scrutiny

Moderate-to-high. Per REVIEW.md, "every new export is intentional" and "burden of proof is on addition" — this is a deliberate public-API-surface expansion (an entire subpath), even if framed as deprecated. A maintainer should sign off on the surface shape, especially given the export * pulls in three non-v1 helper functions (getResultSchema/getRequestSchema/getNotificationSchema) and the changeset only promises *Schema constants.

Other factors

  • docs/migration-SKILL.md:101 and docs/migration.md (around L447–503) currently tell users Zod schemas are gone and to use type guards / specTypeSchema; that guidance now needs a pointer to this compat subpath (REVIEW.md "flag prose that now contradicts the implementation").
  • v1's @modelcontextprotocol/sdk/types.js was consumed by client-side code too (e.g., validating CallToolResult), but only the server package gets the subpath here — may be intentional, but worth an explicit call.
  • Core package is private: true and bundled via noExternal, so the new ./schemas core export doesn't leak externally — that part is fine.
  • Test coverage is minimal (instanceof + one parse) but adequate for a re-export shim.

Comment thread packages/server/src/zodSchemas.ts
Comment thread packages/server/src/zodSchemas.ts Outdated
@felixweinberger felixweinberger force-pushed the fweinberger/v2-bc-zod-schemas-subpath branch 2 times, most recently from a860d7e to f7e391b Compare April 16, 2026 20:35
@felixweinberger
Copy link
Copy Markdown
Contributor Author

Re the review-body points: migration-SKILL.md:101 updated to mention the subpath. A separate @modelcontextprotocol/client/zod-schemas mirror isn't needed: the schemas are protocol-level (not server-specific), and the @modelcontextprotocol/sdk meta-package re-exports this subpath at sdk/types.js for both client and server consumers.

@claude review

@claude
Copy link
Copy Markdown

claude bot commented Apr 16, 2026

Claude finished @felixweinberger's task in 2m 37s —— View job


Review

  • Gather context from PR
  • Read changed files (current branch state)
  • Review implementation
  • Post findings

The previous nits were all addressed appropriately. One documentation gap remains; a couple of minor points below.

🔴 docs/migration.md:503 still misleads on Zod schema imports

migration-SKILL.md:101 was updated to mention the /zod-schemas subpath, but docs/migration.md:503 still says:

All other types and schemas exported from @modelcontextprotocol/sdk/types.js retain their original names — import them from @modelcontextprotocol/client or @modelcontextprotocol/server.

That sentence is now incorrect for Zod schema constants — they're not reachable from the package roots, only from @modelcontextprotocol/server/zod-schemas. A consumer reading migration.md will try import { CallToolRequestSchema } from '@modelcontextprotocol/server' and get a type error.

Suggested fix for that line:

All other type symbols from @modelcontextprotocol/sdk/types.js retain their original names — import them from @modelcontextprotocol/client or @modelcontextprotocol/server. Zod schemas (e.g., CallToolRequestSchema) are no longer re-exported from the package roots; for runtime validation prefer specTypeSchema() or, for v1 source compatibility, import from @modelcontextprotocol/server/zod-schemas.

Fix this →


🟡 Test coverage doesn't exercise the changeset-stated surface

The changeset explicitly calls out getResultSchema/getRequestSchema/getNotificationSchema as intentionally included. The test at packages/server/test/zod-schemas.compat.test.ts only checks protocol *Schema constants. Adding one assertion like:

import { getResultSchema } from '@modelcontextprotocol/server/zod-schemas';
// ...
expect(getResultSchema('tools/call')).toBeInstanceOf(z.ZodType);

…would make the test surface match what the changeset promises. Minor, but the helpers are the only non-obvious part of this subpath — they deserve at least one smoke-test line.


🟡 @modelcontextprotocol/sdk meta-package is an unresolved forward reference

Felix's explanation for why @modelcontextprotocol/client/zod-schemas isn't needed is that the @modelcontextprotocol/sdk meta-package will re-export the server subpath at sdk/types.js. The workspace root is @modelcontextprotocol/sdk but is currently private: true with no exports map — so there's no published package today that bridges client-side v1 consumers to this subpath.

This is fine as a series commitment, but it means the client-side gap is real until that meta-package PR lands. Worth noting as a TODO comment in the changeset or this PR, so a future reviewer knows the two PRs are coupled.


✅ Everything from the previous review is addressed

  • @deprecated + specTypeSchema() pointer in zodSchemas.ts — done
  • get*Schema helper exposure documented in changeset rather than excluded — reasonable call
  • migration-SKILL.md:101 updated — done

The implementation itself (package.json exports map, tsdown entry, tsconfig paths, typedoc exclusion) is correct. The noExternal: ['@modelcontextprotocol/core'] ensures the bundled output is self-contained.

Re-exports the internal *Schema Zod constants (CallToolRequestSchema,
JSONRPCMessageSchema, etc.) from a deprecated /zod-schemas subpath so v1
code that imported schemas from @modelcontextprotocol/sdk/types.js has a
single drop-in target.

- packages/server/src/zodSchemas.ts: re-export barrel (deprecated module)
- packages/server/package.json: ./zod-schemas exports entry
- packages/server/tsdown.config.ts: build entry + dts path mapping
- packages/server/tsconfig.json: path mappings for core/schemas and the
  self-reference subpath
- packages/core/package.json: internal ./schemas subpath (core is private,
  consumed only by sibling packages)
- compat test asserting the import resolves and schemas parse

The schemas remain an internal implementation detail; their Zod major
version is not covered by semver. Subpath will be removed in v3.
@felixweinberger
Copy link
Copy Markdown
Contributor Author

Addressed: migration.md:503 updated; getResultSchema smoke test added; changeset notes the #1913 coupling.

@claude review

@felixweinberger felixweinberger force-pushed the fweinberger/v2-bc-zod-schemas-subpath branch from f7e391b to e77d678 Compare April 16, 2026 21:00
@claude
Copy link
Copy Markdown

claude bot commented Apr 16, 2026

Claude finished @felixweinberger's task in 1m 15s —— View job


Review

  • Gather context from PR
  • Read changed files (current branch state)
  • Verify claimed fixes
  • Review implementation
  • Post findings

All three issues from the previous review are resolved. The implementation is clean.

migration.md:503 — fixed correctly

The line now reads:

All other type symbols from @modelcontextprotocol/sdk/types.js retain their original names — import them from @modelcontextprotocol/client or @modelcontextprotocol/server. Zod schemas (e.g., CallToolRequestSchema) are not re-exported from the package roots; for runtime validation prefer specTypeSchema() or, for v1 source compatibility, import them from @modelcontextprotocol/server/zod-schemas.

Matches the suggested wording closely. migration-SKILL.md:101 is also updated consistently.

getResultSchema smoke test — added

packages/server/test/zod-schemas.compat.test.ts:20-22 covers the helper:

it('re-exports the get*Schema lookup helpers', () => {
    expect(getResultSchema('tools/call')).toBeInstanceOf(z.ZodType);
});

The test surface now matches the changeset's stated API.

@deprecated JSDoc — added with the right shape

packages/server/src/zodSchemas.ts:1-9 has a @packageDocumentation block with @deprecated, a pointer to specTypeSchema(), and the v1-compat framing. Per-group @deprecated tags on each export block cover the IDE hover case. No specific removal version promised, consistent with stated series policy.

✅ Changeset — notes #1913 coupling

The last line of .changeset/compat-zod-schemas-subpath.md explicitly calls out that @modelcontextprotocol/sdk (#1913) re-exports this subpath at sdk/types.js, so reviewers landing on either PR understand the dependency.

✅ Implementation plumbing

  • core/package.json ./schemas entry → src/types/schemas.ts (source, fine for private workspace dep)
  • tsdown.config.ts path alias for @modelcontextprotocol/core/schemas../core/src/types/schemas.ts; noExternal: ['@modelcontextprotocol/core'] ensures bundled output is self-contained
  • tsconfig.json maps @modelcontextprotocol/server/zod-schemas./src/zodSchemas.ts for test resolution
  • typedoc.json excludes **/zodSchemas.ts (correct — 150+ schema constants shouldn't pollute the API reference)

No further issues. LGTM.

@felixweinberger felixweinberger marked this pull request as ready for review April 17, 2026 10:16
@felixweinberger felixweinberger requested a review from a team as a code owner April 17, 2026 10:16
Comment on lines +4 to +14
* Prefer {@linkcode specTypeSchema} for runtime validation. These are Zod
* schemas; their TS type may change with internal Zod upgrades.
*
* @deprecated Use `specTypeSchema()` for runtime validation.
* @packageDocumentation
*/

/** @deprecated Use `specTypeSchema()` for runtime validation. */
export * from '@modelcontextprotocol/core/schemas';

/** @deprecated Use `specTypeSchema()` for runtime validation. */
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 The specTypeSchema() reference added in f7e391b (the {@linkcode} here, the @deprecated messages, docs/migration.md:503, docs/migration-SKILL.md:101, and the changeset) points at a function that doesn't exist on this branch — it comes from #1887, which hasn't landed and isn't listed as a stack dependency. The changeset already notes the #1913 coupling; consider noting the #1887 coupling the same way (or sequencing this after #1887) so the published migration docs/changelog don't direct users to a nonexistent API.

Extended reasoning...

What's wrong

f7e391b added specTypeSchema() as the "preferred" alternative in five places:

  • packages/server/src/zodSchemas.ts:4{@linkcode specTypeSchema}
  • packages/server/src/zodSchemas.ts:7,11,14@deprecated Use \specTypeSchema()` for runtime validation.`
  • docs/migration.md:503 — "for runtime validation prefer specTypeSchema()"
  • docs/migration-SKILL.md:101 — "prefer type guard functions like isCallToolResult or specTypeSchema()"
  • .changeset/compat-zod-schemas-subpath.md:7 — "Prefer specTypeSchema() for runtime validation."

But grep -r specTypeSchema packages/ shows no definition — the function appears only in the four files this PR touches. The PR description points to #1887 as its source, but that PR hasn't landed (git log --oneline on main shows no such commit) and "Stacks on: none" declares no dependency.

Why nothing prevents it

  • zodSchemas.ts is excluded from typedoc (typedoc.json:5), so the dangling {@linkcode} won't fail the doc build.
  • TypeScript doesn't validate @deprecated prose or {@linkcode} targets.
  • The migration docs and changeset are markdown — no link checker covers API symbol names.

So CI is green even though the prose promises an API that doesn't ship.

Step-by-step proof

  1. A v1 consumer reads docs/migration.md:503 (or the published changelog generated from the changeset) and sees: "for runtime validation prefer specTypeSchema()".
  2. They write import { specTypeSchema } from '@modelcontextprotocol/server' (or /client, or /core).
  3. grep confirms no such export exists in any package on this branch → TS2305 "Module has no exported member 'specTypeSchema'".
  4. The same applies to a developer hovering the @deprecated message in their IDE on any /zod-schemas import — they're told to use a function they can't find.

Impact

Documentation/sequencing only — no runtime breakage. But per the repo's review guide ("prose that promises behavior the code doesn't ship misleads consumers"), the published changelog and the migration guide are user-facing surfaces. The changeset already handles this correctly for the other forward reference (#1913: "re-exports this subpath at sdk/types.js, so v1 imports work unchanged once both PRs land") — the #1887 reference just lacks the equivalent caveat.

Why nit, not normal

This was added deliberately in f7e391b responding to review, so it's coordinated series work, not an oversight. If #1887 lands in the same release (which appears to be the plan), the changelog and docs will be correct at publish time. The risk is only if this PR ships in a release without #1887.

Fix options

Any of:

Comment on lines +30 to +34
} from '@modelcontextprotocol/core';
export {
/** @deprecated Use {@linkcode JSONRPCErrorResponseSchema}. */
JSONRPCErrorResponseSchema as JSONRPCErrorSchema
} from '@modelcontextprotocol/core/schemas';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 This alias block restores the v1 name JSONRPCErrorSchema but doesn't do the same for ResourceReferenceSchemaResourceTemplateReferenceSchema, even though both renames are listed side-by-side in the migration guide's removed-aliases table. A v1 consumer pointing import { ResourceReferenceSchema } from '@modelcontextprotocol/sdk/types.js' at this subpath gets a missing-export error while JSONRPCErrorSchema works — consider adding ResourceTemplateReferenceSchema as ResourceReferenceSchema here for consistency.

Extended reasoning...

What's inconsistent

packages/server/src/zodSchemas.ts:31-34 adds a v1-name alias for one of the two schema renames documented in the migration guide:

export {
    /** @deprecated Use {@linkcode JSONRPCErrorResponseSchema}. */
    JSONRPCErrorResponseSchema as JSONRPCErrorSchema
} from '@modelcontextprotocol/core/schemas';

But the parallel rename — ResourceReferenceSchemaResourceTemplateReferenceSchema — gets no alias. Both appear together in docs/migration.md's removed-aliases table (and in migration-SKILL.md §5) as v1 schema names that were dropped in v2. The export * on line 12 only carries ResourceTemplateReferenceSchema (the v2 name), because packages/core/src/types/schemas.ts does not export the old name.

Why existing code doesn't cover it

The wildcard re-export at line 12 pulls in everything @modelcontextprotocol/core/schemas exports, but that module only defines ResourceTemplateReferenceSchema (schemas.ts:1960). There is no ResourceReferenceSchema binding anywhere in core, so without an explicit as alias here the v1 name is simply absent from the compat subpath.

Step-by-step proof

  1. v1 consumer has import { ResourceReferenceSchema, JSONRPCErrorSchema } from '@modelcontextprotocol/sdk/types.js'.
  2. Per the changeset, feat(sdk): @modelcontextprotocol/sdk meta-package with v1 deep-import subpaths #1913 will re-export this subpath at sdk/types.js "so v1 imports work unchanged once both PRs land".
  3. The consumer's JSONRPCErrorSchema import resolves — line 33 aliases it.
  4. The consumer's ResourceReferenceSchema import fails: export * (line 12) only carries ResourceTemplateReferenceSchema, and there is no explicit alias for the old name.
  5. Result: TS2305 "Module has no exported member 'ResourceReferenceSchema'" — one of the two side-by-side renames works through the shim, the other doesn't.

Impact

Low — this is a completeness gap on a deprecated compat surface, not a correctness bug. The migration docs already tell users to rename both symbols, so the JSONRPCErrorSchema alias is itself a courtesy. But the PR sets a precedent (alias the renamed schema so v1 source compiles unchanged) and then applies it to only one of the two documented renames, which is surprising. ResourceReferenceSchema is admittedly low-traffic compared to JSONRPCErrorSchema.

Fix

Add the second alias alongside the first:

export {
    /** @deprecated Use {@linkcode JSONRPCErrorResponseSchema}. */
    JSONRPCErrorResponseSchema as JSONRPCErrorSchema,
    /** @deprecated Use {@linkcode ResourceTemplateReferenceSchema}. */
    ResourceTemplateReferenceSchema as ResourceReferenceSchema
} from '@modelcontextprotocol/core/schemas';

(Or, if the intent is that users must apply the documented renames regardless, drop the JSONRPCErrorSchema alias so the shim is consistent — but adding the second alias is the cheaper and more user-friendly option.)

@felixweinberger felixweinberger marked this pull request as draft April 17, 2026 10:54
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.

1 participant