feat(client,server): bundle default validators, expose customisation via subpaths (#2088)

This commit is contained in:
Matt 2026-05-29 19:39:32 +01:00 • committed by GitHub
commit 16d13abf78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 749 additions and 314 deletions

View file

@ -3,4 +3,4 @@
'@modelcontextprotocol/client': patch
---
Stop bundling `@cfworker/json-schema` into the main package barrel. Previously `CfWorkerJsonSchemaValidator` was re-exported from the core internal barrel, so tsdown inlined the `@cfworker/json-schema` dev dependency into every consumer's bundle even when it was never used. 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.
Stop bundling `@cfworker/json-schema` into the main package barrel. Previously `CfWorkerJsonSchemaValidator` was re-exported from the core internal barrel, so tsdown inlined the `@cfworker/json-schema` dependency into every consumer's bundle even when it was never used. The named validator classes are now reachable only via the explicit `@modelcontextprotocol/{client,server}/validators/{ajv,cf-worker}` subpaths and the runtime `_shims` conditional, so consumers that import only from the root entry point no longer ship the validator dep.

View file

@ -21,10 +21,10 @@ server.registerTool('greet', {
For raw JSON Schema (e.g. TypeBox output), use the new `fromJsonSchema` adapter:
```typescript
import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core';
import { fromJsonSchema } from '@modelcontextprotocol/server';
server.registerTool('greet', {
inputSchema: fromJsonSchema({ type: 'object', properties: { name: { type: 'string' } } }, new AjvJsonSchemaValidator())
inputSchema: fromJsonSchema({ type: 'object', properties: { name: { type: 'string' } } })
}, handler);
```

View file

@ -0,0 +1,18 @@
---
'@modelcontextprotocol/core': minor
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
---
Bundle automatic JSON Schema validator defaults in `@modelcontextprotocol/client` and `@modelcontextprotocol/server` runtime shims.
Client and server pick the right validator automatically based on the runtime: the Node shim uses AJV, the browser/workerd shim uses `@cfworker/json-schema`. Both backends are bundled into the shim chunks that select them, so the default code path needs no extra installs — `import { McpServer } from '@modelcontextprotocol/server'` does not pull `ajv` or `@cfworker/json-schema` into the root entry chunk.
The named validator classes remain part of the public surface for consumers who want to customize the built-in backend (pre-register schemas by `$id`, register custom AJV formats, switch dialects, change `@cfworker/json-schema` draft). They are exposed through explicit subpaths so they do not bloat the root index chunk:
- `import { AjvJsonSchemaValidator } from '@modelcontextprotocol/{client,server}/validators/ajv'`
- `import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/{client,server}/validators/cf-worker'`
Importing from one of these subpaths means the corresponding peer dep (`ajv` + `ajv-formats`, or `@cfworker/json-schema`) must be in your `package.json`. The shim keeps its own vendored copy for the default path, so a project can use the subpath in some files and rely on the default in others.
The `jsonSchemaValidator` interface remains the public extension point for replacing validation entirely with a custom implementation.

View file

@ -509,8 +509,8 @@ Type changes in handler context:
The SDK now auto-selects the appropriate JSON Schema validator based on runtime:
- Node.js → `AjvJsonSchemaValidator` (no change from v1)
- Cloudflare Workers (workerd) → `CfWorkerJsonSchemaValidator` (previously required manual config)
- Node.js → AJV (no change from v1)
- Cloudflare Workers (workerd) → `@cfworker/json-schema` (previously required manual config)
**No action required** for most users. Cloudflare Workers users can remove explicit `jsonSchemaValidator` configuration:
@ -527,11 +527,12 @@ new McpServer(
new McpServer({ name: 'server', version: '1.0.0' }, {});
```
Access validators explicitly:
Validator behavior:
- Runtime-aware default: `import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';`
- 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` for the default path; client/server bundle the runtime-selected defaults and the root entry point does not pull either dep in.
- To customize the built-in backend (e.g. register custom AJV formats, change `@cfworker/json-schema` draft), import the named class from the package subpath: `@modelcontextprotocol/{client,server}/validators/ajv` for `AjvJsonSchemaValidator`, `@modelcontextprotocol/{client,server}/validators/cf-worker` for `CfWorkerJsonSchemaValidator`. Importing from a subpath means the corresponding peer dep must be in your `package.json`.
- To replace validation entirely, pass `jsonSchemaValidator: myCustomValidator` with your own implementation of the `jsonSchemaValidator` interface.
## 15. Migration Steps (apply in this order)

View file

@ -901,8 +901,8 @@ server.setRequestHandler('tools/call', async (request, ctx) => {
The SDK now automatically selects the appropriate JSON Schema validator based on your runtime environment:
- **Node.js**: Uses `AjvJsonSchemaValidator` (same as v1 default)
- **Cloudflare Workers**: Uses `CfWorkerJsonSchemaValidator` (previously required manual configuration)
- **Node.js**: Uses AJV (same as v1 default)
- **Cloudflare Workers**: Uses `@cfworker/json-schema` (previously required manual configuration)
This means Cloudflare Workers users no longer need to explicitly pass the validator:
@ -933,17 +933,45 @@ const server = new McpServer(
);
```
You can still explicitly override the validator if needed:
You do not need to install or import validator packages for the default behavior. The client and server packages bundle the validator backend selected by the runtime shim, so a normal `import { McpServer } from '@modelcontextprotocol/server'` does not pull `ajv` or `@cfworker/json-schema` into your bundle until you choose to customize.
If you want to customize the **built-in** backend (for example, pre-register schemas by `$id`, register custom AJV formats, or change the `@cfworker/json-schema` draft), import the named class from the explicit subpath and pass an instance through `jsonSchemaValidator`:
```typescript
// Runtime-aware default (auto-selects AjvJsonSchemaValidator or CfWorkerJsonSchemaValidator)
import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';
import { Ajv } from 'ajv';
import addFormats from 'ajv-formats';
import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server/validators/ajv';
// Specific validators
import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server';
import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/validators/cf-worker';
const ajv = new Ajv({ strict: true, allErrors: true });
addFormats(ajv);
const server = new McpServer(
{ name: 'my-server', version: '1.0.0' },
{
capabilities: { tools: {} },
jsonSchemaValidator: new AjvJsonSchemaValidator(ajv)
}
);
```
```typescript
import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/validators/cf-worker';
const server = new McpServer(
{ name: 'my-server', version: '1.0.0' },
{
capabilities: { tools: {} },
jsonSchemaValidator: new CfWorkerJsonSchemaValidator({ draft: '2020-12', shortcircuit: false })
}
);
```
(both subpaths are also available on `@modelcontextprotocol/client/validators/...`)
If you import from one of these subpaths in your own code, the corresponding peer dep (`ajv` + `ajv-formats`, or `@cfworker/json-schema`) needs to be installed in your `package.json`. The runtime shim continues to vendor a copy for the default code path, so you can use the subpath in some files and rely on the default in others.
To replace validation wholesale rather than customizing the built-in classes, implement the `jsonSchemaValidator` interface and pass your own implementation through the option above.
## Unchanged APIs
The following APIs are unchanged between v1 and v2 (only the import paths changed):

View file

@ -28,6 +28,10 @@
"types": "./dist/stdio.d.mts",
"import": "./dist/stdio.mjs"
},
"./validators/ajv": {
"types": "./dist/validators/ajv.d.mts",
"import": "./dist/validators/ajv.mjs"
},
"./validators/cf-worker": {
"types": "./dist/validators/cfWorker.d.mts",
"import": "./dist/validators/cfWorker.mjs"
@ -54,6 +58,9 @@
"types": "./dist/index.d.mts",
"typesVersions": {
"*": {
"validators/ajv": [
"dist/validators/ajv.d.mts"
],
"validators/cf-worker": [
"dist/validators/cfWorker.d.mts"
],
@ -93,6 +100,8 @@
"@modelcontextprotocol/eslint-config": "workspace:^",
"@modelcontextprotocol/test-helpers": "workspace:^",
"@cfworker/json-schema": "catalog:runtimeShared",
"ajv": "catalog:runtimeShared",
"ajv-formats": "catalog:runtimeShared",
"@types/content-type": "catalog:devTools",
"@types/cross-spawn": "catalog:devTools",
"@types/eventsource": "catalog:devTools",

View file

@ -161,7 +161,7 @@ export type ClientOptions = ProtocolOptions & {
* The validator is used to validate structured content returned by tools
* against their declared output schemas.
*
* @default {@linkcode DefaultJsonSchemaValidator} ({@linkcode index.AjvJsonSchemaValidator | AjvJsonSchemaValidator} on Node.js, `CfWorkerJsonSchemaValidator` on Cloudflare Workers)
* @default Runtime-selected validator (AJV-backed on Node.js, `@cfworker/json-schema`-backed on browser/workerd runtimes)
*/
jsonSchemaValidator?: jsonSchemaValidator;

View file

@ -3,7 +3,7 @@
*
* This file is selected via package.json export conditions when running in Node.js.
*/
export { AjvJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core';
export { AjvJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core/validators/ajv';
/**
* Whether `fetch()` may throw `TypeError` due to CORS. CORS is a browser-only concept —

View file

@ -0,0 +1,14 @@
/**
* Customisation entry point for the AJV validator. Re-exports `Ajv` + `addFormats` from the
* SDK's bundled copy, so customising the validator needs no extra installs.
*
* @example
* ```ts
* import { Ajv, addFormats, AjvJsonSchemaValidator } from '@modelcontextprotocol/client/validators/ajv';
*
* const ajv = new Ajv({ strict: true, allErrors: true });
* addFormats(ajv);
* const validator = new AjvJsonSchemaValidator(ajv);
* ```
*/
export { addFormats, Ajv, AjvJsonSchemaValidator } from '@modelcontextprotocol/core/validators/ajv';

View file

@ -1,10 +1,3 @@
/**
* Cloudflare Workers JSON Schema validator, available as a sub-path export.
*
* @example
* ```ts
* import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/client/validators/cf-worker';
* ```
*/
/** Customisation entry point for the `@cfworker/json-schema` validator. */
export type { CfWorkerSchemaDraft } from '@modelcontextprotocol/core/validators/cfWorker';
export { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/core/validators/cfWorker';

View file

@ -8,6 +8,8 @@ import { beforeAll, describe, expect, test } from 'vitest';
const pkgDir = join(dirname(fileURLToPath(import.meta.url)), '../..');
const distDir = join(pkgDir, 'dist');
const NODE_ONLY = /\b(child_process|cross-spawn|node:stream|node:child_process)\b/;
// Anchored at start-of-line so JSDoc-example `from 'ajv'` strings in vendored chunks don't match.
const VALIDATOR_BACKEND_IMPORT = /^import[^\n]*?from\s+["'](?:ajv|ajv-formats|@cfworker\/json-schema)["']/m;
function chunkImportsOf(entryPath: string): string[] {
const visited = new Set<string>();
@ -52,4 +54,17 @@ describe('@modelcontextprotocol/client root entry is browser-safe', () => {
expect(stdio).toMatch(/\bgetDefaultEnvironment\b/);
expect(stdio).toMatch(/\bDEFAULT_INHERITED_ENV_VARS\b/);
});
test('runtime shims vendor default validator backends instead of requiring consumers to install them', () => {
for (const shim of ['shimsNode.mjs', 'shimsWorkerd.mjs', 'shimsBrowser.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) })
);
}
}
});
});

View file

@ -0,0 +1,110 @@
import type { JSONRPCMessage, JsonSchemaType, JsonSchemaValidatorResult, jsonSchemaValidator } from '@modelcontextprotocol/core';
import { InMemoryTransport, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core';
import { Client } from '../../src/client/client.js';
import { fromJsonSchema } from '../../src/fromJsonSchema.js';
class RecordingValidator implements jsonSchemaValidator {
schemas: JsonSchemaType[] = [];
values: unknown[] = [];
getValidator<T>(schema: JsonSchemaType) {
this.schemas.push(schema);
return (value: unknown): JsonSchemaValidatorResult<T> => {
this.values.push(value);
return { valid: true, data: value as T, errorMessage: undefined };
};
}
}
async function connectInitializedClient(client: Client) {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
serverTransport.onmessage = async message => {
if ('method' in message && 'id' in message && message.method === 'initialize') {
await serverTransport.send({
jsonrpc: '2.0',
id: message.id,
result: {
protocolVersion: LATEST_PROTOCOL_VERSION,
capabilities: { tools: {} },
serverInfo: { name: 'test-server', version: '1.0.0' }
}
});
} else if ('method' in message && 'id' in message && message.method === 'tools/list') {
await serverTransport.send({
jsonrpc: '2.0',
id: message.id,
result: {
tools: [
{
name: 'structured-tool',
description: 'A tool with structured output',
inputSchema: { type: 'object' },
outputSchema: {
type: 'object',
properties: { count: { type: 'number' } },
required: ['count']
}
}
]
}
} satisfies JSONRPCMessage);
}
};
await Promise.all([client.connect(clientTransport), serverTransport.start()]);
return { clientTransport, serverTransport };
}
describe('client JSON Schema validator overrides', () => {
test('Client constructor uses a custom validator for tool output schema caching', async () => {
const validator = new RecordingValidator();
const client = new Client(
{ name: 'test-client', version: '1.0.0' },
{
capabilities: {},
jsonSchemaValidator: validator
}
);
const { clientTransport, serverTransport } = await connectInitializedClient(client);
await expect(client.listTools()).resolves.toMatchObject({
tools: [
{
name: 'structured-tool',
outputSchema: {
type: 'object',
properties: { count: { type: 'number' } },
required: ['count']
}
}
]
});
expect(validator.schemas).toEqual([
{
type: 'object',
properties: { count: { type: 'number' } },
required: ['count']
}
]);
await client.close();
await clientTransport.close();
await serverTransport.close();
});
test('fromJsonSchema uses an explicitly supplied custom validator', async () => {
const validator = new RecordingValidator();
const schema: JsonSchemaType = {
type: 'object',
properties: { name: { type: 'string' } },
required: ['name']
};
const standardSchema = fromJsonSchema<{ name: string }>(schema, validator);
expect(standardSchema['~standard'].validate({ name: 123 })).toEqual({ value: { name: 123 } });
expect(validator.schemas).toEqual([schema]);
expect(validator.values).toEqual([{ name: 123 }]);
});
});

View file

@ -7,6 +7,7 @@
"*": ["./*"],
"@modelcontextprotocol/core": ["./node_modules/@modelcontextprotocol/core/src/index.ts"],
"@modelcontextprotocol/core/public": ["./node_modules/@modelcontextprotocol/core/src/exports/public/index.ts"],
"@modelcontextprotocol/core/validators/ajv": ["./node_modules/@modelcontextprotocol/core/src/validators/ajvProvider.ts"],
"@modelcontextprotocol/core/validators/cfWorker": [
"./node_modules/@modelcontextprotocol/core/src/validators/cfWorkerProvider.ts"
],

View file

@ -2,39 +2,35 @@ import { defineConfig } from 'tsdown';
export default defineConfig({
failOnWarn: 'ci-only',
// 1. Entry Points
// Directly matches package.json include/exclude globs
entry: ['src/index.ts', 'src/stdio.ts', 'src/shimsNode.ts', 'src/shimsWorkerd.ts', 'src/shimsBrowser.ts', 'src/validators/cfWorker.ts'],
// 2. Output Configuration
entry: [
'src/index.ts',
'src/stdio.ts',
'src/shimsNode.ts',
'src/shimsWorkerd.ts',
'src/shimsBrowser.ts',
'src/validators/ajv.ts',
'src/validators/cfWorker.ts'
],
format: ['esm'],
outDir: 'dist',
clean: true, // Recommended: Cleans 'dist' before building
clean: true,
sourcemap: true,
// 3. Platform & Target
target: 'esnext',
platform: 'node',
shims: true, // Polyfills common Node.js shims (__dirname, etc.)
// 4. Type Definitions
// Bundles d.ts files into a single output
shims: true,
dts: {
resolver: 'tsc',
// override just for DTS generation:
resolve: ['ajv', 'ajv-formats'],
compilerOptions: {
baseUrl: '.',
paths: {
'@modelcontextprotocol/core': ['../core/src/index.ts'],
'@modelcontextprotocol/core/public': ['../core/src/exports/public/index.ts'],
'@modelcontextprotocol/core/validators/ajv': ['../core/src/validators/ajvProvider.ts'],
'@modelcontextprotocol/core/validators/cfWorker': ['../core/src/validators/cfWorkerProvider.ts']
}
}
},
// 5. Vendoring Strategy - Bundle the code for this specific package into the output,
// but treat all other dependencies as external (require/import).
noExternal: ['@modelcontextprotocol/core'],
// 6. External packages - keep self-reference imports external for runtime resolution
noExternal: ['@modelcontextprotocol/core', 'ajv', 'ajv-formats', '@cfworker/json-schema'],
external: ['@modelcontextprotocol/client/_shims']
});

View file

@ -7,6 +7,11 @@ export interface ImportMapping {
removalMessage?: string;
/** No entries currently set this; scaffolding for when a v1 symbol has no v2 equivalent yet. */
isV2Gap?: boolean;
/**
* Subpath suffix appended after `RESOLVE_BY_CONTEXT` resolves the base package (e.g. `/validators/ajv`).
* The final target becomes `@modelcontextprotocol/{client,server}<subpathSuffix>`.
*/
subpathSuffix?: string;
}
export const IMPORT_MAP: Record<string, ImportMapping> = {
@ -163,6 +168,27 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
}
};
// v1 `validation/*` paths → v2 `validators/*` subpaths. The canonical `*-provider.js` filename and
// the short aliases from the v1 README, with and without `.js` suffix.
const VALIDATOR_V1_VARIANTS: Record<string, readonly string[]> = {
'/validators/ajv': ['validation/ajv-provider.js', 'validation/ajv.js', 'validation/ajv'],
'/validators/cf-worker': ['validation/cfworker-provider.js', 'validation/cfworker.js', 'validation/cfworker']
};
for (const [subpathSuffix, v1Specifiers] of Object.entries(VALIDATOR_V1_VARIANTS)) {
for (const v1Specifier of v1Specifiers) {
IMPORT_MAP[`@modelcontextprotocol/sdk/${v1Specifier}`] = {
target: 'RESOLVE_BY_CONTEXT',
status: 'moved',
subpathSuffix
};
}
}
// `validation/index` / `validation/types` carry only the `jsonSchemaValidator` interface + helpers.
for (const barrelSpecifier of ['@modelcontextprotocol/sdk/validation/index.js', '@modelcontextprotocol/sdk/validation/types.js']) {
IMPORT_MAP[barrelSpecifier] = { target: 'RESOLVE_BY_CONTEXT', status: 'moved' };
}
export function isAuthImport(specifier: string): boolean {
return specifier.includes('/server/auth/') || specifier.includes('/server/auth.');
}

View file

@ -102,6 +102,9 @@ export const importPathsTransform: Transform = {
line,
diagnostics
});
if (mapping.subpathSuffix) {
targetPackage = `${targetPackage}${mapping.subpathSuffix}`;
}
}
const symbolsToRenameInFile: Array<[string, string]> = [];
@ -250,6 +253,9 @@ function rewriteExportDeclarations(
return spec.includes('/server/') || spec === '@modelcontextprotocol/server';
});
targetPackage = resolveTypesPackage(context, hasClientImport, hasServerImport);
if (mapping.subpathSuffix) {
targetPackage = `${targetPackage}${mapping.subpathSuffix}`;
}
}
if (mapping.symbolTargetOverrides) {

View file

@ -64,6 +64,9 @@ function resolveTarget(
return s.includes('/server/') || s === '@modelcontextprotocol/server';
});
target = resolveTypesPackage(context, hasClient, hasServer, diagnosticSink);
if (mapping.subpathSuffix) {
target = `${target}${mapping.subpathSuffix}`;
}
}
return { target, renamedSymbols: mapping.renamedSymbols, symbolTargetOverrides: mapping.symbolTargetOverrides };

View file

@ -448,4 +448,85 @@ describe('import-paths transform', () => {
expect(result).toContain(`from "@modelcontextprotocol/client"`);
expect(result).toContain('InMemoryTransport');
});
describe('validator subpath rewrites', () => {
it('rewrites CfWorkerJsonSchemaValidator from v1 cfworker-provider to client subpath', () => {
const input = [
`import { Client } from '@modelcontextprotocol/sdk/client/index.js';`,
`import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/cfworker-provider.js';`,
''
].join('\n');
const result = applyTransform(input, { projectType: 'client' });
expect(result).toContain(`from "@modelcontextprotocol/client/validators/cf-worker"`);
expect(result).toContain('CfWorkerJsonSchemaValidator');
expect(result).not.toContain('@modelcontextprotocol/sdk');
});
it('rewrites CfWorkerJsonSchemaValidator from v1 cfworker short alias to server subpath', () => {
const input = [
`import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';`,
`import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/cfworker';`,
''
].join('\n');
const result = applyTransform(input, { projectType: 'server' });
expect(result).toContain(`from "@modelcontextprotocol/server/validators/cf-worker"`);
expect(result).toContain('CfWorkerJsonSchemaValidator');
});
it('rewrites AjvJsonSchemaValidator from v1 ajv-provider to server subpath', () => {
const input = [
`import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';`,
`import { AjvJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/ajv-provider.js';`,
''
].join('\n');
const result = applyTransform(input, { projectType: 'server' });
expect(result).toContain(`from "@modelcontextprotocol/server/validators/ajv"`);
expect(result).toContain('AjvJsonSchemaValidator');
});
it('rewrites AjvJsonSchemaValidator from v1 ajv short alias to server subpath', () => {
const input = [
`import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';`,
`import { AjvJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/ajv';`,
''
].join('\n');
const result = applyTransform(input, { projectType: 'server' });
expect(result).toContain(`from "@modelcontextprotocol/server/validators/ajv"`);
expect(result).toContain('AjvJsonSchemaValidator');
});
it('routes validator subpath to client when only client siblings exist', () => {
const input = [
`import { Client } from '@modelcontextprotocol/sdk/client/index.js';`,
`import { AjvJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/ajv-provider.js';`,
''
].join('\n');
const result = applyTransform(input, { projectType: 'both' });
expect(result).toContain(`from "@modelcontextprotocol/client/validators/ajv"`);
});
it('routes validator subpath via project type when no SDK siblings', () => {
const input = `import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/cfworker-provider.js';\n`;
const result = applyTransform(input, { projectType: 'server' });
expect(result).toContain(`from "@modelcontextprotocol/server/validators/cf-worker"`);
});
it('includes the validator subpath in usedPackages', () => {
const project = new Project({ useInMemoryFileSystem: true });
const sourceFile = project.createSourceFile(
'test.ts',
`import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/cfworker-provider.js';\n`
);
const result = importPathsTransform.apply(sourceFile, { projectType: 'client' });
expect(result.usedPackages).toBeDefined();
expect(result.usedPackages!.has('@modelcontextprotocol/client/validators/cf-worker')).toBe(true);
});
it('rewrites the validation/index type-only import to the resolved base package', () => {
const input = `import type { jsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/index.js';\n`;
const result = applyTransform(input, { projectType: 'server' });
expect(result).toContain(`from "@modelcontextprotocol/server"`);
expect(result).toContain('jsonSchemaValidator');
});
});
});

View file

@ -315,4 +315,51 @@ describe('mock-paths transform', () => {
expect(result).toContain('new MyError(');
});
});
describe('validator subpath rewrites', () => {
it('rewrites vi.mock of validator provider to the subpath', () => {
const input = [
`vi.mock('@modelcontextprotocol/sdk/validation/cfworker-provider.js', () => ({`,
` CfWorkerJsonSchemaValidator: vi.fn()`,
`}));`,
''
].join('\n');
const result = applyTransform(input);
expect(result).toContain(`'@modelcontextprotocol/server/validators/cf-worker'`);
expect(result).not.toContain('@modelcontextprotocol/sdk');
});
it('rewrites vi.doMock of ajv provider with sibling client import', () => {
const input = [
`import { Client } from '@modelcontextprotocol/sdk/client/index.js';`,
`vi.doMock('@modelcontextprotocol/sdk/validation/ajv-provider.js', () => ({`,
` AjvJsonSchemaValidator: vi.fn()`,
`}));`,
''
].join('\n');
const result = applyTransform(input, { projectType: 'both' });
expect(result).toContain(`'@modelcontextprotocol/client/validators/ajv'`);
});
it('rewrites dynamic import of validator provider to the subpath', () => {
const input = [
`const { AjvJsonSchemaValidator } = await import('@modelcontextprotocol/sdk/validation/ajv-provider.js');`,
''
].join('\n');
const result = applyTransform(input);
expect(result).toContain(`'@modelcontextprotocol/server/validators/ajv'`);
expect(result).toContain('AjvJsonSchemaValidator');
});
it('rewrites jest.mock of validator short alias to the subpath', () => {
const input = [
`jest.mock('@modelcontextprotocol/sdk/validation/cfworker', () => ({`,
` CfWorkerJsonSchemaValidator: jest.fn()`,
`}));`,
''
].join('\n');
const result = applyTransform(input);
expect(result).toContain(`'@modelcontextprotocol/server/validators/cf-worker'`);
});
});
});

