github / github/copilot-cli

MCP tool arguments with array-or-string union schema (anyOf) are stringified before reaching the server

Aperta
#4,301 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
area:mcp area:tools
Lingua principale
Shell
Stelle
11.2k
Fork
1.9k
Merge medio
14h 16m
PR unite (30g)
6

Descrizione

### Describe the bug

When an MCP tool parameter's JSON Schema declares a union that includes both `array` and `string` (e.g. `anyOf: [array, string]` or a Zod `z.union([z.array(z.string()), z.string(), z.null()])`), Copilot CLI's MCP client flattens/stringifies the argument before sending it to the server -- even when a genuine JSON array is supplied. The MCP server (which correctly expects and validates an array) then rejects the call, because it receives a bare string instead.

This reproduces against the Obsidian Local REST API plugin's MCP server (`obsidian-mcp`), specifically its `vault_patch` and `vault_read` tools' `target` parameter, used for heading-path addressing (e.g. `["Parent Heading", "Child Heading"]`).

### Affected version

GitHub Copilot CLI 1.0.76 (Windows)

### Steps to reproduce the behavior

1. Register an MCP server exposing a tool whose parameter schema is `z.union([z.array(z.string()), z.string(), z.null()])` (or the JSON-Schema equivalent `anyOf: [{type: array, items: {type: string}}, {type: string}, {type: null}]`).
2. From Copilot CLI, call that tool with a genuine array value, e.g. `target: ["Top", "Child"]`.
3. Observe the server rejects the call as if a bare string were sent (e.g. `A heading target must be an array of heading texts, not a bare string`), even though the client was given a proper array.
4. Direct/bypass test: calling the identical tool with the identical array directly against the server's `/mcp` JSON-RPC endpoint (bypassing the Copilot CLI client) succeeds and returns the correct result -- confirming the server is correct and the array is valid.

### Root cause (isolated via a controlled A/B test)

We edited the MCP server's own schema for the affected parameter, removing the `string` option so the union became `array | null` only (no code changes elsewhere; identical Copilot CLI client, identical server, identical call). With that one change:
- The exact same array argument (`["Top", "Child"]`) that previously failed now succeeded and correctly resolved a two-level nested heading.
- A single-element array (`["Top"]`) also succeeded.

This isolates the bug precisely: **Copilot CLI's MCP client only mis-serializes array arguments when the parameter's declared schema is a union that also includes `string`.** Parameters that are plain/unconstrained JSON (no such union), or presumably schemas of `array | null` with no `string` sibling, pass arrays through untouched -- we independently confirmed a plain JSON-typed parameter (no union) on the same server correctly accepted and round-tripped a real array in the same session.

### Reproduction table

| Channel | Argument | Parameter schema | Result |
|---|---|---|---|
| Copilot CLI MCP tool call | `target: ["Top","Child"]` | `anyOf: [array, string, null]` | Fails -- arrives as a stringified value, rejected as "not a bare string" |
| Copilot CLI MCP tool call | `target: ["Top"]` | `anyOf: [array, string, null]` | Fails -- same error |
| Copilot CLI MCP tool call | `target: []` (empty array) | `anyOf: [array, string, null]` | Fails -- same error |
| Copilot CLI MCP tool call | `target: null` | `anyOf: [array, string, null]` | Fails -- `null` also arrives stringified |
| Direct `/mcp` JSON-RPC call (bypassing Copilot CLI) | `target: ["Top","Child"]` (real array) | same schema | Succeeds |
| Copilot CLI MCP tool call, schema patched to remove `string` | `target: ["Top","Child"]` | `union([array, null])` | Succeeds -- correct nested-heading resolution |
| Copilot CLI MCP tool call, schema patched to remove `string` | `target: ["Top"]` | `union([array, null])` | Succeeds |
| Copilot CLI MCP tool call | plain JSON-typed parameter (no union), given `["a","b"]` | no union constraint | Succeeds -- array passed through untouched |

### Expected behavior

A genuine JSON array argument should be transmitted to the MCP server as an array, regardless of whether the parameter's schema also permits a `string` (or other) alternative in the same union/`anyOf`.

### Additional context

- This is likely related to #2634, which reports Copilot CLI's MCP tool-schema handling losing/narrowing fields and unions (including an `anyOf` mixing `array` and `object`) between what an MCP server advertises and what the model/client actually uses. That report is about schema-definition loss at tool-discovery time; this report is about argument-*value* corruption at call time for a union schema. Both point at the same general area of Copilot CLI's MCP layer mishandling `anyOf`/union types that include `array`, so they may share a root cause.
- This is scoped narrowly: it is **not** "Copilot CLI can't send array arguments" -- arrays work fine for parameters with a plain array (or non-union) schema. The bug is specific to a schema union that pairs `array` with `string` (and possibly other non-array types).

Guida per i contributori

Apri la guida per i contributori

Direzione di ricerca

Inizia nel percorso del client MCP di Copilot CLI che converte gli schemi degli strumenti pubblicizzati in argomenti di chiamata, usando la riproduzione documentata di /mcp JSON-RPC per confrontare le richieste dirette e quelle mediate dalla CLI. Verifica che i valori array e null rimangano valori tipizzati quando anyOf include array, string e null, mentre i casi esistenti di array semplici e non-union continuino a funzionare.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
shell
Ambito
api, cli
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Tranquilla
Chiarezza
Specificata chiaramente
Idoneità per principianti
67/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.