Skip to content

Bundle automatic validator defaults in client/server shims#2088

Open
mattzcarey wants to merge 1 commit into
mainfrom
fix/server-cfworker-json-schema-shim
Open

Bundle automatic validator defaults in client/server shims#2088
mattzcarey wants to merge 1 commit into
mainfrom
fix/server-cfworker-json-schema-shim

Conversation

@mattzcarey
Copy link
Copy Markdown
Contributor

@mattzcarey mattzcarey commented May 14, 2026

Summary

  • Bundle both validator backends selected by client/server runtime shims: AJV for Node defaults, @cfworker/json-schema for browser/workerd defaults.
  • Remove explicit client/server validators/cf-worker subpath exports. Validator selection is automatic for normal users; advanced users can provide their own jsonSchemaValidator implementation.
  • Add regression tests that built client/server shims do not leave bare imports for ajv, ajv-formats, or @cfworker/json-schema for consumers to resolve.
  • Update migration docs to say users do not need to install or import validator packages.

Why

Client and server choose runtime defaults via conditional _shims:

  • Node shim: AJV-backed validator
  • Browser/workerd shim: @cfworker/json-schema-backed validator

Because client/server make those default choices, consumers should not need to know about or install validator implementation dependencies. Those backends are now bundled into the shim chunks that select them.

Impact

  • Client/server users: no extra validator install or validator import is needed. Node gets the bundled AJV default through the Node shim; browser/workerd gets the bundled cfworker validator through the browser/workerd shim.
  • Advanced users: custom validation is still possible by passing jsonSchemaValidator: myCustomValidator with their own implementation.
  • Published packages: packed client/server tarballs do not depend on @modelcontextprotocol/core, ajv, ajv-formats, or @cfworker/json-schema; those implementations are bundled where client/server need them.
  • Public surface: explicit concrete validator subpaths are removed from client/server. The migration path is automatic runtime selection, not explicit SDK validator imports.

Follow-up for codemod reviewers

If this validator packaging change ships, the v1-to-v2 codemod should probably remove redundant manual SDK validator imports/configuration for the built-in AJV and Cloudflare Workers validators. Those validators are no longer exported from client/server as explicit public subpaths, and normal migrations should rely on automatic runtime defaults instead.

Examples the codemod should consider simplifying/removing when they refer to the SDK built-ins:

import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server';
import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/validators/cf-worker';

new McpServer(info, {
  jsonSchemaValidator: new CfWorkerJsonSchemaValidator()
});

For custom user validators, the codemod should preserve the override:

new McpServer(info, { jsonSchemaValidator: myCustomValidator });

Reviewer note: this PR intentionally makes built-in validator selection automatic for client/server users; codemod output should not teach users to import SDK concrete validators for the default case.

@mattzcarey mattzcarey requested a review from a team as a code owner May 14, 2026 10:46
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 14, 2026

🦋 Changeset detected

Latest commit: a2b954b

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

This PR includes changesets to release 7 packages
Name Type
@modelcontextprotocol/core Minor
@modelcontextprotocol/client 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

Comment thread packages/codemod/src/bin/batchTest.ts Fixed
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 14, 2026

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: a2b954b

