gemini-mcp-repro.js
Describe the bug
Summary
Any MCP server that publishes a tool whose array items schema uses a union type containing
"object" — e.g. {"type": ["object", "null"]} — makes every Gemini model fail with
400 Bad Request. The same schema works fine on GPT and Claude.
A single non-conformant tool, anywhere in the loaded MCP set, poisons the entire session. The error
names no tool, no server, and no field, so it is effectively undiagnosable without bisection.
Minimal reproduction
No third-party servers needed. Save as gemini-mcp-repro.js:
// node gemini-mcp-repro.js bad (or: good)
const bad = (process.argv[2] || 'bad') === 'bad';
const inputSchema = {
type: 'object',
properties: {
rows: {
type: 'array',
description: 'a list of row objects',
// ONLY difference between the two modes:
items: bad
? { type: ['object', 'null'], properties: { a: { type: 'string' } } }
: { type: 'object', properties: { a: { type: 'string' } } }
}
}
};
let buf = '';
process.stdin.on('data', d => {
buf += d;
let i;
while ((i = buf.indexOf('\n')) >= 0) {
const line = buf.slice(0, i).trim(); buf = buf.slice(i + 1);
if (!line) continue;
let m; try { m = JSON.parse(line); } catch { continue; }
const send = r => process.stdout.write(JSON.stringify(r) + '\n');
if (m.method === 'initialize') send({ jsonrpc:'2.0', id:m.id, result:{ protocolVersion:'2024-11-05', capabilities:{tools:{}}, serverInfo:{name:'repro',version:'1'} } });
else if (m.method === 'tools/list') send({ jsonrpc:'2.0', id:m.id, result:{ tools:[{ name:'repro_tool', description:'repro', inputSchema }] } });
else if (m.method === 'tools/call') send({ jsonrpc:'2.0', id:m.id, result:{ content:[{type:'text',text:'ok'}] } });
else if (m.id !== undefined) send({ jsonrpc:'2.0', id:m.id, result:{} });
}
});
Run:
CFG='{"mcpServers":{"repro":{"type":"stdio","command":"node","args":["./gemini-mcp-repro.js","bad"],"tools":["*"]}}}'
# FAILS
copilot -p "hi" --model gemini-3.7-flash --allow-all-tools \
--disable-builtin-mcps --additional-mcp-config "$CFG"
# Same command with "good" instead of "bad" -> succeeds
Result matrix
|
bad (items.type: ["object","null"]) |
good (items.type: "object") |
gemini-3.7-flash |
❌ 400 |
✅ ok |
gemini-3.1-pro-preview |
❌ 400 |
✅ ok |
claude-sonnet-4.6 |
✅ ok |
✅ ok |
gpt-5.4 |
✅ ok |
✅ ok |
Gemini-specific, and isolated to a single JSON-Schema node.
Scope of the trigger
I bisected each construct with the same one-tool harness. A union type is only fatal on an
object-typed node:
| Schema shape |
Result |
{type: "string"} |
✅ |
{type: ["string","null"]} |
✅ |
{type: ["string","null"], default: null} |
✅ |
{description: "..."} — no type at all |
✅ |
{enum: [...], type: ["string","null"]} |
✅ |
{type: "string", format: "uuid"} |
✅ |
type: ["array","null"] + items.type: "object" |
✅ |
type: "array" + items.type: ["string","null"] |
✅ |
type: "array" + items.type: ["object","null"] |
❌ 400 |
The CLI already strips $schema and normalizes several unsupported keywords before dispatch, so the
sanitization layer exists — it just does not recurse into items for object-typed unions. Correct
OpenAPI 3.0 output would be "type": "object" + "nullable": true.
Second, independent trigger
A boolean schema — "properties": { "value": true }, valid JSON Schema 2020-12 meaning "accept
anything" — also produces a Gemini-only 400 via the same harness. Likely the same root fix.
Why this matters
Asks
- Normalize object-typed unions and boolean schemas in the existing MCP schema sanitizer, so a
non-conformant server degrades gracefully instead of bricking a model family.
- Failing that, make the error actionable — name the offending server, tool, and JSON path in
the 400. Today there is no way to find it short of an O(n) bisection over every tool.
(2) is arguably the more important fix: it converts an undiagnosable outage into a one-line message.
Environment
- Copilot CLI
1.0.81-x, Windows 11
- Repro is platform-independent (plain Node stdio server)
- Request IDs from my failures:
<paste your own>
Affected version
GitHub Copilot CLI 1.0.81-9.
Steps to reproduce the behavior
All the repro details are captured in the above blob. This is a 100% repro and Claude help me craft that minimal repro
Few more related/similar issues:
#4155
#1274
google-gemini/gemini-cli#22179
github/gh-aw#20833
anomalyco/opencode#8417
Expected behavior
No response
Additional context
No response
gemini-mcp-repro.js
Describe the bug
Summary
Any MCP server that publishes a tool whose array
itemsschema uses a union type containing"object"— e.g.{"type": ["object", "null"]}— makes every Gemini model fail with400 Bad Request. The same schema works fine on GPT and Claude.A single non-conformant tool, anywhere in the loaded MCP set, poisons the entire session. The error
names no tool, no server, and no field, so it is effectively undiagnosable without bisection.
Minimal reproduction
No third-party servers needed. Save as
gemini-mcp-repro.js:Run:
Result matrix
bad(items.type: ["object","null"])good(items.type: "object")gemini-3.7-flashgemini-3.1-pro-previewclaude-sonnet-4.6gpt-5.4Gemini-specific, and isolated to a single JSON-Schema node.
Scope of the trigger
I bisected each construct with the same one-tool harness. A union type is only fatal on an
object-typed node:{type: "string"}{type: ["string","null"]}{type: ["string","null"], default: null}{description: "..."}— notypeat all{enum: [...], type: ["string","null"]}{type: "string", format: "uuid"}type: ["array","null"]+items.type: "object"type: "array"+items.type: ["string","null"]type: "array"+items.type: ["object","null"]The CLI already strips
$schemaand normalizes several unsupported keywords before dispatch, so thesanitization layer exists — it just does not recurse into
itemsfor object-typed unions. CorrectOpenAPI 3.0 output would be
"type": "object"+"nullable": true.Second, independent trigger
A boolean schema —
"properties": { "value": true }, valid JSON Schema 2020-12 meaning "acceptanything" — also produces a Gemini-only 400 via the same harness. Likely the same root fix.
Why this matters
["object","null"]— it is what several popular schema generatorsproduce for a nullable list-of-objects parameter. I hit it from two unrelated servers
independently.
six days across every session on the machine.
400 Bad Requestwith no further detail, so the natural user responseis "Gemini is broken" → switch back to Claude. I suspect that is why this is widely experienced but
rarely reported. See CLI constantly getting 400 errors for invalid request body #1274, Gemini models return 400 Bad Request in Copilot CLI #4155, [Bug] MCP Integration fails with API validation error (400) and "malformed function call" unless Yolo mode is enabled google-gemini/gemini-cli#22179 (Atlassian/Jira MCP, same
trigger), gh-aw#20833, Gemini Bad Request with Github Copilot anomalyco/opencode#8417 — all plausibly the same defect.
Asks
non-conformant server degrades gracefully instead of bricking a model family.
the 400. Today there is no way to find it short of an O(n) bisection over every tool.
(2) is arguably the more important fix: it converts an undiagnosable outage into a one-line message.
Environment
1.0.81-x, Windows 11<paste your own>Affected version
GitHub Copilot CLI 1.0.81-9.
Steps to reproduce the behavior
All the repro details are captured in the above blob. This is a 100% repro and Claude help me craft that minimal repro
Few more related/similar issues:
#4155
#1274
google-gemini/gemini-cli#22179
github/gh-aw#20833
anomalyco/opencode#8417
Expected behavior
No response
Additional context
No response