diff --git a/src/types.test.ts b/src/types.test.ts index 3f6f83a14..e6ea0b6d6 100644 --- a/src/types.test.ts +++ b/src/types.test.ts @@ -6,6 +6,7 @@ import { PromptMessageSchema, CallToolResultSchema, CompleteRequestSchema, + ToolSchema, ToolUseContentSchema, ToolResultContentSchema, ToolChoiceSchema, @@ -319,6 +320,132 @@ describe('Types', () => { }); }); + describe('ToolSchema - JSON Schema 2020-12 support', () => { + test('should accept inputSchema with $schema field', () => { + const tool = { + name: 'test', + inputSchema: { + $schema: 'https://json-schema.org/draft/2020-12/schema', + type: 'object', + properties: { name: { type: 'string' } } + } + }; + const result = ToolSchema.safeParse(tool); + expect(result.success).toBe(true); + }); + + test('should accept inputSchema with additionalProperties', () => { + const tool = { + name: 'test', + inputSchema: { + type: 'object', + properties: { name: { type: 'string' } }, + additionalProperties: false + } + }; + const result = ToolSchema.safeParse(tool); + expect(result.success).toBe(true); + }); + + test('should accept inputSchema with composition keywords', () => { + const tool = { + name: 'test', + inputSchema: { + type: 'object', + allOf: [{ properties: { a: { type: 'string' } } }, { properties: { b: { type: 'number' } } }] + } + }; + const result = ToolSchema.safeParse(tool); + expect(result.success).toBe(true); + }); + + test('should accept inputSchema with $ref and $defs', () => { + const tool = { + name: 'test', + inputSchema: { + type: 'object', + properties: { user: { $ref: '#/$defs/User' } }, + $defs: { + User: { type: 'object', properties: { name: { type: 'string' } } } + } + } + }; + const result = ToolSchema.safeParse(tool); + expect(result.success).toBe(true); + }); + + test('should accept inputSchema with metadata keywords', () => { + const tool = { + name: 'test', + inputSchema: { + type: 'object', + title: 'User Input', + description: 'Input parameters for user creation', + deprecated: false, + examples: [{ name: 'John' }], + properties: { name: { type: 'string' } } + } + }; + const result = ToolSchema.safeParse(tool); + expect(result.success).toBe(true); + }); + + test('should accept outputSchema with full JSON Schema features', () => { + const tool = { + name: 'test', + inputSchema: { type: 'object' }, + outputSchema: { + type: 'object', + properties: { + id: { type: 'string' }, + tags: { type: 'array' } + }, + required: ['id'], + additionalProperties: false, + minProperties: 1 + } + }; + const result = ToolSchema.safeParse(tool); + expect(result.success).toBe(true); + }); + + test('should still require type: object at root for inputSchema', () => { + const tool = { + name: 'test', + inputSchema: { + type: 'string' + } + }; + const result = ToolSchema.safeParse(tool); + expect(result.success).toBe(false); + }); + + test('should still require type: object at root for outputSchema', () => { + const tool = { + name: 'test', + inputSchema: { type: 'object' }, + outputSchema: { + type: 'array' + } + }; + const result = ToolSchema.safeParse(tool); + expect(result.success).toBe(false); + }); + + test('should accept simple minimal schema (backward compatibility)', () => { + const tool = { + name: 'test', + inputSchema: { + type: 'object', + properties: { name: { type: 'string' } }, + required: ['name'] + } + }; + const result = ToolSchema.safeParse(tool); + expect(result.success).toBe(true); + }); + }); + describe('ToolUseContent', () => { test('should validate a tool call content', () => { const toolCall = { diff --git a/src/types.ts b/src/types.ts index 64153094d..4ff815ceb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -999,24 +999,28 @@ export const ToolSchema = z.object({ */ description: z.string().optional(), /** - * A JSON Schema object defining the expected parameters for the tool. + * A JSON Schema 2020-12 object defining the expected parameters for the tool. + * Must have type: 'object' at the root level per MCP spec. */ - inputSchema: z.object({ - type: z.literal('object'), - properties: z.record(z.string(), AssertObjectSchema).optional(), - required: z.optional(z.array(z.string())) - }), + inputSchema: z + .object({ + type: z.literal('object'), + properties: z.record(z.string(), AssertObjectSchema).optional(), + required: z.array(z.string()).optional() + }) + .catchall(z.unknown()), /** - * An optional JSON Schema object defining the structure of the tool's output returned in - * the structuredContent field of a CallToolResult. + * An optional JSON Schema 2020-12 object defining the structure of the tool's output + * returned in the structuredContent field of a CallToolResult. + * Must have type: 'object' at the root level per MCP spec. */ outputSchema: z .object({ type: z.literal('object'), properties: z.record(z.string(), AssertObjectSchema).optional(), - required: z.optional(z.array(z.string())), - additionalProperties: z.optional(z.boolean()) + required: z.array(z.string()).optional() }) + .catchall(z.unknown()) .optional(), /** * Optional additional tool information. diff --git a/src/validation/cfworker-provider.ts b/src/validation/cfworker-provider.ts index 60ec3f06e..adb102037 100644 --- a/src/validation/cfworker-provider.ts +++ b/src/validation/cfworker-provider.ts @@ -4,7 +4,6 @@ * This provider uses @cfworker/json-schema for validation without code generation, * making it compatible with edge runtimes like Cloudflare Workers that restrict * eval and new Function. - * */ import { type Schema, Validator } from '@cfworker/json-schema';