feat(client,server): bundle default validators, expose customisation via subpaths#2088
Conversation
🦋 Changeset detectedLatest commit: 99cf12f The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
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 |
@modelcontextprotocol/client
@modelcontextprotocol/codemod
@modelcontextprotocol/server
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
54c18d7 to
ba08235
Compare
c7601a2 to
5db154c
Compare
5db154c to
78bd6c2
Compare
9c5c541 to
57c3a0b
Compare
57c3a0b to
a2b954b
Compare
After PR #2088, end users can no longer import or construct AjvJsonSchemaValidator / CfWorkerJsonSchemaValidator — client/server bundle them via the runtime shim and expose only the jsonSchemaValidator interface as the public extension point. - Strip impossible `new AjvJsonSchemaValidator()` / `new CfWorkerJsonSchemaValidator()` snippets from JSDoc on the validation module, ajvProvider, cfWorkerProvider, and fromJsonSchema; mark the provider classes @internal. - fromJsonSchema example now declares `validator: jsonSchemaValidator` and notes that consumers importing from server/client omit the second arg. - Drop the type-only re-exports of AjvJsonSchemaValidator, CfWorkerJsonSchemaValidator, and CfWorkerSchemaDraft from core/public — with the runtime classes unreachable, a type-only handle is dead surface. - Reword the migration docs to describe backends by name (AJV / @cfworker/json-schema) instead of the now-internal class names.
After PR #2088, end users can no longer import or construct AjvJsonSchemaValidator / CfWorkerJsonSchemaValidator — client/server bundle them via the runtime shim and expose only the jsonSchemaValidator interface as the public extension point. - Strip impossible `new AjvJsonSchemaValidator()` / `new CfWorkerJsonSchemaValidator()` snippets from JSDoc on the validation module, ajvProvider, cfWorkerProvider, and fromJsonSchema; mark the provider classes @internal. - fromJsonSchema example now declares `validator: jsonSchemaValidator` and notes that consumers importing from server/client omit the second arg. - Drop the type-only re-exports of AjvJsonSchemaValidator, CfWorkerJsonSchemaValidator, and CfWorkerSchemaDraft from core/public — with the runtime classes unreachable, a type-only handle is dead surface. - Reword the migration docs to describe backends by name (AJV / @cfworker/json-schema) instead of the now-internal class names.
51f42cd to
e8006c0
Compare
- Update `.changeset/cfworker-out-of-barrel.md` to say the `./validators/cf-worker` subpath was removed (was: "reachable only via" that subpath, which is no longer true).
- Replace the impossible `import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core'` sample in `.changeset/support-standard-json-schema.md` with `fromJsonSchema` from `@modelcontextprotocol/server` and no explicit validator.
- Reword `@default` JSDoc on `ServerOptions`/`ClientOptions` `jsonSchemaValidator` from internal class names to backend names (AJV-backed / `@cfworker/json-schema`-backed) — these lines surface in published `.d.mts` IDE hover.
- Rework `.changeset/workerd-shim-vendors-cfworker.md` from a consumer vantage point: drop `core/public` references, state explicitly that the `./validators/cf-worker` subpath was removed and that the validator classes are no longer exported from client/server (not even as types).
- Delete the duplicate `describe('fromJsonSchema with default validator (server wrapper)')` block in `test/integration/test/standardSchema.test.ts`; its two tests are strict subsets of the preceding `Raw JSON Schema via fromJsonSchema` block (explicit-validator coverage already lives in the new `jsonSchemaValidatorOverride.test.ts` files).
- Document the `./validators/cf-worker` subpath removal in `docs/migration.md` and `docs/migration-SKILL.md` per repo policy for breaking changes.
After PR #2088, end users can no longer import or construct AjvJsonSchemaValidator / CfWorkerJsonSchemaValidator — client/server bundle them via the runtime shim and expose only the jsonSchemaValidator interface as the public extension point. - Strip impossible `new AjvJsonSchemaValidator()` / `new CfWorkerJsonSchemaValidator()` snippets from JSDoc on the validation module, ajvProvider, cfWorkerProvider, and fromJsonSchema; mark the provider classes @internal. - fromJsonSchema example now declares `validator: jsonSchemaValidator` and notes that consumers importing from server/client omit the second arg. - Drop the type-only re-exports of AjvJsonSchemaValidator, CfWorkerJsonSchemaValidator, and CfWorkerSchemaDraft from core/public — with the runtime classes unreachable, a type-only handle is dead surface. - Reword the migration docs to describe backends by name (AJV / @cfworker/json-schema) instead of the now-internal class names.
- Update `.changeset/cfworker-out-of-barrel.md` to say the `./validators/cf-worker` subpath was removed (was: "reachable only via" that subpath, which is no longer true).
- Replace the impossible `import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core'` sample in `.changeset/support-standard-json-schema.md` with `fromJsonSchema` from `@modelcontextprotocol/server` and no explicit validator.
- Reword `@default` JSDoc on `ServerOptions`/`ClientOptions` `jsonSchemaValidator` from internal class names to backend names (AJV-backed / `@cfworker/json-schema`-backed) — these lines surface in published `.d.mts` IDE hover.
- Rework `.changeset/workerd-shim-vendors-cfworker.md` from a consumer vantage point: drop `core/public` references, state explicitly that the `./validators/cf-worker` subpath was removed and that the validator classes are no longer exported from client/server (not even as types).
- Delete the duplicate `describe('fromJsonSchema with default validator (server wrapper)')` block in `test/integration/test/standardSchema.test.ts`; its two tests are strict subsets of the preceding `Raw JSON Schema via fromJsonSchema` block (explicit-validator coverage already lives in the new `jsonSchemaValidatorOverride.test.ts` files).
- Document the `./validators/cf-worker` subpath removal in `docs/migration.md` and `docs/migration-SKILL.md` per repo policy for breaking changes.
…orkers test PR #2088 adds @cfworker/json-schema to noExternal so the validator is bundled into the published @modelcontextprotocol/server tarball. The Cloudflare Workers integration test was still installing the package as a direct dep in the generated consumer package.json, which masked any future re-externalization regression — wrangler would resolve the bare import from the test's own install instead of failing. Removing the dep turns the test into a true regression guard that the bundle is genuinely self-contained, matching the migration docs. Closes the last open review thread on #2088.
Restore the public entry points for the built-in JSON Schema validator
classes via symmetric subpaths so consumers can customize the bundled
backends without having to reimplement the entire 'jsonSchemaValidator'
interface:
- @modelcontextprotocol/{client,server}/validators/ajv
exports AjvJsonSchemaValidator (was unreachable since #2088)
- @modelcontextprotocol/{client,server}/validators/cf-worker
exports CfWorkerJsonSchemaValidator (was reachable on main, removed
by #2088; restored here)
The named classes are intentionally NOT re-exported from the root barrel
(@modelcontextprotocol/server / @modelcontextprotocol/client). Re-exporting
either as a runtime value would pull the corresponding peer dep ('ajv'
or '@cfworker/json-schema') into every consumer's root index chunk,
defeating the purpose of the runtime shim's pay-only-for-what-you-use
default. Consumers who never customize get the validator transparently
via the shim; consumers who customize import from the subpath and
install the peer dep themselves.
The shim continues to bundle 'ajv' + 'ajv-formats' (Node) and
'@cfworker/json-schema' (workerd/browser) via tsdown noExternal so the
default code path remains zero-install. The barrel-clean regression
tests are tightened to match top-of-line bare imports only, so JSDoc
examples that reference 'from "ajv"' inside vendored chunks do not
trigger false positives.
The migration docs and changesets are rewritten to document the new
customization path.
f7b60a2 to
ea0d8e0
Compare
…orkers test PR #2088 adds @cfworker/json-schema to noExternal so the validator is bundled into the published @modelcontextprotocol/server tarball. The Cloudflare Workers integration test was still installing the package as a direct dep in the generated consumer package.json, which masked any future re-externalization regression — wrangler would resolve the bare import from the test's own install instead of failing. Removing the dep turns the test into a true regression guard that the bundle is genuinely self-contained, matching the migration docs. Closes the last open review thread on #2088.
Restore the public entry points for the built-in JSON Schema validator
classes via symmetric subpaths so consumers can customize the bundled
backends without having to reimplement the entire 'jsonSchemaValidator'
interface:
- @modelcontextprotocol/{client,server}/validators/ajv
exports AjvJsonSchemaValidator (was unreachable since #2088)
- @modelcontextprotocol/{client,server}/validators/cf-worker
exports CfWorkerJsonSchemaValidator (was reachable on main, removed
by #2088; restored here)
The named classes are intentionally NOT re-exported from the root barrel
(@modelcontextprotocol/server / @modelcontextprotocol/client). Re-exporting
either as a runtime value would pull the corresponding peer dep ('ajv'
or '@cfworker/json-schema') into every consumer's root index chunk,
defeating the purpose of the runtime shim's pay-only-for-what-you-use
default. Consumers who never customize get the validator transparently
via the shim; consumers who customize import from the subpath and
install the peer dep themselves.
The shim continues to bundle 'ajv' + 'ajv-formats' (Node) and
'@cfworker/json-schema' (workerd/browser) via tsdown noExternal so the
default code path remains zero-install. The barrel-clean regression
tests are tightened to match top-of-line bare imports only, so JSDoc
examples that reference 'from "ajv"' inside vendored chunks do not
trigger false positives.
The migration docs and changesets are rewritten to document the new
customization path.
ea0d8e0 to
e6e3d33
Compare
…orkers test PR #2088 adds @cfworker/json-schema to noExternal so the validator is bundled into the published @modelcontextprotocol/server tarball. The Cloudflare Workers integration test was still installing the package as a direct dep in the generated consumer package.json, which masked any future re-externalization regression — wrangler would resolve the bare import from the test's own install instead of failing. Removing the dep turns the test into a true regression guard that the bundle is genuinely self-contained, matching the migration docs. Closes the last open review thread on #2088.
Restore the public entry points for the built-in JSON Schema validator
classes via symmetric subpaths so consumers can customize the bundled
backends without having to reimplement the entire 'jsonSchemaValidator'
interface:
- @modelcontextprotocol/{client,server}/validators/ajv
exports AjvJsonSchemaValidator (was unreachable since #2088)
- @modelcontextprotocol/{client,server}/validators/cf-worker
exports CfWorkerJsonSchemaValidator (was reachable on main, removed
by #2088; restored here)
The named classes are intentionally NOT re-exported from the root barrel
(@modelcontextprotocol/server / @modelcontextprotocol/client). Re-exporting
either as a runtime value would pull the corresponding peer dep ('ajv'
or '@cfworker/json-schema') into every consumer's root index chunk,
defeating the purpose of the runtime shim's pay-only-for-what-you-use
default. Consumers who never customize get the validator transparently
via the shim; consumers who customize import from the subpath and
install the peer dep themselves.
The shim continues to bundle 'ajv' + 'ajv-formats' (Node) and
'@cfworker/json-schema' (workerd/browser) via tsdown noExternal so the
default code path remains zero-install. The barrel-clean regression
tests are tightened to match top-of-line bare imports only, so JSDoc
examples that reference 'from "ajv"' inside vendored chunks do not
trigger false positives.
The migration docs and changesets are rewritten to document the new
customization path.
e6e3d33 to
917cd4a
Compare
…orkers test PR #2088 adds @cfworker/json-schema to noExternal so the validator is bundled into the published @modelcontextprotocol/server tarball. The Cloudflare Workers integration test was still installing the package as a direct dep in the generated consumer package.json, which masked any future re-externalization regression — wrangler would resolve the bare import from the test's own install instead of failing. Removing the dep turns the test into a true regression guard that the bundle is genuinely self-contained, matching the migration docs. Closes the last open review thread on #2088.
|
@claude review pls |
Restore the public entry points for the built-in JSON Schema validator
classes via symmetric subpaths so consumers can customize the bundled
backends without having to reimplement the entire 'jsonSchemaValidator'
interface:
- @modelcontextprotocol/{client,server}/validators/ajv
exports AjvJsonSchemaValidator (was unreachable since #2088)
- @modelcontextprotocol/{client,server}/validators/cf-worker
exports CfWorkerJsonSchemaValidator (was reachable on main, removed
by #2088; restored here)
The named classes are intentionally NOT re-exported from the root barrel
(@modelcontextprotocol/server / @modelcontextprotocol/client). Re-exporting
either as a runtime value would pull the corresponding peer dep ('ajv'
or '@cfworker/json-schema') into every consumer's root index chunk,
defeating the purpose of the runtime shim's pay-only-for-what-you-use
default. Consumers who never customize get the validator transparently
via the shim; consumers who customize import from the subpath and
install the peer dep themselves.
The shim continues to bundle 'ajv' + 'ajv-formats' (Node) and
'@cfworker/json-schema' (workerd/browser) via tsdown noExternal so the
default code path remains zero-install. The barrel-clean regression
tests are tightened to match top-of-line bare imports only, so JSDoc
examples that reference 'from "ajv"' inside vendored chunks do not
trigger false positives.
The migration docs and changesets are rewritten to document the new
customization path.
917cd4a to
0768977
Compare
v1 source code that imports built-in validators looks like:
import { CfWorkerJsonSchemaValidator } from
'@modelcontextprotocol/sdk/validation/cfworker-provider.js';
The v1-to-v2 codemod now rewrites this to the v2 subpath that ships
the named class, picking the right base package per file:
import { CfWorkerJsonSchemaValidator } from
'@modelcontextprotocol/{client,server}/validators/cf-worker';
Covers all six v1 paths users wrote in the wild:
validation/ajv-provider.js, validation/ajv.js, validation/ajv
validation/cfworker-provider.js, validation/cfworker.js, validation/cfworker
plus the (rarer) validation/index.js and validation/types.js barrels
which carry only the jsonSchemaValidator interface — those route to
the base client/server package, which already re-exports those types.
Resolution uses the same sibling-import + project-type heuristic as
the rest of the importPaths transform: if the file already imports
from sdk/client/* it routes to /client/validators/..., otherwise it
routes to /server/validators/..., otherwise it falls back to the
project type from package.json.
Adds a subpathSuffix field to ImportMapping so the existing
RESOLVE_BY_CONTEXT logic can append a subpath after picking the base.
Eight new tests cover all six v1 paths, both base packages, and the
usedPackages output. Verified against the agents repo (the real-world
v1 caller this PR is unblocking).
0768977 to
99cf12f
Compare
There was a problem hiding this comment.
Thanks for the follow-up commits — the mockPaths transform now appends subpathSuffix (with test coverage), the validator subpaths are re-exposed on client/server, and the CF Workers integration test no longer pre-installs @cfworker/json-schema, which together address my earlier findings, and I found no new issues in this revision. That said, this PR reshapes the public validator API surface (new subpath exports, bundling/dts config, codemod mappings, migration docs), so it still warrants a maintainer's design-level sign-off rather than auto-approval.
Extended reasoning...
Overview
This revision (head 99cf12f) of PR #2088 spans 46 files: it bundles the default JSON Schema validator backends (AJV for Node, @cfworker/json-schema for workerd/browser) into the client/server runtime shims via noExternal, exposes customisation through new ./validators/{ajv,cf-worker} subpath exports that re-export the vendored Ajv/addFormats, demotes the validator classes to type-only on the core barrels, adds codemod mappings (subpathSuffix) for the v1 validation/* paths, and rewrites the migration docs, changesets, tests, and tsconfig path maps accordingly.
Changes since my last review
The latest commits respond directly to prior review threads: mockPaths.ts now appends mapping.subpathSuffix in its RESOLVE_BY_CONTEXT branch (mirroring importPaths.ts) and mockPaths.test.ts adds vi.mock / vi.doMock / jest.mock / dynamic-import coverage for the validator specifiers; the ./validators/ajv and ./validators/cf-worker subpaths exist on both published packages with matching exports/typesVersions/tsdown entries; and cloudflareWorkers.test.ts no longer pre-installs @cfworker/json-schema, so it now genuinely guards the bundling invariant. The bug-hunting pass on this revision surfaced no new findings.
Security risks
No security-sensitive surface is touched — no auth, crypto, or transport-session logic changes. The main risks are supply-chain/bundling-shaped (vendoring ajv/ajv-formats/@cfworker/json-schema into published dist chunks) and API-compatibility-shaped (root barrels now export the validator classes type-only), neither of which is a vulnerability per se.
Level of scrutiny
This is exactly the kind of PR the repo's review conventions reserve for human judgment: it adds new public subpath exports, changes what the root barrels export, alters the published bundle contents, and ships a migration story (docs + codemod) whose prose has already gone through several correction rounds (one previously-flagged docs/changeset thread about the peer-dep wording was resolved by a maintainer and I have not re-raised it). Minimalism and API-surface trade-offs here are maintainer calls, not something to shadow-approve.
Other factors
Test coverage for the new behavior is reasonable: barrel-cleanliness regression tests for the shims, validator-override tests on client and server, codemod transform tests for both import and mock paths, and the workerd end-to-end test. The maintainer is actively engaged on the PR, so this defer is primarily a confirmation that the latest round of fixes checks out from my side.
What this PR does
Makes the default JSON Schema validator a zero-install, zero-config experience that adapts to your runtime, and gives customisation users a single-import escape hatch with no
package.jsonchanges required.Default usage — just import
McpServerThat's it. No installs, no config, no validator imports.
The package's
_shimsconditional export picks the right runtime shim for you, and the shim has the right validator backend bundled into it:node script.js, etc.)dist/shimsNode.mjsajv-formatswrangler devdist/shimsWorkerd.mjs@cfworker/json-schema--platform=browserdist/shimsWorkerd.mjs@cfworker/json-schemadist/shimsWorkerd.mjs@cfworker/json-schemaIf you move the same code from Node to a Worker, the bundle just picks the right backend — your code doesn't change, your
package.jsondoesn't change.Customising the bundled validator
Want to add custom AJV formats, change the draft, pre-register schemas by
$id? Import from the subpath. The subpath re-exportsAjvandaddFormatsfrom the SDK's vendored copy, so you don't needajvorajv-formatsin yourpackage.json:Same idea for the workerd validator (its customisation surface is a plain options object — no third-party constructor to expose):
The same paths exist on
@modelcontextprotocol/client/validators/{ajv,cf-worker}.Important: importing from one of these subpaths pins your code to that backend. The default
McpServerimport would have picked the validator per runtime; the subpath explicitly chooses AJV (or cfworker) regardless of where you deploy. Use the subpath when you actually want that, not as a habit.Replacing validation wholesale
Bring your own object implementing the
jsonSchemaValidatorinterface:Why the named classes are subpath-only, not on the root barrel
Re-exporting
AjvJsonSchemaValidatororCfWorkerJsonSchemaValidatorfrom@modelcontextprotocol/server(root) would pull the corresponding backend's code into every consumer's root index chunk, including the 95% of users who never customise. Subpaths keep the default code path validator-overhead-free and let bundlers tree-shake cleanly. The structural type used for the AJV constructor parameter (AjvLikedeclared insideajvProvider.ts) similarly removes the bareimport { Ajv } from "ajv"from the chunk transitively reached by the root entry, so TS consumers don't hitTS2307either.Codemod (v1 → v2)
The
v1-to-v2codemod (@modelcontextprotocol/codemod) rewrites v1 validator imports to the new v2 subpaths automatically, pickingclientvsserverper file based on sibling imports + the consumer'spackage.json:@modelcontextprotocol/sdk/validation/{ajv,ajv-provider}[.js]@modelcontextprotocol/{client,server}/validators/ajv@modelcontextprotocol/sdk/validation/{cfworker,cfworker-provider}[.js]@modelcontextprotocol/{client,server}/validators/cf-worker@modelcontextprotocol/sdk/validation/{index,types}.js@modelcontextprotocol/{client,server}(type-only barrel)Verified end-to-end against a real-world v1 caller this PR is unblocking.
Known limitations
The SDK's bundled AJV does not tree-shake away if the user passes their own.
Server's constructor falls back tonew DefaultJsonSchemaValidator()via a static??expression, so the bundler keeps the default validator's chunk reachable even when every call site at runtime supplies a customjsonSchemaValidator. A user who importsAjvdirectly from'ajv'(instead of from our subpath) and passes their own instance ends up with two copies of AJV in their bundle — ours (vendored into the shim) + theirs (from theirnode_modules). ~30 KB gzipped overhead.Workarounds for that user:
Ajvfrom our subpath (import { Ajv } from '@modelcontextprotocol/server/validators/ajv') so both the validator class and the AJV constructor come from the SDK's single vendored copy.dedupe, pnpm'sdedupe, Webpackresolve.alias) if their AJV version matches the SDK's pin.The default case — just
McpServerwith no custom validator — is unaffected: one copy of AJV (or@cfworker/json-schema), exactly the one the runtime needs. The duplication only shows up for the customisation path with a third-party AJV install.Properly fixing the tree-shake would require an API change (e.g. dynamic
import()of the default, which propagatesasyncinto validation; or making the default opt-in, which removes the zero-config story). Left as a future follow-up if real complaints come in.Migration
For users who never customised the validator: nothing to do.
For users who customised it manually:
For AJV specifically, the new subpath now serves
AjvandaddFormatstoo, so you can dropajv/ajv-formatsfrompackage.json:The codemod handles the path rewrite. Folding the
ajv/ajv-formatsimports into the new subpath, and removing them frompackage.json, is a follow-up.