Skip to content

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

Open
felixweinberger wants to merge 2 commits intomainfrom
fweinberger/v2-bc-zod-schemas-subpath
Open

feat(compat): add /server/zod-schemas subpath re-exporting *Schema constants#1906
felixweinberger wants to merge 2 commits 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: e8a7dcf

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

@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 thread packages/server/src/zodSchemas.ts Outdated
Comment thread packages/server/src/zodSchemas.ts
@felixweinberger felixweinberger marked this pull request as draft April 17, 2026 10:54
@felixweinberger felixweinberger marked this pull request as ready for review April 27, 2026 15:19
@KKonstantinov
Copy link
Copy Markdown
Contributor

We should be able to achieve this effect by:

This should remove the necessity to keep exporting zod schemas?

Comment on lines +32 to +37
export {
/** @deprecated Use {@linkcode JSONRPCErrorResponseSchema}. */
JSONRPCErrorResponseSchema as JSONRPCErrorSchema,
/** @deprecated Use {@linkcode ResourceTemplateReferenceSchema}. */
ResourceTemplateReferenceSchema as ResourceReferenceSchema
} 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.

🔴 The export * carries v2's JSONRPCResponseSchema (a union of result and error responses) under the same name v1 used for the result-only schema, so v1 code doing JSONRPCResponseSchema.parse(msg) at a transport boundary will now silently accept {jsonrpc, id, error} payloads as valid. This is the schema-constant twin of the isJSONRPCResponse flip already called out at migration.md:505 — add JSONRPCResultResponseSchema as JSONRPCResponseSchema to this alias block (the explicit named export shadows the wildcard) and add the JSONRPCResponseSchema → JSONRPCResultResponseSchema row to the removed-aliases tables in both migration docs.

Extended reasoning...

What changed semantically

In v1 (@modelcontextprotocol/sdk@1.20.2, src/types.ts:99-105), JSONRPCResponseSchema was z.object({jsonrpc, id, result}).strict() with the docstring "A successful (non-error) response to a request." — it required result, and .strict() rejected an error key. v1 had no JSONRPCResultResponseSchema; JSONRPCResponseSchema was the canonical success-response schema.

In v2 (packages/core/src/types/schemas.ts:187), the v1 schema was renamed to JSONRPCResultResponseSchema (line 148, same docstring), and a new JSONRPCResponseSchema was introduced as z.union([JSONRPCResultResponseSchema, JSONRPCErrorResponseSchema]) — it now matches both result and error responses. The migration guide already documents exactly this flip for the type guard at migration.md:505 (isJSONRPCResponseisJSONRPCResultResponse), and guards.ts:71 shows isJSONRPCResponse is implemented as JSONRPCResponseSchema.safeParse(...) — so the schema flipped the same way the guard did.

How the shim leaks the wider schema

zodSchemas.ts:13 does export * from '@modelcontextprotocol/core/schemas', which carries v2's union JSONRPCResponseSchema under the unchanged v1 name. The alias block at lines 32-37 already shadows the wildcard for two sibling renames (JSONRPCErrorResponseSchema as JSONRPCErrorSchema, ResourceTemplateReferenceSchema as ResourceReferenceSchema) but does not add JSONRPCResultResponseSchema as JSONRPCResponseSchema. Per ESM semantics an explicit named export would shadow the same name from export *, so the omission means consumers get the v2 union.

Why nothing catches it

The compat test (zod-schemas.compat.test.ts) only checks instanceof z.ZodType and a CallToolRequestSchema.parse; it never exercises the JSON-RPC envelope schemas. The migration guide's removed-aliases tables (migration.md:492-501, migration-SKILL.md:84-99) list isJSONRPCResponse → isJSONRPCResultResponse but omit the parallel JSONRPCResponseSchema → JSONRPCResultResponseSchema row, so a migrating user has no warning. Because the v2 schema is a strict superset of the v1 schema, nothing fails at compile time and the happy path (result responses) parses identically — the divergence only surfaces when an error response arrives.

