diff --git a/src/core/__tests__/structured-output-schema.test.ts b/src/core/__tests__/structured-output-schema.test.ts index 006f3825..93c801be 100644 --- a/src/core/__tests__/structured-output-schema.test.ts +++ b/src/core/__tests__/structured-output-schema.test.ts @@ -25,6 +25,14 @@ function expectStandaloneCompile(schema: JsonObject): void { expect(() => ajv.compile(schema)).not.toThrow(); } +function stripRegistrationResourceEnvelope(schema: JsonObject): JsonObject { + const stripped = JSON.parse(JSON.stringify(schema)) as JsonObject; + delete stripped.$schema; + delete stripped.$id; + delete stripped.$defs; + return stripped; +} + describe('structured output schema bundling', () => { beforeEach(() => { __resetMcpOutputSchemaCacheForTests(); @@ -119,10 +127,20 @@ describe('structured output schema bundling', () => { $id: 'https://xcodebuildmcp.com/schemas/structured-output/xcodebuildmcp.output.simulator-list/1.registration.schema.json', type: 'object', oneOf: [ - getMcpOutputSchema(ref), - getMcpOutputSchema({ schema: 'xcodebuildmcp.output.error', version: '1' }), + stripRegistrationResourceEnvelope(getMcpOutputSchema(ref)), + stripRegistrationResourceEnvelope( + getMcpOutputSchema({ schema: 'xcodebuildmcp.output.error', version: '1' }), + ), ], + $defs: { + ...((getMcpOutputSchema(ref).$defs as JsonObject) ?? {}), + ...((getMcpOutputSchema({ schema: 'xcodebuildmcp.output.error', version: '1' }) + .$defs as JsonObject) ?? {}), + }, }); + expect((jsonSchema.oneOf as JsonObject[])[0].$defs).toBeUndefined(); + expect((jsonSchema.oneOf as JsonObject[])[0].$id).toBeUndefined(); + expect((jsonSchema.$defs as JsonObject).errorConsistency).toBeDefined(); expectNoExternalCommonRefs(jsonSchema); expectStandaloneCompile(jsonSchema); }); diff --git a/src/core/structured-output-schema.ts b/src/core/structured-output-schema.ts index 8da1722b..c6ad07b4 100644 --- a/src/core/structured-output-schema.ts +++ b/src/core/structured-output-schema.ts @@ -203,6 +203,26 @@ function bundleSchema(rootSchema: JsonObject, commonSchema: JsonObject): JsonObj return bundled; } +function inlineRegistrationSchemaResource(schema: JsonObject): { + schema: JsonObject; + defs: JsonObject; +} { + const inlinedSchema = cloneJson(schema); + const defsCandidate = inlinedSchema.$defs === undefined ? {} : cloneJson(inlinedSchema.$defs); + if (!isRecord(defsCandidate)) { + throw new Error('Structured output registration $defs must be an object when present.'); + } + + delete inlinedSchema.$schema; + delete inlinedSchema.$id; + delete inlinedSchema.$defs; + + return { + schema: inlinedSchema, + defs: defsCandidate, + }; +} + export function getMcpOutputSchema(ref: StructuredOutputSchemaRef): JsonObject { assertSchemaRef(ref); const cacheKey = `${ref.schema}@${ref.version}`; @@ -226,13 +246,30 @@ function getMcpOutputSchemaForRegistrationJson(ref: StructuredOutputSchemaRef): if (ref.schema === STRUCTURED_ERROR_SCHEMA_REF.schema) { return toolSchema; } + const errorSchema = getMcpOutputSchema(STRUCTURED_ERROR_SCHEMA_REF); + const toolResource = inlineRegistrationSchemaResource(toolSchema); + const errorResource = inlineRegistrationSchemaResource(errorSchema); + const defs: JsonObject = {}; - return { + for (const [name, definition] of Object.entries(toolResource.defs)) { + mergeDefinition(defs, name, definition); + } + for (const [name, definition] of Object.entries(errorResource.defs)) { + mergeDefinition(defs, name, definition); + } + + const registrationSchema: JsonObject = { $schema: 'https://json-schema.org/draft/2020-12/schema', $id: `https://xcodebuildmcp.com/schemas/structured-output/${ref.schema}/${ref.version}.registration.schema.json`, type: 'object', - oneOf: [toolSchema, getMcpOutputSchema(STRUCTURED_ERROR_SCHEMA_REF)], + oneOf: [toolResource.schema, errorResource.schema], }; + + if (Object.keys(defs).length > 0) { + registrationSchema.$defs = defs; + } + + return registrationSchema; } export function getMcpOutputSchemaForRegistration(ref: StructuredOutputSchemaRef): McpOutputSchema {