@mattzcarey mattzcarey force-pushed the fix/server-cfworker-json-schema-shim branch from 54c18d7 to ba08235 Compare May 14, 2026 11:02
@mattzcarey mattzcarey changed the title Bundle workerd JSON schema validator in server shim Make validator backends optional in core and bundle client/server defaults May 14, 2026
Comment thread packages/codemod/package.json Outdated
Comment thread packages/codemod/src/bin/batchTest.ts Outdated
Comment thread packages/codemod/src/bin/batchTest.ts Outdated
@mattzcarey mattzcarey force-pushed the fix/server-cfworker-json-schema-shim branch 2 times, most recently from c7601a2 to 5db154c Compare May 14, 2026 11:14
@mattzcarey mattzcarey changed the title Make validator backends optional in core and bundle client/server defaults Bundle automatic validator defaults in client/server shims May 14, 2026
@mattzcarey mattzcarey changed the base branch from main to feature/v2-codemode-draft May 14, 2026 11:16
@mattzcarey mattzcarey force-pushed the fix/server-cfworker-json-schema-shim branch from 5db154c to 78bd6c2 Compare May 14, 2026 11:18
@mattzcarey mattzcarey changed the base branch from feature/v2-codemode-draft to main May 14, 2026 11:18
Comment thread packages/core/src/exports/public/index.ts
@mattzcarey mattzcarey force-pushed the fix/server-cfworker-json-schema-shim branch 5 times, most recently from 9c5c541 to 57c3a0b Compare May 14, 2026 11:36
Comment on lines 28 to 31
"types": "./dist/stdio.d.mts",
"import": "./dist/stdio.mjs"
},
"./validators/cf-worker": {
"types": "./dist/validators/cfWorker.d.mts",
"import": "./dist/validators/cfWorker.mjs"
},
"./_shims": {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 nit: a few prose references to the removed validator paths survive — .changeset/cfworker-out-of-barrel.md:6 still says the validator is "reachable only via … the explicit @modelcontextprotocol/{server,client}/validators/cf-worker subpath … No public API change", packages/core/src/validators/ajvProvider.ts:36's @see still points at those same removed subpaths (this JSDoc is bundled into the published .d.mts), and .changeset/support-standard-json-schema.md:24-27 still shows import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core' + new AjvJsonSchemaValidator(), which is now a type-only export. Worth grepping for validators/cf-worker and updating these alongside the migration-doc rewrites so the next CHANGELOG and IDE hovers don't advertise paths this PR deletes.

Extended reasoning...

What the issue is

This PR removes the ./validators/cf-worker subpath from packages/{client,server}/package.json (and deletes both src/validators/cfWorker.ts files), and demotes AjvJsonSchemaValidator from a runtime re-export to export type in both core barrels. The PR updates the migration docs, the new changeset, and the JSDoc in packages/core/src/index.ts to match — but three pre-existing prose references to the old form survive untouched:

  1. .changeset/cfworker-out-of-barrel.md:6 — still reads: "The validator is now reachable only via the _shims conditional (workerd/browser) and the explicit @modelcontextprotocol/{server,client}/validators/cf-worker subpath, so consumers that don't opt into it no longer ship that code. No public API change." This changeset is not in .changeset/pre.json's consumed changesets array, so it is unreleased and will land in the same CHANGELOG version as this PR's workerd-shim-vendors-cfworker.md.
  2. packages/core/src/validators/ajvProvider.ts:36@see CfWorkerJsonSchemaValidatorfor an edge-runtime-compatible alternative (import from@modelcontextprotocol/server/validators/cf-workeror@modelcontextprotocol/client/validators/cf-worker). This file is not in the PR's changed-files list.
  3. .changeset/support-standard-json-schema.md:24-27import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core'; followed by new AjvJsonSchemaValidator(). After packages/core/src/index.ts:22 becomes export type { AjvJsonSchemaValidator }, that example is a TS error ("cannot be used as a value because it was exported using 'export type'"). This changeset is in pre.json (already applied to an alpha CHANGELOG), but in changesets pre-mode the .md persists and is re-aggregated into the final 2.0.0 CHANGELOG on changeset pre exit.

A repo-wide grep for validators/cf-worker confirms (1) and (2) are the only two surviving references to the removed subpath.

Why existing review feedback doesn't cover it

The earlier inline comment on packages/core/src/exports/public/index.ts:145 enumerated four doc sites that pointed at @modelcontextprotocol/core/... (docs/migration.md, docs/migration-SKILL.md, .changeset/workerd-shim-vendors-cfworker.md, and the JSDoc in packages/core/src/index.ts). The current revision reworked all four. None of the three locations above were on that list, so they were missed in the same cleanup pass — this is the REVIEW.md "Completeness" recurring catch ("when a PR replaces a pattern, grep the package for surviving instances of the old form").

Step-by-step proof

  1. This PR deletes "./validators/cf-worker" from packages/server/package.json exports + typesVersions and from packages/client/package.json, removes both src/validators/cfWorker.ts files, and drops them from both tsdown.config.ts entry arrays.
  2. changesets version runs on the next release. cfworker-out-of-barrel.md is not in pre.json's changesets[], so it is consumed alongside workerd-shim-vendors-cfworker.md. The generated packages/server/CHANGELOG.md for that version contains both entries: one says "reachable only via … /validators/cf-worker subpath … No public API change", the other says explicit validator subpaths are removed. A user reading the release notes gets directly contradictory guidance.
  3. tsdown bundles ajvProvider.ts's class JSDoc into dist/shimsNode.d.mts (it's re-exported as DefaultJsonSchemaValidator) and into the root .d.mts (type-only). A consumer hovers DefaultJsonSchemaValidator or AjvJsonSchemaValidator in their IDE and sees @see … import from @modelcontextprotocol/server/validators/cf-worker. They try import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/validators/cf-worker'ERR_PACKAGE_PATH_NOT_EXPORTED.
  4. On changeset pre exit, support-standard-json-schema.md is re-emitted into the stable 2.0.0 CHANGELOG. A user copies the fromJsonSchema example, gets 'AjvJsonSchemaValidator' cannot be used as a value because it was exported using 'export type' (and @modelcontextprotocol/core is private: true, so the import path was already wrong for external consumers even before this PR).

