MCP tool arguments with array-or-string union schema (anyOf) are stringified before reaching the server
还没有人认领这个 Issue。
- 主要语言
- Shell
- 星标
- 11.2k
- 派生
- 1.9k
- 平均合并
- 14 小时 16 分钟
- 30 天内合并 PR
- 6
描述
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
- 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 equivalentanyOf: [{type: array, items: {type: string}}, {type: string}, {type: null}]). - From Copilot CLI, call that tool with a genuine array value, e.g.
target: ["Top", "Child"]. - 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. - Direct/bypass test: calling the identical tool with the identical array directly against the server's
/mcpJSON-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
anyOfmixingarrayandobject) 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 mishandlinganyOf/union types that includearray, 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
arraywithstring(and possibly other non-array types).
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
调研方向
从 Copilot CLI MCP 客户端中将已公布的工具 schema 转换为调用参数的路径开始,使用已记录的 /mcp JSON-RPC 复现来比较直接请求和经由 CLI 的请求。验证当 anyOf 包含 array、string 和 null 时,array 值和 null 值仍保持为带类型的值,同时现有的纯 array 和非 union 情况继续正常工作。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- shell
- 领域
- api, cli
- Issue 类型
- 缺陷
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 活跃度
- 冷清
- 描述清晰度
- 描述清楚
- 新手友好度
- 67/100