From 8a28bf1ccb672609d0caa039e6fa8ce5dde1b9fe Mon Sep 17 00:00:00 2001 From: youngxhui Date: Tue, 2 Jun 2026 23:32:28 +0800 Subject: [PATCH 1/3] fix(kosong): repair mismatched schema types from Xcode 26.5 MCP Xcode 26.5 (17F42) mcpbridge generates contradictory JSON Schemas where String-backed Swift enums carry type: 'object' alongside string enum values. Moonshot rejects these as invalid. Detect and repair the mismatch in normalizeKimiToolSchema, stripping irrelevant structure keys after the fix. Closes #302 --- .../fix-xcode-mcp-schema-type-mismatch.md | 6 ++ packages/kosong/src/providers/kimi-schema.ts | 55 +++++++++++++++ packages/kosong/src/providers/kimi.ts | 13 ++++ .../kosong/test/providers/kimi-schema.test.ts | 70 +++++++++++++++++++ 4 files changed, 144 insertions(+) create mode 100644 .changeset/fix-xcode-mcp-schema-type-mismatch.md diff --git a/.changeset/fix-xcode-mcp-schema-type-mismatch.md b/.changeset/fix-xcode-mcp-schema-type-mismatch.md new file mode 100644 index 00000000..7d03da6f --- /dev/null +++ b/.changeset/fix-xcode-mcp-schema-type-mismatch.md @@ -0,0 +1,6 @@ +--- +"@moonshot-ai/kosong": patch +"@moonshot-ai/kimi-code": patch +--- + +Repair mismatched JSON Schema types emitted by Xcode 26.5 MCP server for Moonshot compatibility. diff --git a/packages/kosong/src/providers/kimi-schema.ts b/packages/kosong/src/providers/kimi-schema.ts index f603a144..3e407d84 100644 --- a/packages/kosong/src/providers/kimi-schema.ts +++ b/packages/kosong/src/providers/kimi-schema.ts @@ -309,11 +309,66 @@ function normalizeProperty(node: unknown): void { } else { node['type'] = inferTypeFromStructure(node); } + } else if (!hasAnyKey(node, TYPE_COMPLETION_SKIP_KEYS) && typeof node['type'] === 'string') { + // Some MCP servers emit schemas where a $ref merge or a generator bug + // leaves an explicit type that contradicts the enum/const values (e.g. + // type: 'object' alongside string enum values). Moonshot rejects these + // as invalid, so repair the type when it disagrees with the values. + // + // Known trigger: Xcode MCP (xcrun mcpbridge) starting with + // Version 26.5 (17F42) generates this bug for String-backed Swift enums. + const enumValues = node['enum']; + if (Array.isArray(enumValues) && enumValues.length > 0) { + try { + const inferred = inferTypeFromValues(enumValues); + if (node['type'] !== inferred) { + // eslint-disable-next-line no-console + console.warn( + `[kimi-schema] repaired mismatched type: changed "${String(node['type'])}" to "${inferred}" for enum ${JSON.stringify(enumValues.slice(0, 3))}${enumValues.length > 3 ? '...' : ''}`, + ); + node['type'] = inferred; + removeIrrelevantStructureKeys(node, inferred); + } + } catch { + // Mixed or uninferable enum types — leave the explicit type as-is + // and let the provider validator surface the error. + } + } else if (hasOwn(node, 'const')) { + try { + const inferred = inferTypeFromValues([node['const']]); + if (node['type'] !== inferred) { + // eslint-disable-next-line no-console + console.warn( + `[kimi-schema] repaired mismatched type: changed "${String(node['type'])}" to "${inferred}" for const ${JSON.stringify(node['const'])}`, + ); + node['type'] = inferred; + removeIrrelevantStructureKeys(node, inferred); + } + } catch { + // Same as above. + } + } } recurseSchema(node); } +function removeIrrelevantStructureKeys( + node: Record, + newType: JsonSchemaType, +): void { + if (newType !== 'object') { + for (const key of OBJECT_STRUCTURE_KEYS) { + delete node[key]; + } + } + if (newType !== 'array') { + for (const key of ARRAY_STRUCTURE_KEYS) { + delete node[key]; + } + } +} + function inferTypeFromStructure(schema: Record): JsonSchemaType { if (hasAnyKey(schema, OBJECT_STRUCTURE_KEYS)) { return 'object'; diff --git a/packages/kosong/src/providers/kimi.ts b/packages/kosong/src/providers/kimi.ts index ef53eca7..4f4a9413 100644 --- a/packages/kosong/src/providers/kimi.ts +++ b/packages/kosong/src/providers/kimi.ts @@ -494,6 +494,19 @@ export class KimiChatProvider implements ChatProvider { )) as unknown as OpenAI.Chat.ChatCompletion | AsyncIterable; return new KimiStreamedMessage(response, this._stream); } catch (error: unknown) { + const apiError = error as { status?: number; message?: string }; + if (apiError.status === 400 && typeof apiError.message === 'string') { + if ( + apiError.message.includes('tools.function.parameters') || + apiError.message.includes('json schema') + ) { + // eslint-disable-next-line no-console + console.error( + '[KimiChatProvider] 400 error with tools schema. createParams.tools:\n' + + JSON.stringify(createParams['tools'], null, 2), + ); + } + } throw convertOpenAIError(error); } } diff --git a/packages/kosong/test/providers/kimi-schema.test.ts b/packages/kosong/test/providers/kimi-schema.test.ts index c257ecf3..25007d14 100644 --- a/packages/kosong/test/providers/kimi-schema.test.ts +++ b/packages/kosong/test/providers/kimi-schema.test.ts @@ -372,6 +372,76 @@ describe('normalizeKimiToolSchema', () => { }); }); + it('repairs mismatched explicit type when enum values contradict it', () => { + // Regression: Xcode MCP (xcrun mcpbridge) Version 26.5 (17F42) and later + // generates schemas where String-backed Swift enums incorrectly carry + // type: 'object' alongside string enum values. We overwrite the contradictory + // type and strip object/array structure keys that are no longer relevant. + const schema = { + type: 'object', + properties: { + operation: { + type: 'object', + enum: ['move', 'copy'], + properties: { + rawValue: { type: 'string' }, + }, + required: ['rawValue'], + }, + }, + }; + + const result = normalizeKimiToolSchema(schema); + + expect(result).toEqual({ + type: 'object', + properties: { + operation: { + type: 'string', + enum: ['move', 'copy'], + }, + }, + }); + }); + + it('repairs mismatched explicit type when const value contradicts it', () => { + const schema = { + type: 'object', + properties: { + mode: { type: 'object', const: 'fast' }, + }, + }; + + const result = normalizeKimiToolSchema(schema); + + expect(result).toEqual({ + type: 'object', + properties: { + mode: { type: 'string', const: 'fast' }, + }, + }); + }); + + it('leaves mixed enum types with explicit type untouched to surface provider error', () => { + const schema = { + type: 'object', + properties: { + bad: { type: 'object', enum: ['move', 1] }, + }, + }; + + // inferTypeFromValues throws for mixed types; we should not overwrite the + // explicit type so the downstream provider validator can report the issue. + expect(() => normalizeKimiToolSchema(schema)).not.toThrow(); + const result = normalizeKimiToolSchema(schema); + expect(result).toEqual({ + type: 'object', + properties: { + bad: { type: 'object', enum: ['move', 1] }, + }, + }); + }); + it('infers object and array property types from container enum/const values', () => { const schema = { type: 'object', From 7b783aad13b2762b71e631125341e1adb0c91a2f Mon Sep 17 00:00:00 2001 From: youngxhui Date: Wed, 3 Jun 2026 14:22:15 +0800 Subject: [PATCH 2/3] fix(kosong): avoid dumping full tool schemas on schema-related 400 errors --- packages/kosong/src/providers/kimi.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/kosong/src/providers/kimi.ts b/packages/kosong/src/providers/kimi.ts index 4f4a9413..e4be6135 100644 --- a/packages/kosong/src/providers/kimi.ts +++ b/packages/kosong/src/providers/kimi.ts @@ -500,10 +500,12 @@ export class KimiChatProvider implements ChatProvider { apiError.message.includes('tools.function.parameters') || apiError.message.includes('json schema') ) { + const toolNames = (createParams['tools'] as Array<{ function?: { name?: string } }>) + ?.map((t) => t.function?.name) + .filter((n): n is string => typeof n === 'string'); // eslint-disable-next-line no-console console.error( - '[KimiChatProvider] 400 error with tools schema. createParams.tools:\n' + - JSON.stringify(createParams['tools'], null, 2), + `[KimiChatProvider] 400 error with tools schema. tools: [${toolNames?.join(', ') ?? 'unknown'}]`, ); } } From a8dac060c5b32758d270c94637ccaebf435540f1 Mon Sep 17 00:00:00 2001 From: youngxhui Date: Wed, 3 Jun 2026 14:32:41 +0800 Subject: [PATCH 3/3] fix(kosong): redact enum and const values in schema repair diagnostics --- packages/kosong/src/providers/kimi-schema.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kosong/src/providers/kimi-schema.ts b/packages/kosong/src/providers/kimi-schema.ts index 3e407d84..d551100b 100644 --- a/packages/kosong/src/providers/kimi-schema.ts +++ b/packages/kosong/src/providers/kimi-schema.ts @@ -324,7 +324,7 @@ function normalizeProperty(node: unknown): void { if (node['type'] !== inferred) { // eslint-disable-next-line no-console console.warn( - `[kimi-schema] repaired mismatched type: changed "${String(node['type'])}" to "${inferred}" for enum ${JSON.stringify(enumValues.slice(0, 3))}${enumValues.length > 3 ? '...' : ''}`, + `[kimi-schema] repaired mismatched type: changed "${String(node['type'])}" to "${inferred}" for enum (${enumValues.length} values)`, ); node['type'] = inferred; removeIrrelevantStructureKeys(node, inferred); @@ -339,7 +339,7 @@ function normalizeProperty(node: unknown): void { if (node['type'] !== inferred) { // eslint-disable-next-line no-console console.warn( - `[kimi-schema] repaired mismatched type: changed "${String(node['type'])}" to "${inferred}" for const ${JSON.stringify(node['const'])}`, + `[kimi-schema] repaired mismatched type: changed "${String(node['type'])}" to "${inferred}" for const value`, ); node['type'] = inferred; removeIrrelevantStructureKeys(node, inferred);