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

Đang mở
#4,731 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

area:mcp area:tools
Ngôn ngữ chính
Shell
Star
11.2k
Fork
1.9k
Merge trung bình
14 giờ 16 phút
Pull request đã merge (30 ngày)
6

Mô tả

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.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu bằng cách lần theo đường dẫn Rust copilot_runtime::session::mcp::tool_catalog bằng workspace .vscode/mcp.json và một máy chủ MCP stdio bị chặn. Tái hiện timeout của client và lần làm mới tools/list tiếp theo, sau đó xác minh rằng các lần làm mới thất bại vẫn giữ nguyên catalog hiện có, không dispatch đến máy chủ bị chặn và cung cấp lỗi retry hoặc lỗi transport có thể khôi phục.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
rust, vscode
Lĩnh vực
backend, devtools
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
48/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.