Impact

Doc-only; no runtime effect. The cfworker-out-of-barrel.md case is the most concrete because it produces self-contradicting published release notes in the very next version bump. The ajvProvider.ts @see surfaces in end-user IDE tooltips. The support-standard-json-schema.md example was already broken for external users (private package), so this PR just makes it slightly more broken; still worth fixing while validator docs are being touched.

How to fix

  • .changeset/cfworker-out-of-barrel.md: either delete it (its purpose is subsumed by workerd-shim-vendors-cfworker.md) or rewrite line 6 to drop the /validators/cf-worker subpath reference and the "No public API change" claim.
  • packages/core/src/validators/ajvProvider.ts:36: change the @see to reference automatic runtime selection (matching the updated packages/core/src/index.ts JSDoc), or simply @see {@link CfWorkerJsonSchemaValidator} without an import path.
  • .changeset/support-standard-json-schema.md:23-28: rewrite the example to import { fromJsonSchema } from '@modelcontextprotocol/server' and drop the explicit new AjvJsonSchemaValidator() argument, matching the updated docs/migration.md guidance.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks — ajvProvider.ts:36's @see is now fixed. The two changeset files are still untouched, though:

  • .changeset/cfworker-out-of-barrel.md:6 still says the validator is "reachable only via … the explicit @modelcontextprotocol/{server,client}/validators/cf-worker subpath … No public API change". This changeset is unreleased and will land in the same CHANGELOG version as workerd-shim-vendors-cfworker.md, producing directly contradictory release notes. Easiest fix is to delete it (its purpose is now subsumed by the new changeset).
  • .changeset/support-standard-json-schema.md:24-27 still shows import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core' + new AjvJsonSchemaValidator(), which is now a type-only export. Suggest rewriting to import { fromJsonSchema } from '@modelcontextprotocol/server' and dropping the explicit validator arg, matching the updated docs/migration.md example.

(Repo-wide grep for validators/cf-worker now matches only cfworker-out-of-barrel.md, confirming the ajvProvider.ts reference is gone.)

