From aafe3002265ed05a50c0246f46068cc78c43cd03 Mon Sep 17 00:00:00 2001 From: semihisikman Date: Mon, 28 Sep 2026 14:26:45 +0700 Subject: [PATCH] fix(translator): strip errorMessage and other non-standard schema keywords from Gemini tool schemas --- open-sse/translator/formats/gemini.js | 9 +- .../unit/gemini-unknown-schema-fields.test.js | 105 ++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 tests/unit/gemini-unknown-schema-fields.test.js diff --git a/open-sse/translator/formats/gemini.js b/open-sse/translator/formats/gemini.js index 412fadcc..75a429b8 100644 --- a/open-sse/translator/formats/gemini.js +++ b/open-sse/translator/formats/gemini.js @@ -32,7 +32,14 @@ export const UNSUPPORTED_SCHEMA_CONSTRAINTS = [ "title", "optional", "deprecated", "if", "then", "else", "contentMediaType", "contentEncoding", // UI/Styling properties (from Cursor tools - NOT JSON Schema standard) "cornerRadius", "fillColor", "fontFamily", "fontSize", "fontWeight", - "gap", "padding", "strokeColor", "strokeThickness", "textColor" + "gap", "padding", "strokeColor", "strokeThickness", "textColor", + // Non-standard annotation/error keywords used by some MCP tool schemas (#4283). + // Gemini's schema proto has no field for these and rejects the whole request with + // "Unknown name X: Cannot find field" if any nested schema node carries them. + "errorMessage", "errorMessages", "x-errorMessage", "x-errorMessages", + "markdownDescription", "x-intellij-html-description", + "x-taplo-info", "x-taplo", "doNotSuggest", "suggestSortText", + "minProperties", "maxProperties" ]; // Default safety settings diff --git a/tests/unit/gemini-unknown-schema-fields.test.js b/tests/unit/gemini-unknown-schema-fields.test.js new file mode 100644 index 00000000..b28be4d5 --- /dev/null +++ b/tests/unit/gemini-unknown-schema-fields.test.js @@ -0,0 +1,105 @@ +/** + * Regression test for #4283 + * + * Gemini Antigravity rejects tool schemas that contain unknown JSON Schema + * keywords with: "Unknown name X: Cannot find field." + * + * The UNSUPPORTED_SCHEMA_CONSTRAINTS list in gemini.js did not include + * "errorMessage" (and similar non-standard annotation keywords used by some + * MCP tool schemas), causing 400 INVALID_ARGUMENT errors on tools with error + * documentation fields. + * + * Fix: add errorMessage, errorMessages, markdownDescription, and other + * non-standard annotation keywords to the strip list. + */ + +import { describe, it, expect } from "vitest"; +import { cleanJSONSchemaForAntigravity, UNSUPPORTED_SCHEMA_CONSTRAINTS } from "../../open-sse/translator/formats/gemini.js"; + +describe("UNSUPPORTED_SCHEMA_CONSTRAINTS includes non-standard annotation keywords (#4283)", () => { + it("includes errorMessage", () => { + expect(UNSUPPORTED_SCHEMA_CONSTRAINTS).toContain("errorMessage"); + }); + it("includes errorMessages", () => { + expect(UNSUPPORTED_SCHEMA_CONSTRAINTS).toContain("errorMessages"); + }); + it("includes markdownDescription", () => { + expect(UNSUPPORTED_SCHEMA_CONSTRAINTS).toContain("markdownDescription"); + }); + it("includes minProperties", () => { + expect(UNSUPPORTED_SCHEMA_CONSTRAINTS).toContain("minProperties"); + }); + it("includes maxProperties", () => { + expect(UNSUPPORTED_SCHEMA_CONSTRAINTS).toContain("maxProperties"); + }); +}); + +describe("cleanJSONSchemaForAntigravity strips errorMessage recursively (#4283)", () => { + it("strips top-level errorMessage", () => { + const schema = { + type: "object", + properties: { + code: { type: "integer" } + }, + errorMessage: "Invalid input" + }; + const result = cleanJSONSchemaForAntigravity(structuredClone(schema)); + expect(result).not.toHaveProperty("errorMessage"); + }); + + it("strips errorMessage nested inside array items", () => { + const schema = { + type: "object", + properties: { + tags: { + type: "array", + items: { + type: "string", + errorMessage: "Must be a non-empty string" + } + } + } + }; + const result = cleanJSONSchemaForAntigravity(structuredClone(schema)); + expect(result.properties.tags.items).not.toHaveProperty("errorMessage"); + }); + + it("strips markdownDescription from nested property", () => { + const schema = { + type: "object", + properties: { + name: { + type: "string", + markdownDescription: "The **name** of the resource" + } + } + }; + const result = cleanJSONSchemaForAntigravity(structuredClone(schema)); + expect(result.properties.name).not.toHaveProperty("markdownDescription"); + }); + + it("strips minProperties / maxProperties", () => { + const schema = { + type: "object", + minProperties: 1, + maxProperties: 10, + properties: { x: { type: "string" } } + }; + const result = cleanJSONSchemaForAntigravity(structuredClone(schema)); + expect(result).not.toHaveProperty("minProperties"); + expect(result).not.toHaveProperty("maxProperties"); + }); + + it("leaves other valid fields intact", () => { + const schema = { + type: "object", + description: "A valid tool", + properties: { + n: { type: "number", description: "A number" } + } + }; + const result = cleanJSONSchemaForAntigravity(structuredClone(schema)); + expect(result.description).toBe("A valid tool"); + expect(result.properties.n.description).toBe("A number"); + }); +}); \ No newline at end of file