Step-by-step proof

  1. v1 consumer has a custom transport doing const ok = JSONRPCResponseSchema.safeParse(raw); if (ok.success) handleResult(ok.data.result); else handleError(raw); — under v1, ok.successraw.result exists (.strict() + required result).
  2. They follow this PR's migration guidance and point the import at @modelcontextprotocol/server/zod-schemas (or, once feat(sdk): @modelcontextprotocol/sdk meta-package with v1 deep-import subpaths #1913 lands, leave it at sdk/types.js).
  3. export * at line 13 binds JSONRPCResponseSchema to v2's z.union([JSONRPCResultResponseSchema, JSONRPCErrorResponseSchema]).
  4. Server returns {"jsonrpc":"2.0","id":1,"error":{"code":-32603,"message":"Internal error"}}.
  5. v1 schema: .parse() throws / .safeParse().success === false (no result, extra error key under .strict()).
  6. v2 schema via this shim: .safeParse().success === true (matches the JSONRPCErrorResponseSchema arm of the union).
  7. handleResult(ok.data.result) runs with undefined; the error is swallowed.

Impact

This is the PR's stated target use case ("runtime validation at HTTP boundaries… custom transports"), and the changeset promises "v1 imports work unchanged." Same-name/silently-wider-acceptance is the worst failure mode for that promise: unlike a missing export, nothing fails — error responses pass validation as success and downstream code reads .result on an error payload. The repo already considered this footgun serious enough to give isJSONRPCResponse its own callout box in the migration guide; the schema constant deserves the same treatment.

Fix

Add to the alias block at lines 32-37:

export {
    /** @deprecated Use {@linkcode JSONRPCErrorResponseSchema}. */
    JSONRPCErrorResponseSchema as JSONRPCErrorSchema,
    /** @deprecated Use {@linkcode ResourceTemplateReferenceSchema}. */
    ResourceTemplateReferenceSchema as ResourceReferenceSchema,
    /** @deprecated Use {@linkcode JSONRPCResultResponseSchema}. v2's `JSONRPCResponseSchema` now matches both result and error responses. */
    JSONRPCResultResponseSchema as JSONRPCResponseSchema
} from '@modelcontextprotocol/core/schemas';

And add JSONRPCResponseSchema → JSONRPCResultResponseSchema (and JSONRPCResponse → JSONRPCResultResponse for the type) to the removed-aliases tables in docs/migration.md and docs/migration-SKILL.md, alongside the existing isJSONRPCResponse row.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Checked this against v1.25.2, v1.26.0, and v1.29.0 — JSONRPCResponseSchema is already z.union([JSONRPCResultResponseSchema, JSONRPCErrorResponseSchema]) at all three (src/types.ts:257-258). The result-only form cited above is from v1.20.2; the schema flipped mid-v1, not at v1→v2. So the shim's export * carrying the union matches latest v1 exactly, and aliasing JSONRPCResultResponseSchema as JSONRPCResponseSchema here would actually diverge from v1.29.0.

The guard isJSONRPCResponse did change semantics v1→v2 (v1.29.0:186 has export const isJSONRPCResponse = isJSONRPCResultResponse as a deprecated result-only alias), which migration.md:503 already documents — but /zod-schemas doesn't re-export guards.

The two 🟡 below (migration.md:896 stale "All Zod schemas" line, and the OAuth-block @deprecated pointing at spec-type-only specTypeSchemas) do look real and worth a quick fixup; my approve stands with those as non-blocking nits.

