Bundle automatic validator defaults in client/server shims#2088
Bundle automatic validator defaults in client/server shims#2088mattzcarey wants to merge 3 commits into
Conversation
🦋 Changeset detectedLatest commit: 9739e09 The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 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/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.
| - AJV (Node.js): `import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server';` | ||
| - CF Worker: `import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/validators/cf-worker';` | ||
| - Do not add validator imports for normal migrations. | ||
| - Do not install `ajv`, `ajv-formats`, or `@cfworker/json-schema`; client/server bundle the runtime-selected defaults. |
There was a problem hiding this comment.
🟡 The migration docs added here say consumers should not install @cfworker/json-schema, but test/integration/test/server/cloudflareWorkers.test.ts:46 — the only end-to-end test that runs the packed server tarball under real workerd — still pre-installs '@cfworker/json-schema': '^4.1.1' in the generated consumer package.json. That dep is now dead weight after this PR adds @cfworker/json-schema to noExternal, and it masks a future re-externalization regression because wrangler would resolve the bare import from the test's pre-installed copy instead of failing. Removing line 46 turns the test into a true regression guard and aligns it with the docs this PR adds.
Extended reasoning...
What the issue is
docs/migration-SKILL.md:524 (and the matching paragraph in docs/migration.md and .changeset/workerd-shim-vendors-cfworker.md) now instructs consumers: "Do not install ajv, ajv-formats, or @cfworker/json-schema; client/server bundle the runtime-selected defaults." But the SDK's own consumer simulation in test/integration/test/server/cloudflareWorkers.test.ts:44-47 still does exactly the thing the docs say not to do — it writes a generated package.json with an explicit dependency:
dependencies: {
'@modelcontextprotocol/server': `file:./${tarballName}`,
'@cfworker/json-schema': '^4.1.1' // <-- now contradicts the docs
},That file is not in this PR's diff (last touched in #1652), so this is a surviving instance of the pattern the PR replaces.
Why the explicit install used to be needed, and isn't anymore
At the PR base commit, packages/server/tsdown.config.ts had noExternal: ['@modelcontextprotocol/core'] only. The built dist/shimsWorkerd.mjs (and its chunks) therefore left a bare import { Validator } from '@cfworker/json-schema' for the consumer's bundler to resolve. wrangler's esbuild step would fail without it, so the test pre-installed the package.
This PR adds 'ajv', 'ajv-formats', '@cfworker/json-schema' to noExternal in both packages/server/tsdown.config.ts and packages/client/tsdown.config.ts, inlining the cfworker code into the workerd shim chunk. The explicit dep in the test consumer's package.json is now redundant.
Why it matters: the dead dep masks a regression
cloudflareWorkers.test.ts is the only test that exercises the packed tarball under a real workerd runtime via wrangler dev. The new barrelClean.test.ts tests added in this PR only string-match the dist/*.mjs output for bare from 'ajv'|'ajv-formats'|'@cfworker/json-schema' imports — useful but static. If a future tsdown/config change re-externalizes @cfworker/json-schema (e.g. someone removes it from noExternal while refactoring), the static test would catch it — but if that test is also touched, the workerd test should be the safety net. Today it isn't, because the consumer ships its own copy of @cfworker/json-schema, so wrangler's bundler would resolve the bare import from node_modules and the test would keep passing.
Step-by-step proof
- Test packs
@modelcontextprotocol/serverto a tarball and writes a consumerpackage.jsonlisting bothfile:./<tarball>and'@cfworker/json-schema': '^4.1.1'. npm installputs both in the consumer'snode_modules.- The test's
server.tsconstructsnew McpServer(...)— theServerconstructor eagerly doesthis._jsonSchemaValidator = options?.jsonSchemaValidator ?? new DefaultJsonSchemaValidator(), so the workerd shim's static@cfworker/json-schemaimport path is exercised. wrangler devruns esbuild overserver.ts. After this PR, the import resolves to the bundled copy insidedist/shimsWorkerd.mjs— no bare import to satisfy.- Hypothetical regression:
@cfworker/json-schemais dropped fromnoExternal.dist/shimsWorkerd.mjsnow hasfrom '@cfworker/json-schema'. Without line 46, esbuild fails ("Could not resolve") and the test catches it. With line 46, esbuild resolves it from the test's pre-installed copy, the test passes, and the regression ships.
How to fix
Delete the '@cfworker/json-schema': '^4.1.1' line from the generated consumer package.json at test/integration/test/server/cloudflareWorkers.test.ts:46. This is a one-line change that turns the test into a genuine end-to-end regression guard for the bundling invariant and brings it into agreement with the docs this PR adds.
Filed as a nit: the test isn't directly modified by the PR, the static barrelClean.test.ts already guards the structural invariant, and removing the line is a hygiene improvement rather than a fix for broken behavior.
Summary
@cfworker/json-schemafor browser/workerd defaults.validators/cf-workersubpath exports. Validator selection is automatic for normal users; advanced users can provide their ownjsonSchemaValidatorimplementation.AjvJsonSchemaValidatorandCfWorkerJsonSchemaValidator@internal; drop their type-only re-exports fromcore/publicso the runtime classes and their TypeScript type handles are both unreachable from@modelcontextprotocol/client/@modelcontextprotocol/server..examples.tsfiles and JSDoc snippets inpackages/core/so they no longer demonstrate end-user code (new AjvJsonSchemaValidator(),new CfWorkerJsonSchemaValidator()) that the new surface makes impossible.fromJsonSchema's example now uses a declaredjsonSchemaValidatorand notes that consumers importing from server/client omit the second arg.ajv,ajv-formats, or@cfworker/json-schemafor consumers to resolve.@cfworker/json-schema) rather than the now-internal class names.Why
Client and server choose runtime defaults via conditional
_shims:@cfworker/json-schema-backed validatorBecause 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. The implementation classes are also no longer part of the public surface — neither as runtime constructors nor as TypeScript types — because exposing a handle to a constructor consumers can't reach is purely confusing.
Impact
jsonSchemaValidator: myCustomValidatorwith their own implementation of thejsonSchemaValidatorinterface.@modelcontextprotocol/core,ajv,ajv-formats, or@cfworker/json-schema; those implementations are bundled where client/server need them.@internal, andcore/publicno longer re-exports them as types. The supported customisation path is thejsonSchemaValidatorinterface (still exported fromcore/publicand re-exported by client/server).Follow-up for codemod reviewers
If this validator packaging change ships, the v1-to-v2 codemod should 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 (not even as types), and normal migrations should rely on automatic runtime defaults instead.
Examples the codemod should simplify/remove when they refer to the SDK built-ins:
For custom user validators, the codemod should preserve the override:
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.