Skip to content

Commit a2b954b

Browse files
committed
Bundle automatic validator defaults in client and server shims
1 parent 2c0c481 commit a2b954b

27 files changed

Lines changed: 353 additions & 91 deletions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@modelcontextprotocol/core': minor
3+
'@modelcontextprotocol/client': patch
4+
'@modelcontextprotocol/server': patch
5+
---
6+
7+
Make validator backends symmetrical in core and bundle automatic defaults in client/server runtime shims.
8+
9+
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.
10+
11+
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.

docs/migration-SKILL.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -518,11 +518,11 @@ new McpServer(
518518
new McpServer({ name: 'server', version: '1.0.0' }, {});
519519
```
520520

521-
Access validators explicitly:
521+
Validator behavior:
522522

523-
- Runtime-aware default: `import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';`
524-
- AJV (Node.js): `import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server';`
525-
- CF Worker: `import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/validators/cf-worker';`
523+
- Do not add validator imports for normal migrations.
524+
- Do not install `ajv`, `ajv-formats`, or `@cfworker/json-schema`; client/server bundle the runtime-selected defaults.
525+
- Advanced users may pass `jsonSchemaValidator: myCustomValidator` with their own validator implementation.
526526

527527
## 15. Migration Steps (apply in this order)
528528

docs/migration.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -932,15 +932,18 @@ const server = new McpServer(
932932
);
933933
```
934934

935-
You can still explicitly override the validator if needed:
935+
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.
936936

937-
```typescript
938-
// Runtime-aware default (auto-selects AjvJsonSchemaValidator or CfWorkerJsonSchemaValidator)
939-
import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';
937+
Advanced users can still override validation by passing an object that implements the SDK's JSON Schema validator interface:
940938

941-
// Specific validators
942-
import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server';
943-
import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/validators/cf-worker';
939+
```typescript
940+
const server = new McpServer(
941+
{ name: 'my-server', version: '1.0.0' },
942+
{
943+
capabilities: { tools: {} },
944+
jsonSchemaValidator: myCustomValidator
945+
}
946+
);
944947
```
945948

946949
## Unchanged APIs

packages/client/package.json

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@
2828
"types": "./dist/stdio.d.mts",
2929
"import": "./dist/stdio.mjs"
3030
},
31-
"./validators/cf-worker": {
32-
"types": "./dist/validators/cfWorker.d.mts",
33-
"import": "./dist/validators/cfWorker.mjs"
34-
},
3531
"./_shims": {
3632
"workerd": {
3733
"types": "./dist/shimsWorkerd.d.mts",
@@ -54,9 +50,6 @@
5450
"types": "./dist/index.d.mts",
5551
"typesVersions": {
5652
"*": {
57-
"validators/cf-worker": [
58-
"dist/validators/cfWorker.d.mts"
59-
],
6053
"stdio": [
6154
"dist/stdio.d.mts"
6255
]
@@ -93,6 +86,8 @@
9386
"@modelcontextprotocol/eslint-config": "workspace:^",
9487
"@modelcontextprotocol/test-helpers": "workspace:^",
9588
"@cfworker/json-schema": "catalog:runtimeShared",
89+
"ajv": "catalog:runtimeShared",
90+
"ajv-formats": "catalog:runtimeShared",
9691
"@types/content-type": "catalog:devTools",
9792
"@types/cross-spawn": "catalog:devTools",
9893
"@types/eventsource": "catalog:devTools",

packages/client/src/client/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export type ClientOptions = ProtocolOptions & {
161161
* The validator is used to validate structured content returned by tools
162162
* against their declared output schemas.
163163
*
164-
* @default {@linkcode DefaultJsonSchemaValidator} ({@linkcode index.AjvJsonSchemaValidator | AjvJsonSchemaValidator} on Node.js, `CfWorkerJsonSchemaValidator` on Cloudflare Workers)
164+
* @default Runtime-selected validator (`AjvJsonSchemaValidator` on Node.js, `CfWorkerJsonSchemaValidator` on browser/workerd runtimes)
165165
*/
166166
jsonSchemaValidator?: jsonSchemaValidator;
167167

packages/client/src/shimsNode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* This file is selected via package.json export conditions when running in Node.js.
55
*/
6-
export { AjvJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core';
6+
export { AjvJsonSchemaValidator as DefaultJsonSchemaValidator } from '@modelcontextprotocol/core/validators/ajv';
77

88
/**
99
* Whether `fetch()` may throw `TypeError` due to CORS. CORS is a browser-only concept —

packages/client/src/validators/cfWorker.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

packages/client/test/client/barrelClean.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { beforeAll, describe, expect, test } from 'vitest';
88
const pkgDir = join(dirname(fileURLToPath(import.meta.url)), '../..');
99
const distDir = join(pkgDir, 'dist');
1010
const NODE_ONLY = /\b(child_process|cross-spawn|node:stream|node:child_process)\b/;
11+
const VALIDATOR_BACKEND_IMPORT = /from\s+["'](?:ajv|ajv-formats|@cfworker\/json-schema)["']/;
1112

1213
function chunkImportsOf(entryPath: string): string[] {
1314
const visited = new Set<string>();
@@ -52,4 +53,17 @@ describe('@modelcontextprotocol/client root entry is browser-safe', () => {
5253
expect(stdio).toMatch(/\bgetDefaultEnvironment\b/);
5354
expect(stdio).toMatch(/\bDEFAULT_INHERITED_ENV_VARS\b/);
5455
});
56+
57+
test('runtime shims vendor default validator backends instead of requiring consumers to install them', () => {
58+
for (const shim of ['shimsNode.mjs', 'shimsWorkerd.mjs', 'shimsBrowser.mjs']) {
59+
const entry = join(distDir, shim);
60+
expect(readFileSync(entry, 'utf8')).not.toMatch(VALIDATOR_BACKEND_IMPORT);
61+
62+
for (const chunk of chunkImportsOf(entry)) {
63+
expect({ chunk, content: readFileSync(chunk, 'utf8') }).not.toEqual(
64+
expect.objectContaining({ content: expect.stringMatching(VALIDATOR_BACKEND_IMPORT) })
65+
);
66+
}
67+
}
68+
});
5569
});
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import type { JSONRPCMessage, JsonSchemaType, JsonSchemaValidatorResult, jsonSchemaValidator } from '@modelcontextprotocol/core';
2+
import { InMemoryTransport, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core';
3+
import { Client } from '../../src/client/client.js';
4+
import { fromJsonSchema } from '../../src/fromJsonSchema.js';
5+
6+
class RecordingValidator implements jsonSchemaValidator {
7+
schemas: JsonSchemaType[] = [];
8+
values: unknown[] = [];
9+
10+
getValidator<T>(schema: JsonSchemaType) {
11+
this.schemas.push(schema);
12+
return (value: unknown): JsonSchemaValidatorResult<T> => {
13+
this.values.push(value);
14+
return { valid: true, data: value as T, errorMessage: undefined };
15+
};
16+
}
17+
}
18+
19+
async function connectInitializedClient(client: Client) {
20+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
21+
serverTransport.onmessage = async message => {
22+
if ('method' in message && 'id' in message && message.method === 'initialize') {
23+
await serverTransport.send({
24+
jsonrpc: '2.0',
25+
id: message.id,
26+
result: {
27+
protocolVersion: LATEST_PROTOCOL_VERSION,
28+
capabilities: { tools: {} },
29+
serverInfo: { name: 'test-server', version: '1.0.0' }
30+
}
31+
});
32+
} else if ('method' in message && 'id' in message && message.method === 'tools/list') {
33+
await serverTransport.send({
34+
jsonrpc: '2.0',
35+
id: message.id,
36+
result: {
37+
tools: [
38+
{
39+
name: 'structured-tool',
40+
description: 'A tool with structured output',
41+
inputSchema: { type: 'object' },
42+
outputSchema: {
43+
type: 'object',
44+
properties: { count: { type: 'number' } },
45+
required: ['count']
46+
}
47+
}
48+
]
49+
}
50+
} satisfies JSONRPCMessage);
51+
}
52+
};
53+
54+
await Promise.all([client.connect(clientTransport), serverTransport.start()]);
55+
return { clientTransport, serverTransport };
56+
}
57+
58+
describe('client JSON Schema validator overrides', () => {
59+
test('Client constructor uses a custom validator for tool output schema caching', async () => {
60+
const validator = new RecordingValidator();
61+
const client = new Client(
62+
{ name: 'test-client', version: '1.0.0' },
63+
{
64+
capabilities: {},
65+
jsonSchemaValidator: validator
66+
}
67+
);
68+
const { clientTransport, serverTransport } = await connectInitializedClient(client);
69+
70+
await expect(client.listTools()).resolves.toMatchObject({
71+
tools: [
72+
{
73+
name: 'structured-tool',
74+
outputSchema: {
75+
type: 'object',
76+
properties: { count: { type: 'number' } },
77+
required: ['count']
78+
}
79+
}
80+
]
81+
});
82+
83+
expect(validator.schemas).toEqual([
84+
{
85+
type: 'object',
86+
properties: { count: { type: 'number' } },
87+
required: ['count']
88+
}
89+
]);
90+
91+
await client.close();
92+
await clientTransport.close();
93+
await serverTransport.close();
94+
});
95+
96+
test('fromJsonSchema uses an explicitly supplied custom validator', async () => {
97+
const validator = new RecordingValidator();
98+
const schema: JsonSchemaType = {
99+
type: 'object',
100+
properties: { name: { type: 'string' } },
101+
required: ['name']
102+
};
103+
104+
const standardSchema = fromJsonSchema<{ name: string }>(schema, validator);
105+
expect(standardSchema['~standard'].validate({ name: 123 })).toEqual({ value: { name: 123 } });
106+
107+
expect(validator.schemas).toEqual([schema]);
108+
expect(validator.values).toEqual([{ name: 123 }]);
109+
});
110+
});

packages/client/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"*": ["./*"],
88
"@modelcontextprotocol/core": ["./node_modules/@modelcontextprotocol/core/src/index.ts"],
99
"@modelcontextprotocol/core/public": ["./node_modules/@modelcontextprotocol/core/src/exports/public/index.ts"],
10+
"@modelcontextprotocol/core/validators/ajv": ["./node_modules/@modelcontextprotocol/core/src/validators/ajvProvider.ts"],
1011
"@modelcontextprotocol/core/validators/cfWorker": [
1112
"./node_modules/@modelcontextprotocol/core/src/validators/cfWorkerProvider.ts"
1213
],

0 commit comments

Comments
 (0)