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:
- 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.
- 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 characters — smaller 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
- 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).
- Confirm its tools are listed and working — call something trivial and get a result.
- Issue one tool call that will not return within the client timeout (~180 s).
- Observe the call fail with
MCP error -32001: Request timed out.
- 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>.
- Call any tool on that server — including the trivial one from step 2. It returns
Tool '<name>' does not exist. instantly.
- 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
- 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.
- 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.
- 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).
- 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
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.
Describe the bug
When a tool call to a stdio MCP server hits the client-side request timeout, the runtime immediately dispatches a
tools/listrefresh 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:
Every subsequent call to that server returns instantly:
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:
The timeout alone is not the bug — the stacked refresh is
Same session, same server, same 180 s client timeout, 11
-32001timeouts total:1.0.83-2) — both had atools/listrefresh 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:
tools/listControlled isolation: only the client-side cancel is fatal
Every call to this server after a clean restart, in order:
print warm=1)Tool … does not existThree 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 characters — smaller 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
ps12 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
Steps to reproduce the behavior
uvx microsoft-fabric-rti-mcpissuing Kusto queries; anything that can block for >180 s works).MCP error -32001: Request timed out.tools/listfor the same server is dispatched immediately and dies 60 s later, followed byFailed to list MCP tools for <server>.Tool '<name>' does not exist.instantly.Reproduced twice within 22 minutes, the second time in a freshly restarted runtime process.
Expected behavior
tools/listrefresh 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.tools/listinto 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.Additional context
Environment — this is the VS Code Insiders Agent window, not the terminal TUI. Reproducing by running
copilotin a shell may not exercise the same path; nocopilotinvocation is involved.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_catalogwarning 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:
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
initializehas 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 atinitialize. This report is the post-handshake twin — it needs a server that already listed successfully and is actively serving.initializemarks server failed for the whole session with no retry/backoff #4466 — transient 5xx oninitializemarks a server failed for the whole session with no retry/backoff. Same "one transient failure is permanent" family.Tool '…' does not exist) from a different cause (session resume).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.