View file

@ -33,6 +33,10 @@
"types": "./src/exports/public/index.ts",
"import": "./src/exports/public/index.ts"
},
"./validators/ajv": {
"types": "./src/validators/ajvProvider.ts",
"import": "./src/validators/ajvProvider.ts"
},
"./validators/cfWorker": {
"types": "./src/validators/cfWorkerProvider.ts",
"import": "./src/validators/cfWorkerProvider.ts"
@ -49,19 +53,25 @@
"client": "tsx scripts/cli.ts client"
},
"dependencies": {
"ajv": "catalog:runtimeShared",
"ajv-formats": "catalog:runtimeShared",
"json-schema-typed": "catalog:runtimeShared",
"zod": "catalog:runtimeShared"
},
"peerDependencies": {
"@cfworker/json-schema": "catalog:runtimeShared",
"ajv": "catalog:runtimeShared",
"ajv-formats": "catalog:runtimeShared",
"zod": "catalog:runtimeShared"
},
"peerDependenciesMeta": {
"@cfworker/json-schema": {
"optional": true
},
"ajv": {
"optional": true
},
"ajv-formats": {
"optional": true
},
"zod": {
"optional": false
}
@ -71,6 +81,8 @@
"@modelcontextprotocol/vitest-config": "workspace:^",
"@modelcontextprotocol/eslint-config": "workspace:^",
"@cfworker/json-schema": "catalog:runtimeShared",
"ajv": "catalog:runtimeShared",
"ajv-formats": "catalog:runtimeShared",
"@eslint/js": "catalog:devTools",
"@types/content-type": "catalog:devTools",
"@types/cors": "catalog:devTools",

Some files were not shown because too many files have changed in this diff Show more