Skip to content

Gemini Flash: one malformed MCP array enum breaks all prompts with HTTP 400 #4835

Closed

Description

@kondv

Type: Bug

Copilot CLI Version: 1.0.84-5
OS Version: Windows 11 x64
Model: gemini-3.7-flash

Summary

Having a single MCP tool whose schema contains an integer enum directly on an array property causes all requests to Gemini Flash to fail with an opaque HTTP 400 Bad Request error.

The error does not identify which MCP server, tool, or schema property caused the failure.

Steps to Reproduce

  1. Save schema-repro.mjs, gemini-bad.json, and gemini-fixed.json from the Reproducer files section below in the same directory. Node.js must be available.
  2. From that directory, run these native Copilot commands:
copilot --model gemini-3.7-flash --no-custom-instructions --additional-mcp-config "@gemini-bad.json" --available-tools schema_repro-probe_000 --allow-all-tools -p "Reply with exactly OK. Do not call tools."
copilot --model gemini-3.7-flash --no-custom-instructions --additional-mcp-config "@gemini-fixed.json" --available-tools schema_repro-probe_000 --allow-all-tools -p "Reply with exactly OK. Do not call tools."

Both commands advertise exactly one inert tool. There is no SDK dependency or shell helper.

The bad fixture defines:

{
  "type": "object",
  "properties": {
    "levels": {
      "type": ["array", "null"],
      "items": { "type": "integer" },
      "enum": [0, 1, 2, 3, 4, 25]
    }
  }
}

Actual Behaviour

The request immediately fails with:

Execution failed: 400 Bad Request (Request ID: ...)

Moving enum under items:

{
  "type": ["array", "null"],
  "items": {
    "type": "integer",
    "enum": [0, 1, 2, 3, 4, 25]
  }
}

completely resolves the issue and the request succeeds.

Native CLI results:

Input Schema Advertised tools Result
enum on array level 1 HTTP 400
enum under items 1 OK

Expected Behaviour

Copilot CLI should validate tool schemas client-side before sending them to the provider API, and report actionable errors identifying:

  • Server name
  • Tool name
  • Schema property path

For this fixture: schema_repro, probe_000, and /properties/levels/enum.

Failing that, the error message should include the provider's validation details instead of a generic 400 error.

Notes

Impact

A single malformed tool schema from any active MCP server silently breaks all model calls to Gemini without any indication of which server or tool is at fault.

Reproducer files

schema-repro.mjs

import { createInterface } from 'node:readline';

const mode = process.argv[2];
const count = Number(process.argv[3] ?? 1);
const allowedModes = ['array-enum-bad', 'array-enum-fixed', 'count'];

if (!allowedModes.includes(mode) ||
    !Number.isInteger(count) ||
    count < 1 ||
    count > 400) {
  throw new Error('Usage: node schema-repro.mjs array-enum-bad|array-enum-fixed|count [1..400]');
}

const levels = { type: ['array', 'null'], items: { type: 'integer' } };
if (mode === 'array-enum-bad') {
  levels.enum = [0, 1, 2, 3, 4, 25];
} else {
  levels.items.enum = [0, 1, 2, 3, 4, 25];
}

const tools = Array.from({ length: mode === 'count' ? count : 1 }, (_, index) => ({
  name: `probe_${String(index).padStart(3, '0')}`,
  description: 'Schema diagnostic only; performs no operation.',
  inputSchema: {
    type: 'object',
    properties: mode === 'count' ? { value: { type: 'string' } } : { levels },
  },
}));

for await (const line of createInterface({ input: process.stdin, crlfDelay: Infinity })) {
  if (!line.trim()) {
    continue;
  }
  const request = JSON.parse(line);
  if (request.id === undefined) {
    continue;
  }

  let result;
  if (request.method === 'initialize') {
    result = {
      protocolVersion: request.params.protocolVersion,
      capabilities: { tools: {} },
      serverInfo: { name: 'schema-repro', version: '1.0.0' },
    };
  } else if (request.method === 'tools/list') {
    result = { tools };
  } else if (request.method === 'tools/call') {
    result = {
      isError: true,
      content: [{ type: 'text', text: 'This diagnostic never executes an operation.' }],
    };
  } else if (request.method === 'ping') {
    result = {};
  } else {
    process.stdout.write(`${JSON.stringify({
      jsonrpc: '2.0',
      id: request.id,
      error: { code: -32601, message: 'Method not found' },
    })}\n`);
    continue;
  }

  process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id: request.id, result })}\n`);
}

gemini-bad.json

{
  "mcpServers": {
    "schema_repro": {
      "type": "stdio",
      "command": "node",
      "args": ["schema-repro.mjs", "array-enum-bad"],
      "tools": ["*"]
    }
  }
}

gemini-fixed.json

{
  "mcpServers": {
    "schema_repro": {
      "type": "stdio",
      "command": "node",
      "args": ["schema-repro.mjs", "array-enum-fixed"],
      "tools": ["*"]
    }
  }
}

Activity

  1. patniko commented on Oct 26, 2026

    @patniko
    Collaborator

    Thanks for the excellent reproducer. This is fixed in Copilot CLI v1.0.100: when a tool schema puts enum on an array-typed property, the CLI now moves it onto the array items before sending the tools to Gemini, so a single MCP tool like this no longer makes every Gemini request fail with a 400; v1.0.103 also fixed further Gemini schema errors involving nullable and enum fields. If you still see opaque 400s from Gemini with your fixture on the latest version, please reopen.

    Triaged as part of a Copilot SDK backlog review, with help from GitHub Copilot.

  2. kondv commented on Oct 28, 2026

    @kondv
    Author

    Thanks @patniko. Confirmed on 1.0.104 with the same fixtures. gemini-3.7-flash isn't available anymore, so I ran it on gemini-3.8-flash and gemini-4-argon, and the bad schema returns OK on both.

    Retest on 1.0.104

    Same schema-repro.mjs, gemini-bad.json and gemini-fixed.json as in the issue:

    copilot --model gemini-3.8-flash --no-custom-instructions --additional-mcp-config "@gemini-bad.json" --available-tools schema_repro-probe_000 --allow-all-tools -p "Reply with exactly OK. Do not call tools." --log-level debug --log-dir <dir>
    Fixture Model Result Tool digest canonicalDigest
    gemini-bad.json gemini-3.8-flash OK, exit 0 (2 runs) af93c55d adca007b
    gemini-bad.json gemini-4-argon OK, exit 0 af93c55d adca007b
    gemini-fixed.json gemini-3.8-flash OK, exit 0 8ea2e436 adca007b

    The bad and fixed fixtures have different raw digests but the same canonicalDigest, which matches the fix you described (the enum moved onto items before sending).

    — kondv's Copilot 🤖

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions