Skip to content

Commit 27e2c4b

Browse files
fix(compat): pass StandardSchema without ~standard.jsonSchema through normalizeRawShapeSchema
The merge from main brought in the zod 4.0-4.1 fallback in standardSchemaToJsonSchema, but normalizeRawShapeSchema's guard (tightened to isStandardSchemaWithJSON before that fallback existed on main) threw at registration time for exactly the schemas the fallback handles, making it unreachable from registerTool/registerPrompt. Relax the guard to isStandardSchema so any valid StandardSchema passes through; standardSchemaToJsonSchema already owns per-vendor handling for schemas without ~standard.jsonSchema (zod 4.0-4.1 fallback, zod 3 error, non-zod error). This keeps a single source of truth for what is convertible.
1 parent 9b7ee90 commit 27e2c4b

3 files changed

Lines changed: 26 additions & 6 deletions

File tree

packages/core/src/util/zodCompat.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import * as z from 'zod/v4';
88

99
import type { StandardSchemaWithJSON } from './standardSchema.js';
10-
import { isStandardSchema, isStandardSchemaWithJSON } from './standardSchema.js';
10+
import { isStandardSchema } from './standardSchema.js';
1111

1212
function isZodV4Schema(v: unknown): v is z.ZodType {
1313
// `_zod` is the v4 internal namespace property. Zod v3 schemas have `_def`
@@ -67,10 +67,14 @@ export function normalizeRawShapeSchema(
6767
'Raw-shape inputSchema/outputSchema/argsSchema fields must be Zod v4 schemas. Got a Zod v3 field schema. Import from `zod/v4` (or upgrade your zod import), or wrap with `z.object({...})` yourself.'
6868
);
6969
}
70-
if (!isStandardSchemaWithJSON(schema)) {
70+
if (!isStandardSchema(schema)) {
7171
throw new TypeError(
72-
'inputSchema/outputSchema/argsSchema must be a Standard Schema with JSON Schema export (`~standard.jsonSchema`, e.g. z.object({...}) from zod >=4.2.0) or a raw Zod shape ({ field: z.string() }).'
72+
'inputSchema/outputSchema/argsSchema must be a Standard Schema (e.g. z.object({...})) or a raw Zod shape ({ field: z.string() }).'
7373
);
7474
}
75+
// Any StandardSchema passes through; standardSchemaToJsonSchema owns the per-vendor
76+
// handling for schemas without `~standard.jsonSchema` (zod 4.0-4.1 fallback, zod 3
77+
// and non-zod errors). Gating on `~standard.jsonSchema` here would unreachably
78+
// front-run that fallback.
7579
return schema;
7680
}

packages/core/test/util/zodCompat.test.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { vi } from 'vitest';
12
import * as z from 'zod/v4';
23

34
import { standardSchemaToJsonSchema } from '../../src/util/standardSchema.js';
@@ -60,9 +61,24 @@ describe('normalizeRawShapeSchema', () => {
6061
test('throws TypeError for an invalid object that is neither raw shape nor Standard Schema', () => {
6162
expect(() => normalizeRawShapeSchema({ a: 'not a zod schema' } as never)).toThrow(TypeError);
6263
});
63-
test('throws TypeError for a Standard Schema without JSON Schema export', () => {
64+
test('passes through a Standard Schema without `~standard.jsonSchema` (per-vendor handling deferred to standardSchemaToJsonSchema)', () => {
6465
const noJson = { '~standard': { version: 1, vendor: 'x', validate: () => ({ value: {} }) } };
65-
expect(() => normalizeRawShapeSchema(noJson as never)).toThrow(/~standard\.jsonSchema/);
66+
expect(normalizeRawShapeSchema(noJson as never)).toBe(noJson);
67+
});
68+
test('passes through a zod 4.0-4.1 schema so standardSchemaToJsonSchema can apply its z.toJSONSchema fallback', () => {
69+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
70+
const real = z.object({ a: z.string() });
71+
// Simulate zod 4.0-4.1: shadow `~standard` with `jsonSchema` removed, keep `_zod` intact.
72+
const { jsonSchema: _drop, ...stdNoJson } = real['~standard'] as unknown as Record<string, unknown>;
73+
void _drop;
74+
Object.defineProperty(real, '~standard', { value: { ...stdNoJson, vendor: 'zod' }, configurable: true });
75+
76+
const normalized = normalizeRawShapeSchema(real);
77+
expect(normalized).toBe(real);
78+
const json = standardSchemaToJsonSchema(normalized!, 'input');
79+
expect(json.type).toBe('object');
80+
expect((json.properties as Record<string, unknown>)?.a).toBeDefined();
81+
warn.mockRestore();
6682
});
6783
test('throws actionable TypeError for a raw shape with Zod v3 fields', () => {
6884
expect(() => normalizeRawShapeSchema({ a: mockZodV3String() } as never)).toThrow(/Zod v4 schemas.*Got a Zod v3 field schema/);

packages/server/src/server/mcp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ import {
3939
validateAndWarnToolName,
4040
validateStandardSchema
4141
} from '@modelcontextprotocol/core';
42-
4342
import type * as z from 'zod/v4';
43+
4444
import type { ToolTaskHandler } from '../experimental/tasks/interfaces.js';
4545
import { ExperimentalMcpServerTasks } from '../experimental/tasks/mcpServer.js';
4646
import { getCompleter, isCompletable } from './completable.js';

0 commit comments

Comments
 (0)