github / github/copilot-cli

A tools/list refresh dispatched into a server still blocked by a just-cancelled tool call times out and permanently strips that server's tools for the life of the process

未关闭
#4,731 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

area:mcp area:tools
主要语言
Shell
星标
11.2k
派生
1.9k
平均合并
14 小时 16 分钟
30 天内合并 PR
6

描述

Describe the bug

When a tool call to a stdio MCP server hits the client-side request timeout, the runtime immediately dispatches a tools/list refresh into the very server it just abandoned. That server is still occupied by the cancelled work and does not answer, so the refresh also times out — at a separate 60 s budget.

The catalog then drops that server's tools. Despite the warning text, the previously-listed tools are not retained:

[rust:rmcp::service] cancelled {"id":"6","reason":"request timeout"}
[rust:rmcp::service] cancelled {"id":"7","reason":"request timeout"}
[rust:copilot_runtime::session::mcp::tool_catalog] Failed to list MCP tools for fabric-rti-mcp;
    continuing with the previously listed tools: MCP request timed out after 60000 ms

Every subsequent call to that server returns instantly:

Tool 'fabric-rti-mcp-kusto_query' does not exist.

The failure is server-scoped: the warning names the server rather than a tool, and afterwards the agent reported no tool from that server present in its available-tools list. There is no re-list and no backoff, so the eviction is permanent for the process. The MCP server process itself stays alive and healthy the whole time — only the runtime's view of it is destroyed. The only recovery observed was restarting the session.

Two things make this expensive in practice:

  1. The agent cannot tell this apart from a tool that was never available, so it "helpfully" falls back to worse strategies instead of reporting a transport fault. In the affected session the agent burned ~11 minutes across two occurrences retrying and sleeping — neither of which can ever work.
  2. It needs a perfectly healthy server. This is not a startup or handshake failure. The server had listed successfully and answered a query 66 seconds before the drop.
The timeout alone is not the bug — the stacked refresh is

Same session, same server, same 180 s client timeout, 11 -32001 timeouts total:

  • 9 survivable (Aug 26–28) — none caused any tool loss; the server kept serving (some later calls errored or timed out again, but the tools stayed registered throughout).
  • 2 fatal (Sep 4, 1.0.83-2) — both had a tools/list refresh stack on top.

And on the fatal path the refresh is provably a consequence of the cancel, not a coincidence — it is the next sequential request id, dispatched within milliseconds:

Occurrence tool call cancelled tools/list cancelled delta
1 id 6 22:52:24.336Z id 7 22:53:24.349Z 60.013 s
2 id 15 23:10:03.568Z id 16 23:11:03.573Z 60.005 s
Controlled isolation: only the client-side cancel is fatal

Every call to this server after a clean restart, in order:

# Duration Outcome Tools after
1 10.4 s server-side error (cold auth) ✅ retained
2 3.8 s OK (print warm=1) ✅ retained
3 17.1 s OK — real query ✅ retained
4 0.7 s server-side error (bad KQL) ✅ retained
5 6.4 s server-side error (memory budget) ✅ retained
6 180.0 s client-side timeout 💀 all tools evicted
7–9 ~0 ms Tool … does not exist 💀

Three server-side errors left the catalog intact. The one client-side cancel destroyed it. The trigger is specifically the client abandoning an in-flight request — not the upstream service failing.

Note that call #6's payload was 606 characterssmaller than three calls that were fine. Payload size is irrelevant; what matters is that the server was mid-work when the client gave up.

The server is still alive after its tools are gone

ps 12 minutes after the eviction shows the stdio child still running (PID 26530, spawned 23:04:44), wedged on the abandoned request. It is not a crash, and it is not a transport close — the runtime simply has no path back to a server it is still connected to.

Affected version

GitHub Copilot CLI 1.0.83-2.

Steps to reproduce the behavior

  1. Configure any stdio MCP server that can take a long time to answer a single tool call (encountered with uvx microsoft-fabric-rti-mcp issuing Kusto queries; anything that can block for >180 s works).
  2. Confirm its tools are listed and working — call something trivial and get a result.
  3. Issue one tool call that will not return within the client timeout (~180 s).
  4. Observe the call fail with MCP error -32001: Request timed out.
  5. Watch the log: a tools/list for the same server is dispatched immediately and dies 60 s later, followed by Failed to list MCP tools for <server>.
  6. Call any tool on that server — including the trivial one from step 2. It returns Tool '<name>' does not exist. instantly.
  7. Wait and retry (the agent tried 20 s / 30 s / 40 s / 45 s / 50 s). The tools did not come back. Only restarting the session recovered them.

Reproduced twice within 22 minutes, the second time in a freshly restarted runtime process.

Expected behavior

  1. A failed tools/list refresh should genuinely retain the previously-listed tools, as the warning already claims. Today the fallback path evicts them, so the message and the behaviour disagree.
  2. Don't dispatch tools/list into a server known to be blocked. The runtime has just cancelled a request against that server for timing out; sending the refresh immediately into the same serial stdio pipe is close to guaranteed to time out too. Defer it, or wait for the server to drain.
  3. Retry the refresh with backoff. A single transient failure should not be terminal for the life of the process, and there is currently no in-session way to recover (no re-list, no reconnect).
  4. If tools genuinely must be dropped, surface it to the user and the model as a transport fault, distinctly from "this tool does not exist". Right now the agent is told the tool never existed and silently degrades.

Additional context

Environment — this is the VS Code Insiders Agent window, not the terminal TUI. Reproducing by running copilot in a shell may not exercise the same path; no copilot invocation is involved.

Host:        VS Code Insiders — Agent window (Agent sessions)
             client_name = vscode-agent-host · producer = copilot-agent
Runtime:     @github/copilot-darwin-arm64 1.0.83-2, spawned headless by VS Code
             (--headless --no-auto-update --stdio --no-auto-login)
MCP config:  workspace .vscode/mcp.json
             (~/.copilot/mcp-config.json does not exist on this machine)
MCP server:  fabric-rti-mcp — stdio, `uvx microsoft-fabric-rti-mcp`
OS:          macOS (darwin-arm64)

The MCP config source may matter: the server is declared only in the workspace .vscode/mcp.json, so reproducing from a user-scope CLI config may exercise a different path.

Possible regression. The tool_catalog warning appears only in the three newest runtime logs on this machine (Sep 3 21:21 onward), which are also the only ones whose startup banner carries the [plugin-dir] line. The older logs cover the same MCP server and the same 180 s timeouts — 9 of them — with no such warning and no tool loss. Correlation only; the responsible change is unconfirmed.

Workaround for anyone hitting this with a Kusto-backed server. Since server-side errors are harmless and only the client cancel is fatal, make the server give up first:

client_request_properties = {"servertimeout": "00:02:00"}

A 120 s server timeout under the 180 s client ceiling converts the fatal case into a normal error and keeps the tools registered. That's server-specific, but the general shape — ensure the server always answers before the client gives up — should apply to any stdio MCP server.

Related
  • #4421 — MCP initialize has a fixed, non-configurable 60 s budget with no retry, and never recovers. Closest sibling: same 60 s budget, same permanent-for-the-session outcome, but at initialize. This report is the post-handshake twin — it needs a server that already listed successfully and is actively serving.
  • #4466 — transient 5xx on initialize marks a server failed for the whole session with no retry/backoff. Same "one transient failure is permanent" family.
  • #4598 — startup connects only some servers and never retries; reloads destroy live handles mid-session.
  • #2304 (closed, 1.0.11) — same user-visible string (Tool '…' does not exist) from a different cause (session resume).
  • #172 (closed) — configured MCP timeouts are not respected, which is why the 180 s ceiling can't be raised from config to avoid this.

Three orphaned MCP server processes reparented to PID 1 were also observed on this machine, but that's already covered by #4697 / #4392 / #4461 and isn't part of this report.

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

首先,使用 workspace .vscode/mcp.json 和一个阻塞的 stdio MCP 服务器,跟踪 Rust 路径 copilot_runtime::session::mcp::tool_catalog。重现客户端超时以及随后的 tools/list 刷新,然后验证刷新失败时会保留现有目录,不会向被阻塞的服务器进行调度,并提供可恢复的重试或传输错误。

由索引模型根据 Issue 内容生成。

评估

技术栈
rust, vscode
领域
backend, devtools
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
活跃
描述清晰度
基本清楚
新手友好度
48/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。