Comment thread docs/migration.md
| `AuthInfo` (from `server/auth/types.js`) | `AuthInfo` (now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`) |

All other types and schemas exported from `@modelcontextprotocol/sdk/types.js` retain their original names — import them from `@modelcontextprotocol/client` or `@modelcontextprotocol/server`.
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 the `isSpecType` / `specTypeSchemas` Records, or for v1 source compatibility import them from `@modelcontextprotocol/server/zod-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 PR rewrote line 503 (and migration-SKILL.md:101) to clarify that Zod schema constants are not at the package roots and live at @modelcontextprotocol/server/zod-schemas — but the same two files still say the opposite further down: migration.md:896 ("All Zod schemas and type definitions from types.ts" under Unchanged APIs — only the import paths changed) and migration-SKILL.md:206-207 ("Unchanged APIs … all Zod schemas"). A reader following those sections will map sdk/types.js → package root per the §3 table and hit TS2305. Suggest dropping "Zod schemas and" from migration.md:896 (or pointing at the /zod-schemas subpath), and dropping "all Zod schemas," from migration-SKILL.md:207.

Extended reasoning...

What's stale

This PR's purpose is to document where Zod schema constants live in v2. It rewrote two lines to that end:

  • docs/migration.md:503 — now reads: "Zod schemas (e.g., CallToolRequestSchema) are not re-exported from the package roots; … import them from @modelcontextprotocol/server/zod-schemas."
  • docs/migration-SKILL.md:101 — now reads: "Zod schemas … are not exported from the package root; … available from the @modelcontextprotocol/server/zod-schemas subpath."

But each file has a second occurrence of the same claim that the PR didn't touch:

  • docs/migration.md:896 — under ## Unchanged APIs whose lead-in is "The following APIs are unchanged between v1 and v2 (only the import paths changed)", the bullet still reads: "All Zod schemas and type definitions from types.ts (except the aliases listed above)".
  • docs/migration-SKILL.md:206-207"Unchanged APIs (only import paths changed): … StdioServerTransport, all Zod schemas, all callback return types."

Both leftover passages list Zod schemas alongside Client, McpServer, StdioServerTransport, etc. — every other item in those lists moved to a package root. The §3 import-mapping table for @modelcontextprotocol/sdk/types.js also points at the package root. So a reader applying "Zod schemas → unchanged, only import path changed → use the §3 mapping" lands on @modelcontextprotocol/server, which is exactly the misconception line 503 was rewritten to dispel.

Why nothing catches it

Both files are prose markdown; no link-checker or symbol-checker validates API names mentioned in them. CI is green. The author updated one occurrence in each file (responding to earlier bot review of line 503) but missed the second occurrence further down — a classic §Completeness leftover ("grep for surviving instances of the old form").

Step-by-step proof (migration.md)

  1. A v1 consumer reads docs/migration.md top-to-bottom and reaches ## Unchanged APIs (line 889).
  2. The lead-in says "only the import paths changed"; line 896 lists "All Zod schemas and type definitions from types.ts" alongside Client, transports, and StdioServerTransport.
  3. Every sibling bullet in that list resolves via the §Package-split mapping (sdk/types.js@modelcontextprotocol/client or @modelcontextprotocol/server).
  4. Consumer writes import { CallToolRequestSchema } from '@modelcontextprotocol/server'.
  5. packages/server/src/index.ts does not re-export CallToolRequestSchema (only zodSchemas.ts does, behind the ./zod-schemas subpath added in this PR) → TS2305 "Module has no exported member 'CallToolRequestSchema'".
  6. This is precisely the failure mode the 2026-04-16 bot review flagged on line 503 ("A consumer reading migration.md will try import { CallToolRequestSchema } from '@modelcontextprotocol/server' and get a type error") and that the PR fixed at 503 — line 896 is the surviving instance.

The same walk-through applies to migration-SKILL.md, which is designed to be loaded into an LLM for mechanical migration; an automated rewrite that pattern-matches "all Zod schemas → only import path changed → apply §3 table" will produce the broken import.

Addressing the refutation (migration-SKILL.md only)

One verifier argued that for migration-SKILL.md specifically, the PR resolved rather than created a contradiction: pre-PR line 101 said schemas were "no longer part of the public API", which flatly contradicted line 207's "all Zod schemas [unchanged]"; post-PR line 101 says they're at /zod-schemas, so line 207's "(only import paths changed)" is now literally true.

That's fair as far as it goes — line 207 is no longer a hard contradiction in the SKILL file. But it remains misleading in context: every other item in that comma-separated list (Client, McpServer, transports, StdioServerTransport, callback return types) maps to a package root via §3, and line 207 names no path, so the only mapping a mechanical reader can apply is the §3 sdk/types.js → package-root one. Line 101 corrects this if read, but an LLM scanning §5's summary list may not back-reference. Importantly, the refuter explicitly agreed that the migration.md:896 case is a genuine leftover ("both lines were previously consistent (and wrong), and the PR's edit to 503 left 896 stale"). Since the PR should fix migration.md:896 anyway, tidying the parallel migration-SKILL.md:207 in the same sweep is essentially free and keeps the two docs aligned.

Impact

Documentation consistency only — no runtime or build impact. Worst case is a confused migrating user (or LLM-driven migration) trying the package root, getting TS2305, and having to re-read §5. Hence nit.

Fix

  • migration.md:896- All type definitions from types.ts(except the aliases listed above); Zod schema constants moved to@modelcontextprotocol/server/zod-schemas`` — or simply drop "Zod schemas and".
  • migration-SKILL.md:207 → drop "all Zod schemas," (line 101 already covers them), or replace with "all Zod schemas (via the /zod-schemas subpath)".

Comment on lines +15 to +31
/** @deprecated Use `specTypeSchemas` / `isSpecType` for runtime validation. */
export {
IdJagTokenExchangeResponseSchema,
OAuthClientInformationFullSchema,
OAuthClientInformationSchema,
OAuthClientMetadataSchema,
OAuthClientRegistrationErrorSchema,
OAuthErrorResponseSchema,
OAuthMetadataSchema,
OAuthProtectedResourceMetadataSchema,
OAuthTokenRevocationRequestSchema,
OAuthTokensSchema,
OpenIdProviderDiscoveryMetadataSchema,
OpenIdProviderMetadataSchema,
OptionalSafeUrlSchema,
SafeUrlSchema
} from '@modelcontextprotocol/core';
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 @deprecated message on the OAuth/OIDC export block (line 15) was copy-pasted from the protocol-schema block above and points users at specTypeSchemas / isSpecType — but those are keyed on MCP spec types from core/src/types/schemas.ts, while OAuthTokensSchema, OAuthMetadataSchema, SafeUrlSchema, etc. live in core/src/shared/auth.ts and won't appear there. Since the PR description explicitly targets "token endpoints, OIDC discovery" consumers, this hint sends them to a dead end; consider dropping the replacement pointer on this block (just @deprecated v1-compat shim) or pointing at whatever the v2-native source for auth schemas is intended to be.

Extended reasoning...

What's wrong

packages/server/src/zodSchemas.ts:15 carries the same @deprecated text as line 12:

/** @deprecated Use `specTypeSchemas` / `isSpecType` for runtime validation. */
export {
    IdJagTokenExchangeResponseSchema,
    OAuthClientInformationFullSchema,
    ...
    OAuthTokensSchema,
    OpenIdProviderDiscoveryMetadataSchema,
    ...
    SafeUrlSchema
} from '@modelcontextprotocol/core';

On line 12 the pointer is correct — the wildcard re-export pulls from @modelcontextprotocol/core/schemas (i.e. core/src/types/schemas.ts), which is exactly the surface that specTypeSchemas / isSpecType (#1887) index by spec-type name. On line 15 it isn't: the OAuth/OIDC schemas come from packages/core/src/shared/auth.ts, which is a separate module that has nothing to do with MCP spec types.

Why specTypeSchemas / isSpecType can't cover these

  • specTypeSchemas / isSpecType (per feat(core): add isSpecType / specTypeSchemas Records for runtime validation of spec types #1887 and the PR description's own example, specTypeSchema('CallToolRequest')) are keyed on MCP protocol type names — the contents of core/src/types/schemas.ts.
  • Grep confirms schemas.ts contains zero OAuth* or OpenId* references — the OAuth/OIDC Zod schemas are defined exclusively in core/src/shared/auth.ts.
  • The core public barrel makes the separation explicit: packages/core/src/exports/public/index.ts:18 is annotated "Auth TypeScript types (NOT Zod schemas like OAuthMetadataSchema)" — the auth Zod schemas are deliberately kept off the spec-type surface.

So there will be no specTypeSchemas.OAuthTokens / isSpecType.OAuthMetadata entry for a consumer to migrate to.

Step-by-step proof

  1. A v1 consumer validates a token-endpoint response: OAuthTokensSchema.parse(await res.json()).
  2. They migrate to v2, import OAuthTokensSchema from @modelcontextprotocol/server/zod-schemas, and their IDE shows the deprecation hover from line 15: "Use specTypeSchemas / isSpecType for runtime validation."
  3. They look up specTypeSchemas (from feat(core): add isSpecType / specTypeSchemas Records for runtime validation of spec types #1887) and find it is a Record<SpecTypeName, ZodType> keyed on names like CallToolRequest, JSONRPCMessage, Tool — the contents of schemas.ts.
  4. There is no OAuthTokens, OAuthMetadata, OpenIdProviderMetadata, or SafeUrl key, because those schemas are defined in shared/auth.ts, not types/schemas.ts.
  5. The deprecation pointer is a dead end — there is no v2-native replacement at the location named.

Why this audience matters

The PR description's own motivation calls out "Consumers using them for runtime validation at HTTP boundaries (token endpoints, OIDC discovery, custom transports)" — i.e., OAuth-schema users are an explicitly targeted audience of this subpath. They are precisely the group that will hover OAuthTokensSchema and follow this hint.

Why nothing catches it

Line 15's text was copy-pasted from line 12 when e8a7dcf updated the wording from specTypeSchema() to specTypeSchemas / isSpecType. zodSchemas.ts is excluded from typedoc (typedoc.json:5), and TypeScript doesn't validate @deprecated prose, so no tooling flags the inapplicable pointer. This falls under the repo's recurring "Documentation & Changesets" catch — prose that promises behavior the code doesn't ship misleads consumers.

Impact & fix

Documentation/DX only, no runtime impact — hence nit. But the whole point of this PR is to give v1 consumers correct migration guidance, so a dead-end pointer on one of its two explicitly-named use cases is worth fixing. Either:

/** @deprecated v1-compat shim; OAuth/OIDC Zod schemas have no v2-native replacement yet. */

or simply drop the replacement clause on this block:

/** @deprecated v1-compat shim. */

(or, if there is an intended v2-native home for auth schemas, point there instead).

Copy link
Copy Markdown
Contributor

@bhosmer-ant bhosmer-ant left a comment

Choose a reason for hiding this comment

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

Reviewed at e8a7dcf9. LGTM — completeness verified exhaustively.

comm -23 of v1.29.0's exported *Schema constants against this subpath = empty set after the two aliases (JSONRPCErrorSchema, ResourceReferenceSchema). All 13 v1 auth schemas present. Built dist/zodSchemas.mjs shares the same chunk as index.mjs (no duplicated Zod construction). Tests/typecheck pass.

Merge-order note (zodSchemas.ts:4-15, docs/migration.md:503): the @deprecated guidance and migration-doc text point at specTypeSchemas / isSpecType, which come from #1887 (open). If this lands first, IDE tooltips and docs reference a non-existent API until #1887 is in. Suggest landing #1887 first, or softening to "prefer the type-guard helpers (isCallToolResult, etc.)" in the interim.

Question: subpath is server-only; there's no @modelcontextprotocol/client/zod-schemas. The PR description's own motivating examples (token endpoints, OIDC discovery, custom transports) are client-side use cases. Intentional given the #1913 meta-package covers sdk/types.js, or worth a client mirror?

Nit (docs/migration-SKILL.md:101): the update is prose-after-the-table. For the LLM-mechanical-migration use case, a mapping-table row (e.g. | *Schema constants from sdk/types.js | @modelcontextprotocol/server/zod-schemas (deprecated) |) would be more directly actionable.

Process: deep-analysis subagent + dynamic verification (built package, completeness diff vs v1.29.0, runtime import check). Bughunter unavailable this run (env auth issue).

* @packageDocumentation
*/

/** @deprecated Use `specTypeSchemas` / `isSpecType` for runtime validation. */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One more from a deeper pass (nit, non-blocking): the /** @deprecated */ on lines 12 and 14 doesn't propagate through export * / block-level export {…} to the individual symbols — TS only honors @deprecated on per-specifier JSDoc or the original declaration. So CallToolRequestSchema etc. won't show IDE strikethrough; only the two aliases at 33-37 do. The @packageDocumentation tag covers the TypeDoc page but not call sites. If per-symbol deprecation matters, each name needs its own /** @deprecated */ specifier (~156 of them); if the module-level signal is enough, fine to leave as-is.

Separately — bughunter independently flagged the JSONRPCResponseSchema claim above; same refutation applies (union since v1.25.2).

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.

3 participants