|
| 1 | +import * as z from 'zod/v4'; |
| 2 | + |
| 3 | +import { standardSchemaToJsonSchema } from '../../src/util/standardSchema.js'; |
| 4 | + |
| 5 | +describe('standardSchemaToJsonSchema', () => { |
| 6 | + test('emits type:object for plain z.object schemas', () => { |
| 7 | + const schema = z.object({ name: z.string(), age: z.number() }); |
| 8 | + const result = standardSchemaToJsonSchema(schema, 'input'); |
| 9 | + |
| 10 | + expect(result.type).toBe('object'); |
| 11 | + expect(result.properties).toBeDefined(); |
| 12 | + }); |
| 13 | + |
| 14 | + test('emits type:object for discriminated unions', () => { |
| 15 | + const schema = z.discriminatedUnion('action', [ |
| 16 | + z.object({ action: z.literal('create'), name: z.string() }), |
| 17 | + z.object({ action: z.literal('delete'), id: z.string() }) |
| 18 | + ]); |
| 19 | + const result = standardSchemaToJsonSchema(schema, 'input'); |
| 20 | + |
| 21 | + expect(result.type).toBe('object'); |
| 22 | + // Zod emits oneOf for discriminated unions; the catchall on Tool.inputSchema |
| 23 | + // accepts it, but the top-level type must be present per MCP spec. |
| 24 | + expect(result.oneOf ?? result.anyOf).toBeDefined(); |
| 25 | + }); |
| 26 | + |
| 27 | + test('throws for schemas with explicit non-object type', () => { |
| 28 | + expect(() => standardSchemaToJsonSchema(z.string(), 'input')).toThrow(/must describe objects/); |
| 29 | + expect(() => standardSchemaToJsonSchema(z.array(z.string()), 'input')).toThrow(/must describe objects/); |
| 30 | + expect(() => standardSchemaToJsonSchema(z.number(), 'input')).toThrow(/must describe objects/); |
| 31 | + }); |
| 32 | + |
| 33 | + test('preserves existing type:object without modification', () => { |
| 34 | + const schema = z.object({ x: z.string() }); |
| 35 | + const result = standardSchemaToJsonSchema(schema, 'input'); |
| 36 | + |
| 37 | + // Spread order means zod's own type:"object" wins; verify no double-wrap. |
| 38 | + const keys = Object.keys(result); |
| 39 | + expect(keys.filter(k => k === 'type')).toHaveLength(1); |
| 40 | + expect(result.type).toBe('object'); |
| 41 | + }); |
| 42 | +}); |
0 commit comments