fix: ensure empty object schemas include required field for OpenAI compatibility#1702
Conversation
…mpatibility When generating JSON Schema from Zod schemas via schemaToJson(), object schemas now always include the 'required' field, even when empty. This is necessary for compatibility with OpenAI's strict JSON schema mode, which mandates that 'required' always be present. The fix recursively processes all nested object schemas, including those in properties, array items, additionalProperties, allOf/anyOf/oneOf, and $defs. Fixes modelcontextprotocol#1659.
🦋 Changeset detectedLatest commit: 788a86a The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
travisbreaks
left a comment
There was a problem hiding this comment.
Good fix for the OpenAI strict mode compatibility issue. The recursive approach is thorough. A few observations:
1. In-place mutation
ensureRequiredField mutates the schema object directly. If z.toJSONSchema() caches or reuses objects internally, this could cause surprising side effects. Safer to spread into a new object:
const result = { ...schema };
if (result.type === 'object' && !('required' in result)) {
result.required = [];
}2. Missing recursive cases
The function handles properties, additionalProperties, items, allOf/anyOf/oneOf, and $defs, but misses:
not(e.g.z.object({}).not(...))if/then/else(conditional schemas)prefixItems(tuple schemas in JSON Schema 2020-12)
These are less common with Zod output, but if the goal is general JSON Schema compliance for OpenAI, they could appear in user-constructed schemas.
3. Test coverage gap
Tests cover nested objects, arrays, and deep nesting, but none of the combiner paths (allOf, anyOf, oneOf) are tested. A test with z.union([z.object({}), z.object({})]) would validate that branch.
Overall a clean contribution. The core logic is correct and the tests cover the primary use case well.
Summary
When generating JSON Schema from Zod schemas via
schemaToJson(), object schemas now always include therequiredfield, even when empty. This is necessary for compatibility with OpenAI's strict JSON schema mode, which mandates thatrequiredalways be present.Problem
As reported in #1659, when registering a tool using an empty Zod object as
inputSchema:The generated JSON schema is:
{ "type": "object", "properties": {}, "additionalProperties": false }This is valid JSON Schema, but OpenAI's strict mode requires
requiredto always be present:Solution
Modified
schemaToJson()to post-process the JSON Schema output and ensure all object schemas have arequiredfield. The fix recursively processes:propertiesadditionalPropertiesschemasallOf/anyOf/oneOfcombinators$defsNow the output is:
{ "type": "object", "properties": {}, "additionalProperties": false, "required": [] }Testing
Added comprehensive tests covering:
All existing tests pass.
Fixes #1659