Skip to content

Commit 5f32a90

Browse files
fix(core): make fromJsonSchema() use runtime-aware default validator … (#1825)
Co-authored-by: Felix Weinberger <3823880+felixweinberger@users.noreply.github.com>
1 parent 2fd7f5f commit 5f32a90

File tree

9 files changed

+74
-6
lines changed

9 files changed

+74
-6
lines changed

docs/migration-SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ Zod schemas, all callback return types. Note: `callTool()` and `request()` signa
210210

211211
The variadic `.tool()`, `.prompt()`, `.resource()` methods are removed. Use the `register*` methods with a config object.
212212

213-
**IMPORTANT**: v2 requires schema objects implementing [Standard Schema](https://standardschema.dev/) — raw shapes like `{ name: z.string() }` are no longer supported. Wrap with `z.object()` (Zod v4), or use ArkType's `type({...})`, or Valibot. For raw JSON Schema, wrap with `fromJsonSchema(schema, validator)` from `@modelcontextprotocol/server`. Applies to `inputSchema`, `outputSchema`, and `argsSchema`.
213+
**IMPORTANT**: v2 requires schema objects implementing [Standard Schema](https://standardschema.dev/) — raw shapes like `{ name: z.string() }` are no longer supported. Wrap with `z.object()` (Zod v4), or use ArkType's `type({...})`, or Valibot. For raw JSON Schema, wrap with `fromJsonSchema(schema)` from `@modelcontextprotocol/server` (validator defaults automatically; pass an explicit validator for custom configurations). Applies to `inputSchema`, `outputSchema`, and `argsSchema`.
214214

215215
### Tools
216216

docs/migration.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,10 @@ server.registerTool('greet', {
274274
inputSchema: type({ name: 'string' })
275275
}, async ({ name }) => { ... });
276276

277-
// Raw JSON Schema via fromJsonSchema
278-
import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/server';
277+
// Raw JSON Schema via fromJsonSchema (validator defaults to runtime-appropriate choice)
278+
import { fromJsonSchema } from '@modelcontextprotocol/server';
279279
server.registerTool('greet', {
280-
inputSchema: fromJsonSchema({ type: 'object', properties: { name: { type: 'string' } } }, new AjvJsonSchemaValidator())
280+
inputSchema: fromJsonSchema({ type: 'object', properties: { name: { type: 'string' } } })
281281
}, handler);
282282

283283
// For tools with no parameters, use z.object({})
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/client/_shims';
2+
import type { JsonSchemaType, jsonSchemaValidator, StandardSchemaWithJSON } from '@modelcontextprotocol/core';
3+
import { fromJsonSchema as coreFromJsonSchema } from '@modelcontextprotocol/core';
4+
5+
let _defaultValidator: jsonSchemaValidator | undefined;
6+
7+
export function fromJsonSchema<T = unknown>(schema: JsonSchemaType, validator?: jsonSchemaValidator): StandardSchemaWithJSON<T, T> {
8+
return coreFromJsonSchema<T>(schema, validator ?? (_defaultValidator ??= new DefaultJsonSchemaValidator()));
9+
}

packages/client/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,8 @@ export { StreamableHTTPClientTransport } from './client/streamableHttp.js';
7373
// experimental exports
7474
export { ExperimentalClientTasks } from './experimental/tasks/client.js';
7575

76+
// runtime-aware wrapper (shadows core/public's fromJsonSchema with optional validator)
77+
export { fromJsonSchema } from './fromJsonSchema.js';
78+
7679
// re-export curated public API from core
7780
export * from '@modelcontextprotocol/core/public';

packages/core/src/exports/public/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,6 @@ export type { StandardSchemaWithJSON } from '../../util/standardSchema.js';
138138
export { AjvJsonSchemaValidator } from '../../validators/ajvProvider.js';
139139
export type { CfWorkerSchemaDraft } from '../../validators/cfWorkerProvider.js';
140140
export { CfWorkerJsonSchemaValidator } from '../../validators/cfWorkerProvider.js';
141-
export { fromJsonSchema } from '../../validators/fromJsonSchema.js';
141+
// fromJsonSchema is intentionally NOT exported here — the server and client packages
142+
// provide runtime-aware wrappers that default to the appropriate validator via _shims.
142143
export type { JsonSchemaType, JsonSchemaValidator, jsonSchemaValidator, JsonSchemaValidatorResult } from '../../validators/types.js';

packages/core/src/validators/fromJsonSchema.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import type { JsonSchemaType, jsonSchemaValidator } from './types.js';
1010
* The callback arguments will be typed `unknown` (raw JSON Schema has no TypeScript
1111
* types attached). Cast at the call site, or use the generic `fromJsonSchema<MyType>(...)`.
1212
*
13+
* @param schema - A JSON Schema object describing the expected shape
14+
* @param validator - A validator provider. When importing `fromJsonSchema` from
15+
* `@modelcontextprotocol/server` or `@modelcontextprotocol/client`, a runtime-appropriate
16+
* default is provided automatically (AJV on Node.js, CfWorker on edge runtimes).
17+
*
1318
* @example
1419
* ```ts source="./fromJsonSchema.examples.ts#fromJsonSchema_basicUsage"
1520
* const inputSchema = fromJsonSchema<{ name: string }>(
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { JsonSchemaType, jsonSchemaValidator, StandardSchemaWithJSON } from '@modelcontextprotocol/core';
2+
import { fromJsonSchema as coreFromJsonSchema } from '@modelcontextprotocol/core';
3+
import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';
4+
5+
let _defaultValidator: jsonSchemaValidator | undefined;
6+
7+
export function fromJsonSchema<T = unknown>(schema: JsonSchemaType, validator?: jsonSchemaValidator): StandardSchemaWithJSON<T, T> {
8+
return coreFromJsonSchema<T>(schema, validator ?? (_defaultValidator ??= new DefaultJsonSchemaValidator()));
9+
}

packages/server/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,8 @@ export type { CreateTaskRequestHandler, TaskRequestHandler, ToolTaskHandler } fr
4343
export { ExperimentalMcpServerTasks } from './experimental/tasks/mcpServer.js';
4444
export { ExperimentalServerTasks } from './experimental/tasks/server.js';
4545

46+
// runtime-aware wrapper (shadows core/public's fromJsonSchema with optional validator)
47+
export { fromJsonSchema } from './fromJsonSchema.js';
48+
4649
// re-export curated public API from core
4750
export * from '@modelcontextprotocol/core/public';

test/integration/test/standardSchema.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { Client } from '@modelcontextprotocol/client';
77
import type { TextContent } from '@modelcontextprotocol/core';
88
import { AjvJsonSchemaValidator, fromJsonSchema, InMemoryTransport } from '@modelcontextprotocol/core';
9-
import { completable, McpServer } from '@modelcontextprotocol/server';
9+
import { completable, fromJsonSchema as serverFromJsonSchema, McpServer } from '@modelcontextprotocol/server';
1010
import { toStandardJsonSchema } from '@valibot/to-json-schema';
1111
import { type } from 'arktype';
1212
import * as v from 'valibot';
@@ -428,6 +428,44 @@ describe('Standard Schema Support', () => {
428428
});
429429
});
430430

431+
describe('fromJsonSchema with default validator (server wrapper)', () => {
432+
test('should use runtime-appropriate default validator when none is provided', async () => {
433+
const inputSchema = serverFromJsonSchema<{ name: string }>({
434+
type: 'object',
435+
properties: { name: { type: 'string' } },
436+
required: ['name']
437+
});
438+
439+
mcpServer.registerTool('greet-default', { inputSchema }, async ({ name }) => ({
440+
content: [{ type: 'text', text: `Hello, ${name}!` }]
441+
}));
442+
443+
await connectClientAndServer();
444+
445+
const result = await client.request({ method: 'tools/call', params: { name: 'greet-default', arguments: { name: 'World' } } });
446+
expect((result.content[0] as TextContent).text).toBe('Hello, World!');
447+
});
448+
449+
test('should reject invalid input with default validator', async () => {
450+
const inputSchema = serverFromJsonSchema({ type: 'object', properties: { count: { type: 'number' } }, required: ['count'] });
451+
452+
mcpServer.registerTool('double-default', { inputSchema }, async args => {
453+
const { count } = args as { count: number };
454+
return { content: [{ type: 'text', text: `${count * 2}` }] };
455+
});
456+
457+
await connectClientAndServer();
458+
459+
const result = await client.request({
460+
method: 'tools/call',
461+
params: { name: 'double-default', arguments: { count: 'not a number' } }
462+
});
463+
expect(result.isError).toBe(true);
464+
const errorText = (result.content[0] as TextContent).text;
465+
expect(errorText).toContain('Input validation error');
466+
});
467+
});
468+
431469
describe('Prompt completions with Zod completable', () => {
432470
// Note: completable() is currently Zod-specific
433471
// These tests verify that Zod schemas with completable still work

0 commit comments

Comments
 (0)