@mattzcarey mattzcarey force-pushed the fix/server-cfworker-json-schema-shim branch from 57c3a0b to a2b954b Compare May 14, 2026 11:46
Comment on lines 385 to 393
test('should register tool with raw JSON Schema input', async () => {
const inputSchema = fromJsonSchema<{ name: string }>(
{ type: 'object', properties: { name: { type: 'string' } }, required: ['name'] },
validator
);
const inputSchema = serverFromJsonSchema<{ name: string }>({
type: 'object',
properties: { name: { type: 'string' } },
required: ['name']
});

mcpServer.registerTool('greet', { inputSchema }, async ({ name }) => ({
content: [{ type: 'text', text: `Hello, ${name}!` }]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 nit: this rewrite makes describe('Raw JSON Schema via fromJsonSchema') and describe('fromJsonSchema with default validator (server wrapper)') exercise the same code path — both now call serverFromJsonSchema(...) with no explicit validator on the same {name:string} / {count:number} schemas, so the second block is a strict subset of the first (the first additionally asserts the tools/list inputSchema shape). The explicit-validator coverage that block 1 used to provide moved to the new packages/{client,server}/test/**/jsonSchemaValidatorOverride.test.ts files, so the simplest cleanup is to delete the second describe block.

Extended reasoning...

What changed

Before this PR, the two adjacent describe blocks in standardSchema.test.ts covered distinct axes:

  • describe('Raw JSON Schema via fromJsonSchema') built schemas via core's fromJsonSchema(schema, new AjvJsonSchemaValidator()) — i.e. an explicit validator instance passed by the caller.
  • describe('fromJsonSchema with default validator (server wrapper)') built schemas via serverFromJsonSchema(schema) with no validator argument — i.e. the server wrapper's runtime-selected default.

This PR removes AjvJsonSchemaValidator and core fromJsonSchema from the file's imports and rewrites block 1 to use serverFromJsonSchema with no validator argument (and renames its second test from 'should reject invalid input via AJV validation''…via default validation'). After the rewrite, both blocks call exactly the same function with the same default-validator semantics.

Side-by-side after the diff

Block 1: "Raw JSON Schema via fromJsonSchema" Block 2: "fromJsonSchema with default validator (server wrapper)"
Test 1 schema serverFromJsonSchema<{name:string}>({…name…}) serverFromJsonSchema<{name:string}>({…name…})
Test 1 assertions tools/list inputSchema shape + tools/call returns Hello, World! tools/call returns Hello, World!
Test 2 schema serverFromJsonSchema({…count:number…}) serverFromJsonSchema({…count:number…})
Test 2 assertions count:'not a number'isError + 'Input validation error' identical

Block 2 is now a strict subset of block 1 — the only delta is block 1's extra tools/list toMatchObject assertion. The two "reject invalid input" tests are functionally identical.

Why nothing was actually lost (addressing the refutation)

One reviewer argued this isn't worth flagging because (a) the blocks aren't strictly identical and (b) the explicit-validator coverage didn't disappear. Both points are correct and are why this is a nit, not a defect:

  • The rewrite was forced by this PR's own change — AjvJsonSchemaValidator is no longer a runtime export from @modelcontextprotocol/core's root barrel, so new AjvJsonSchemaValidator() here would have required rewiring to the /validators/ajv subpath (as elicitation.test.ts does in this same diff).
  • The explicit-override path that block 1 used to cover did move, not vanish: this PR adds packages/server/test/server/jsonSchemaValidatorOverride.test.ts and packages/client/test/client/jsonSchemaValidatorOverride.test.ts, each containing a 'fromJsonSchema uses an explicitly supplied custom validator' test with a RecordingValidator that asserts the exact schema and value passed through. That's stronger coverage than the old AJV smoke test.

So no coverage regression — the only residue is that this file now contains two describe blocks doing the same thing, which is the kind of test-hygiene cleanup that's cheap to do while the file is already being touched.

Step-by-step proof

  1. Pre-PR line 8 imports AjvJsonSchemaValidator, fromJsonSchema from core; block 1 opens with const validator = new AjvJsonSchemaValidator() and calls fromJsonSchema(schema, validator).
  2. Diff replaces line 8 with import { InMemoryTransport } from '@modelcontextprotocol/core' (dropping both symbols), deletes the validator const, and rewrites both fromJsonSchema(schema, validator) calls to serverFromJsonSchema({...}).
  3. serverFromJsonSchema was already imported on line 9 and is what block 2 (lines ~431-467, untouched by this diff) already uses.
  4. Result: block 1 test 1 ≡ block 2 test 1 minus the tools/list assertion; block 1 test 2 ≡ block 2 test 2 (tool name 'double' vs 'double-default' is the only difference).

Suggested fix

Delete describe('fromJsonSchema with default validator (server wrapper)') (lines ~431-467) — its tests are now fully covered by the rewritten block above it. Alternatively, fold block 1's tools/list assertion into block 2 and delete block 1; either way one block goes.

Comment on lines +41 to 46
* Client and server packages bundle their runtime default validator backends, so most users should
* rely on automatic runtime selection. Advanced users can pass their own validator implementation
* through client/server options.
*
* @example For Node.js with AJV
* ```ts source="./index.examples.ts#validation_ajv"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 nit: the prose update here stops one line short — the two @example blocks immediately below (lines 45-53, sourced from index.examples.ts#validation_ajv / #validation_cfWorker) still render new AjvJsonSchemaValidator() / new CfWorkerJsonSchemaValidator(), which now directly contradicts the rewritten paragraph saying to rely on automatic selection or pass a custom jsonSchemaValidator. This is separate from the three sites in the open comment on packages/server/package.json:31 (the suggested validators/cf-worker grep won't find these); easiest fix is to drop both @example blocks and the corresponding regions in index.examples.ts, or rewrite them to show { jsonSchemaValidator: myCustomValidator }.

Extended reasoning...

What the issue is

This PR's hunk for packages/core/src/index.ts rewrites the @module validation JSDoc: lines 22/27 demote AjvJsonSchemaValidator and CfWorkerJsonSchemaValidator to export type, and lines 35-43 replace the old "Import from: …/validators/cf-worker" guidance with new prose saying "Client and server packages bundle their runtime default validator backends, so most users should rely on automatic runtime selection. Advanced users can pass their own validator implementation through client/server options." The diff hunk's last context line is @example For Node.js with AJV — i.e. the edit stops exactly at the example boundary.

Lines 45-53, immediately below, are unchanged and still render:

// @example For Node.js with AJV
const validator = new AjvJsonSchemaValidator();
// @example For Cloudflare Workers
const validator = new CfWorkerJsonSchemaValidator();

Both snippets are sourced from packages/core/src/index.examples.ts (#validation_ajv at lines 16-21, #validation_cfWorker at lines 26-31), which still imports the concrete classes from ./validators/{ajv,cfWorker}Provider.js. So the examples file still compiles — this is purely a rendered-doc contradiction, not a build break.

Why it matters / why existing checks don't catch it

After this PR, both validator classes are type-only in both core barrels (src/index.ts:22,27 and src/exports/public/index.ts:144-145), and the published ./validators/cf-worker subpaths are removed from client and server. There is no published runtime path a reader could use to do new AjvJsonSchemaValidator() — yet the very JSDoc block this PR edits still demonstrates exactly that, two lines below prose telling them not to. Because index.examples.ts imports via internal relative paths, neither tsc nor the barrelClean tests flag the staleness.

Not a duplicate of the open comments

  • The still-open inline comment on packages/server/package.json:31 enumerates three other stale-prose sites (.changeset/cfworker-out-of-barrel.md, ajvProvider.ts:36, .changeset/support-standard-json-schema.md) and suggests grepping for validators/cf-worker. These @example blocks contain neither that string nor any import path, so that grep would not find them.
  • The resolved inline comment on exports/public/index.ts:145 did name "the JSDoc in packages/core/src/index.ts" — and the author updated the prose paragraph in response, but stopped before the adjacent @example snippets. This is the leftover half of that fix.

Step-by-step proof

  1. Open the rendered @module validation JSDoc (or just read packages/core/src/index.ts:30-53 after this PR).
  2. Lines 41-43 say: "most users should rely on automatic runtime selection. Advanced users can pass their own validator implementation through client/server options."
  3. Line 45 (@example For Node.js with AJV) renders const validator = new AjvJsonSchemaValidator();.
  4. AjvJsonSchemaValidator is export type on line 22 of this same file and on line 144 of exports/public/index.ts; the /validators/ajv and /validators/cf-worker subpaths are gone from packages/{client,server}/package.json. A reader following the example has no published constructor to call.
  5. Line 50 repeats the same for CfWorkerJsonSchemaValidator.
  6. The same JSDoc block therefore tells readers "don't instantiate concrete validators" and then shows them doing so — an internal contradiction introduced by this PR's prose rewrite.

Impact

Doc-only. @modelcontextprotocol/core is private: true and this is the internal root barrel's @module-level comment, so end-user exposure is limited (it's unclear it even surfaces in published .d.mts). But it's an inconsistency within the very block this PR edits, cheap to fold into the same cleanup pass that addresses the open packages/server/package.json:31 comment — hence nit.

How to fix

Either:

  • Delete the two @example blocks (lines 45-53) and the corresponding validation_ajv / validation_cfWorker regions in packages/core/src/index.examples.ts; or
  • Rewrite them to demonstrate the override pattern the new prose teaches, e.g. new McpServer(info, { jsonSchemaValidator: myCustomValidator }), mirroring the updated docs/migration.md snippet.

Comment on lines +58 to +69
test('runtime shims vendor default validator backends instead of requiring consumers to install them', () => {
for (const shim of ['shimsNode.mjs', 'shimsWorkerd.mjs']) {
const entry = join(distDir, shim);
expect(readFileSync(entry, 'utf8')).not.toMatch(VALIDATOR_BACKEND_IMPORT);

for (const chunk of chunkImportsOf(entry)) {
expect({ chunk, content: readFileSync(chunk, 'utf8') }).not.toEqual(
expect.objectContaining({ content: expect.stringMatching(VALIDATOR_BACKEND_IMPORT) })
);
}
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 nit (pre-existing): this regression test only covers .mjs, but the published .d.mts still bare-imports ajv — building packages/server emits dist/ajvProvider-*.d.mts with import { Ajv } from "ajv"; (for constructor(ajv?: Ajv)), transitively pulled in by dist/index.d.mts and dist/shimsNode.d.mts, and dist/types-*.d.mts likewise has import { JSONSchema } from "json-schema-typed";. Neither ajv nor json-schema-typed is in server/client dependencies/peerDependencies, so a skipLibCheck: false consumer who follows the new docs/migration-SKILL.md:524 instruction "Do not install ajv, ajv-formats, or @cfworker/json-schema" gets TS2307: Cannot find module 'ajv'. Either extend the scan to dist/*.d.mts and replace the Ajv ctor-arg type with a local structural type (matching what cfWorkerProvider already does — its .d.mts leaks no @cfworker/json-schema import), or add a skipLibCheck caveat to the migration-SKILL line. Same applies to packages/client/test/client/barrelClean.test.ts.

Extended reasoning...

What the issue is

This PR adds a regression test asserting that the runtime shim .mjs files (and their .mjs chunk imports) contain no bare from 'ajv'|'ajv-formats'|'@cfworker/json-schema' import. That correctly enforces the runtime half of the "users do not need to install validator packages" goal. But the published type declarations still leak a bare ajv import that this test does not cover, and this PR's new migration-doc line tells users not to install ajv.

Building packages/server at this PR's HEAD produces:

  • dist/ajvProvider-BD030R9d.d.mts:2import { Ajv } from "ajv"; (needed for constructor(ajv?: Ajv) on line 39).
  • dist/index.d.mts:3 and dist/shimsNode.d.mts:1 both import that chunk, so any consumer importing anything from @modelcontextprotocol/server has TypeScript load a .d.mts that does import { Ajv } from "ajv".
  • dist/types-YaX0ZWcZ.d.mts:1import { JSONSchema } from "json-schema-typed";.

Neither ajv nor json-schema-typed appears in packages/server/package.json dependencies or peerDependenciesajv is added only to devDependencies in this PR, and json-schema-typed is only in private core's deps. By contrast, dist/cfWorkerProvider-*.d.mts leaks no @cfworker/json-schema import because its constructor uses a locally-declared CfWorkerSchemaDraft type — so the AJV side is asymmetrically worse and the fix pattern already exists in-tree.

Why existing code doesn't catch it

The new VALIDATOR_BACKEND_IMPORT check iterates only ['shimsNode.mjs', 'shimsWorkerd.mjs'], and chunkImportsOf() follows only ./*.mjs relative imports — .d.mts files are never visited. tsdown's noExternal bundles the runtime code but does not inline external type references in the DTS output, so the Ajv parameter type survives as a bare import.

Why this is worth a nit on this PR despite being pre-existing

The .d.mts leak itself is pre-existing — the parent commit 2c0c481 also emits from "ajv" in dist/index-*.d.mts — so it is not a regression. What this PR newly adds is docs/migration-SKILL.md:524: "Do not install ajv, ajv-formats, or @cfworker/json-schema; client/server bundle the runtime-selected defaults." That instruction, taken at face value by a consumer with "skipLibCheck": false, produces a compile error on the published artifacts. This PR also touches ajvProvider.ts directly (the @see line), so the file in question is in-diff. The new test gives a (mild) false sense that the "no validator install needed" goal is fully enforced — it covers runtime but not types.

This is not a duplicate of the open inline comments: #3241089399 covers stale prose referencing the removed /validators/cf-worker subpath, and #3241222796 covers stale @example blocks in core/src/index.ts. Neither mentions the .d.mts bare-import leak or the skipLibCheck interaction.

Step-by-step proof

  1. A consumer creates a fresh project with "skipLibCheck": false in tsconfig.json and runs npm install @modelcontextprotocol/server.
  2. Following the new docs/migration-SKILL.md:524 guidance, they do not install ajv, ajv-formats, or @cfworker/json-schema.
  3. They write import { McpServer } from '@modelcontextprotocol/server'; and run tsc.
  4. TypeScript resolves node_modules/@modelcontextprotocol/server/dist/index.d.mts, which on line 3 does import … from './ajvProvider-BD030R9d.mjs'; → resolves ajvProvider-BD030R9d.d.mts.
  5. Line 2 of that file is import { Ajv } from "ajv";. ajv is not in node_modules (only zod is a runtime dependency of @modelcontextprotocol/server).
  6. tsc fails with error TS2307: Cannot find module 'ajv' or its corresponding type declarations. (and likewise TS2307 for json-schema-typed via types-*.d.mts).
  7. The new barrelClean.test.ts regression test passes throughout because it never reads *.d.mts.

Impact

Low — most TypeScript projects set skipLibCheck: true (it's the tsc --init default), so the failure mode is narrow. Runtime behavior is correct: the bundled .mjs chunks contain the AJV/cfworker code and no consumer install is needed to run. The concrete harm is doc-vs-artifact contradiction for the skipLibCheck:false minority, plus a test that enforces only half of the stated goal. The json-schema-typed half is clearly out-of-scope for this PR; mentioned for completeness.

How to fix

Either of:

  1. Close the leak — in packages/core/src/validators/ajvProvider.ts, replace the constructor(ajv?: Ajv) parameter type with a locally-declared structural type (or unknown/an internal interface), mirroring cfWorkerProvider.ts's CfWorkerSchemaDraft approach so the emitted .d.mts no longer needs import { Ajv } from "ajv". Optionally extend this test (and the client twin) to also scan dist/*.d.mts for VALIDATOR_BACKEND_IMPORT so the invariant is enforced for types as well as runtime.
  2. Soften the doc — add a brief caveat to docs/migration-SKILL.md:524 (and the corresponding docs/migration.md paragraph) noting that projects with skipLibCheck: false may still need ajv installed for type resolution until the declaration leak is closed.

Comment on lines +7 to +11
Make validator backends symmetrical in core and bundle automatic defaults in client/server runtime shims.

Core no longer re-exports concrete validator providers as runtime values from the root/public barrels. AJV/AJV formats and `@cfworker/json-schema` are optional peer backends behind explicit core validator provider subpaths, used internally by client/server shims.

Client/server continue to select defaults automatically: Node shims use AJV, while browser/workerd shims use `@cfworker/json-schema`. Those backends are bundled into the shim chunks that select them, so users do not need to install validator packages or import explicit validators for default behavior. Advanced users can still pass their own `jsonSchemaValidator` implementation.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 nit: this changeset describes the core barrel change and the shim bundling, but never says that the ./validators/cf-worker subpath export was removed from @modelcontextprotocol/{client,server} — line 11's "do not need to … import explicit validators" reads as a convenience, not a removal. Worth one explicit sentence here, especially since the open comment on packages/server/package.json:31 suggests deleting .changeset/cfworker-out-of-barrel.md, which would leave this as the only changeset covering the subpath and the removal entirely undocumented in the generated CHANGELOG.

Extended reasoning...

What the issue is

This PR removes a public subpath export from two published packages: ./validators/cf-worker is deleted from the exports and typesVersions maps in packages/{client,server}/package.json, both src/validators/cfWorker.ts re-export files are deleted, and the entry is dropped from both tsdown.config.ts arrays. The previously-documented pattern import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/validators/cf-worker' now resolves to ERR_PACKAGE_PATH_NOT_EXPORTED.

The PR's own changeset, .changeset/workerd-shim-vendors-cfworker.md, describes two things: (a) core no longer re-exporting concrete validator providers as runtime values from its barrels, and (b) client/server bundling default backends into their shim chunks. Line 11 says "users do not need to install validator packages or import explicit validators for default behavior" — but that frames explicit imports as no longer necessary, not as no longer possible. Nowhere does the changeset state that the ./validators/cf-worker subpath was removed from client/server.

Why this isn't covered by existing comments

The still-open inline comment on packages/server/package.json:31 is about other prose that still references the removed subpath (.changeset/cfworker-out-of-barrel.md, ajvProvider.ts:36's @see, .changeset/support-standard-json-schema.md). This finding is the inverse: the PR's own new changeset fails to announce the removal it introduces.

The two interact: that open comment's first suggested fix is "delete cfworker-out-of-barrel.md (its purpose is subsumed by workerd-shim-vendors-cfworker.md)". If the author follows that suggestion as-is, workerd-shim-vendors-cfworker.md becomes the only changeset touching this area — and it doesn't mention the subpath removal, so the generated CHANGELOG would contain no record that @modelcontextprotocol/{client,server}/validators/cf-worker ever went away.

The resolved comment on exports/public/index.ts:145 did raise the patch-vs-breaking concern, but for the earlier export type change. The author's resolution expanded the breaking surface (it removed the subpath entirely instead of adding a parallel one) without revisiting the changeset prose. The bump-level concern itself is muted by 2.0.0-alpha.2 pre-release status, so the actionable residue is just the missing prose.

Step-by-step proof

  1. A consumer on @modelcontextprotocol/server@2.0.0-alpha.2 has import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/validators/cf-worker' — the exact pattern the pre-PR docs/migration.md and docs/migration-SKILL.md documented.
  2. They upgrade to the next alpha containing this PR. packages/server/package.json no longer has "./validators/cf-worker" in exports; Node throws ERR_PACKAGE_PATH_NOT_EXPORTED.
  3. They open the generated packages/server/CHANGELOG.md for that version. The entry from workerd-shim-vendors-cfworker.md reads: "Client/server continue to select defaults automatically … users do not need to install validator packages or import explicit validators for default behavior. Advanced users can still pass their own jsonSchemaValidator implementation."
  4. Nothing in that text says the subpath was removed. The consumer has to infer the API removal from a sentence about defaults, or grep the diff.
  5. If .changeset/cfworker-out-of-barrel.md is also deleted per the open comment's suggestion, there is no CHANGELOG entry mentioning /validators/cf-worker at all.

Impact

Doc-only; no runtime effect. The PR description and updated migration guides do call this out ("Public surface: explicit concrete validator subpaths are removed from client/server"), so the gap is specifically in the changeset → CHANGELOG path. Filed as a nit given the alpha pre-release context and that three related doc nits are already open — but it's a one-line addition while the changeset is being touched anyway.

How to fix

Add one sentence to .changeset/workerd-shim-vendors-cfworker.md, e.g. after line 11:

The explicit ./validators/cf-worker subpath export has been removed from @modelcontextprotocol/client and @modelcontextprotocol/server; rely on automatic runtime selection or pass a custom jsonSchemaValidator.

(If .changeset/cfworker-out-of-barrel.md is kept and rewritten per the other open comment instead of deleted, that would also cover it — either location works as long as one changeset states the removal.)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants