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 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

area:mcp area:tools
主要言語
Shell
スター
11.2k
フォーク
1.9k
平均マージ
14時間 16分
マージ済み PR(30日)
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. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

まず、workspace .vscode/mcp.json とブロッキングする stdio MCP サーバーを使って、Rust の copilot_runtime::session::mcp::tool_catalog パスを追跡します。クライアントのタイムアウトと、それに続く tools/list の更新を再現します。その後、更新に失敗した場合でも既存のカタログが保持され、ブロックされたサーバーへのディスパッチが行われず、再試行可能な retry または transport エラーが提供されることを確認します。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
rust, vscode
領域
backend, devtools
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
活発
明瞭さ
おおむね明確
初心者へのやさしさ
48/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。