SEP-1613: use.catchall() on inputSchema/outputSchema to support JSON Schema 2020-12 (#1135)

This commit is contained in:
Felix Weinberger 2025-11-21 13:41:12 +00:00 • committed by GitHub
commit 41c6b35e07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 141 additions and 11 deletions

View file

@ -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 = {

View file

@ -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.

View file

@ -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';