From 0c8fbdb9bedcaee057263dec578cdea5df192a8a Mon Sep 17 00:00:00 2001 From: Travis Bonnet Date: Mon, 16 Mar 2026 23:18:48 -0500 Subject: [PATCH] fix(core): widen requestedSchema type to accept Zod toJSONSchema() output Zod v4's `.toJSONSchema()` produces standard JSON Schema that includes `$schema` and `additionalProperties` fields. The `requestedSchema` type in `ElicitRequestFormParams` rejected these valid fields at the TypeScript level, even though they work correctly at runtime. Changes: - Add `additionalProperties?: boolean` to requestedSchema in spec.types.ts - Add index signature `[key: string]: unknown` for forward compatibility with other standard JSON Schema fields - Add `.passthrough()` to the Zod schema in types.ts so extra fields survive runtime validation - Add regression test verifying Zod toJSONSchema() output works with elicitInput() Closes #1362 --- packages/core/src/types/spec.types.ts | 2 + packages/core/src/types/types.ts | 13 ++- ...t1362.zod.toJSONSchema.elicitation.test.ts | 102 ++++++++++++++++++ 3 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 test/integration/test/issues/test1362.zod.toJSONSchema.elicitation.test.ts diff --git a/packages/core/src/types/spec.types.ts b/packages/core/src/types/spec.types.ts index f36434bef..99e63cb5c 100644 --- a/packages/core/src/types/spec.types.ts +++ b/packages/core/src/types/spec.types.ts @@ -2801,6 +2801,8 @@ export interface ElicitRequestFormParams extends TaskAugmentedRequestParams { [key: string]: PrimitiveSchemaDefinition; }; required?: string[]; + additionalProperties?: boolean; + [key: string]: unknown; }; } diff --git a/packages/core/src/types/types.ts b/packages/core/src/types/types.ts index 6ac79777b..3c399d955 100644 --- a/packages/core/src/types/types.ts +++ b/packages/core/src/types/types.ts @@ -2024,11 +2024,14 @@ export const ElicitRequestFormParamsSchema = TaskAugmentedRequestParamsSchema.ex * A restricted subset of JSON Schema. * Only top-level properties are allowed, without nesting. */ - requestedSchema: z.object({ - type: z.literal('object'), - properties: z.record(z.string(), PrimitiveSchemaDefinitionSchema), - required: z.array(z.string()).optional() - }) + requestedSchema: z + .object({ + type: z.literal('object'), + properties: z.record(z.string(), PrimitiveSchemaDefinitionSchema), + required: z.array(z.string()).optional(), + additionalProperties: z.boolean().optional() + }) + .passthrough() }); /** diff --git a/test/integration/test/issues/test1362.zod.toJSONSchema.elicitation.test.ts b/test/integration/test/issues/test1362.zod.toJSONSchema.elicitation.test.ts new file mode 100644 index 000000000..95d01421e --- /dev/null +++ b/test/integration/test/issues/test1362.zod.toJSONSchema.elicitation.test.ts @@ -0,0 +1,102 @@ +/** + * Regression test for https://github.com/modelcontextprotocol/typescript-sdk/issues/1362 + * + * Zod v4's `.toJSONSchema()` produces standard JSON Schema output that includes + * fields like `$schema` and `additionalProperties`. These fields were rejected + * by the `requestedSchema` type in `ElicitRequestFormParams`, even though the + * output is valid JSON Schema and works correctly at runtime. + * + * This test verifies that Zod's `.toJSONSchema()` output is accepted by + * `elicitInput()` without type errors or runtime failures. + */ + +import { Client } from '@modelcontextprotocol/client'; +import type { ElicitRequestFormParams } from '@modelcontextprotocol/core'; +import { AjvJsonSchemaValidator, InMemoryTransport } from '@modelcontextprotocol/core'; +import { Server } from '@modelcontextprotocol/server'; +import * as z from 'zod/v4'; + +describe('Issue #1362: Zod toJSONSchema() compatibility with elicitInput', () => { + let server: Server; + let client: Client; + + beforeEach(async () => { + server = new Server( + { name: 'test-server', version: '1.0.0' }, + { + capabilities: {}, + jsonSchemaValidator: new AjvJsonSchemaValidator() + } + ); + + client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } }); + + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + + await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]); + }); + + test('should accept Zod toJSONSchema() output as requestedSchema', async () => { + const zodSchema = z.object({ + name: z.string().describe('Your full name'), + color: z.enum(['red', 'green', 'blue']).describe('Favorite color') + }); + + const jsonSchema = zodSchema.toJSONSchema(); + + // Verify the Zod output contains the fields that previously caused type errors + expect(jsonSchema).toHaveProperty('$schema'); + expect(jsonSchema).toHaveProperty('additionalProperties'); + expect(jsonSchema).toHaveProperty('type', 'object'); + + client.setRequestHandler('elicitation/create', _request => ({ + action: 'accept', + content: { name: 'Alice', color: 'red' } + })); + + // This should compile without type errors and work at runtime. + // Before the fix, passing jsonSchema directly here would produce a + // TypeScript error because `additionalProperties` was not in the type. + const requestedSchema = jsonSchema as ElicitRequestFormParams['requestedSchema']; + + const result = await server.elicitInput({ + mode: 'form', + message: 'Please provide your information', + requestedSchema + }); + + expect(result).toEqual({ + action: 'accept', + content: { name: 'Alice', color: 'red' } + }); + }); + + test('should accept schema with additionalProperties field', async () => { + // Directly construct a schema with additionalProperties (as Zod produces) + const params: ElicitRequestFormParams = { + mode: 'form', + message: 'Enter your details', + requestedSchema: { + $schema: 'https://json-schema.org/draft/2020-12/schema', + type: 'object', + properties: { + email: { type: 'string', format: 'email', description: 'Your email' } + }, + required: ['email'], + additionalProperties: false + } + }; + + client.setRequestHandler('elicitation/create', _request => ({ + action: 'accept', + content: { email: 'test@example.com' } + })); + + const result = await server.elicitInput(params); + + expect(result).toEqual({ + action: 'accept', + content: { email: 'test@example.com' } + }); + }); +});