[BUG] Claude Desktop: tools/call discarded when the chat is switched before dispatch
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
This is a Claude Desktop bug. Filed here because Anthropic support directs Desktop reports to this repository and this is the only form available.
A tool call reaches the MCP server only if its chat is the active chat at the moment of dispatch. Between submitting the prompt and the dispatch, 5–45 seconds pass — with deferred tools the model runs tool_search first. Open a different chat in that window and the call is discarded: nothing is written to the server, no retry, no log entry anywhere in the client. Four minutes later the chat says:
No result received from the Claude Desktop app after waiting 4 minutes. The local MCP server providing this tool may be unresponsive, crashed, or not running. Further calls to this tool are likely to time out the same way; consider using an alternative approach or ask the user to restart their local MCP servers.
All three claims are false — the same server answered a control call in under 2 ms, 13 seconds after that message appeared, on the same process.
That text is not only shown to the user: it is delivered to the model as the tool result, and it ends with an instruction — "consider using an alternative approach or ask the user to restart their local MCP servers." The model follows it. In this run it replied that the server appeared "unresponsive or not running" and suggested restarting Claude Desktop's MCP servers. The wrong diagnosis is not merely displayed, it is propagated, and the suggested restart destroys the evidence.
Every call submitted while staying in the chat was delivered. Every call where I left the chat before dispatch was discarded.
Not a duplicate of #22451. That one was closed on 2026-05-25 — "the fix shipped in February", with an invitation to reopen on a current build — and then auto-locked, so the invitation cannot be taken up. The February fix addressed a collapsed tool box; the trigger here is leaving the chat, and it survives that fix six months later.
#66726 carries the same signature in June 2026 with logs from both sides, and was auto-closed as a duplicate of the fixed ticket by a bot. That thread states there is "no deterministic trigger". This report supplies one.
Also not the July 2026 dispatch outage (#79926, #79971, #80002). That one rejected the whole tools/list response when a tool declared $schema, so no call was ever dispatched, it failed instantly with "Tool execution failed", and it resolved server-side on 2026-07-23. Here the tool list was accepted and eight calls from it were dispatched normally on 2026-08-04; the tools declare no $schema.
What Should Happen?
A submitted tool call should be dispatched regardless of which chat is displayed when the dispatch happens.
Failing that, three lesser fixes would each remove most of the damage:
- Do not blame the local MCP server, and do not instruct the model to have the user restart it. The server is provably uninvolved — no bytes ever reach it.
- Log the discard. Discarded calls produce zero lines in
mcp.log,main.logand the renderer log, so neither a user nor support can diagnose this from the client's own output. - Fail fast, or retry. Four minutes of silence followed by a wrong diagnosis is the worst of both.
Error Messages/Logs
From an instrumented MCP server. Its capture and the client's own mcp.log agree to
the millisecond on every delivered call, and both are empty for every discarded one.
DELIVERED — probe-kilo, submitted, then switched to another APPLICATION
capture 15:06:23.493 parsed in id=8 echo {"text":"probe-kilo"}
capture 15:06:23.493 write out id=8
capture 15:06:23.494 flushed id=8
mcp.log 2026-08-04T15:06:23.492Z [mcp-debug] Message from client: method="tools/call" id=8
mcp.log 2026-08-04T15:06:23.494Z [mcp-debug] Message from server: id=8 result(1 blocks)
The dispatch happened 43 s AFTER the user left the application, with the window
fully occluded. Window visibility and background throttling are not the factor.
DISCARDED — probe-juliett, submitted, then switched to another CHAT
No line in the capture. None in mcp.log. None in main.log. No tool_approval_gate
entry in claude.ai-web.log. The call exists only as a tool block in the UI and,
four minutes later, as an error message.
DISCRIMINATOR — three calls, one variable each
call after submitting, switched to returned after result
------------- ----------------------------- -------------- ---------------
probe-kilo another application never delivered, 1 ms
probe-lima another chat 3 s delivered, 1 ms
probe-juliett another chat ~60 s discarded
Three seconds away is harmless: the dispatch had not happened yet.
One minute away is fatal: it had.
RULED OUT — each with the measurement that refuted it
server crashed, hung or restarted
control calls answered in under 2 ms between the failures, and an in-process
counter ran 0 -> 1 -> 2 -> 3 unbroken over 41 minutes
back-pressure or a blocked write
zero blocked writes; the detector does fire — against a deliberately
non-reading consumer it reported 24 of 24 writes blocked
approval dialog holding the call
with the tool set to "always allow" it got worse, not better: nothing was
delivered in that run
deferred tool loading (tool_search)
same result with a tool already in the loaded set
tool identity, message size, handler duration
ping, echo and slow_echo behave identically; 86-123 B requests,
0-102 ms handlers
Electron/Chromium background throttling
see above: dispatched 43 s after the window was occluded
Steps to Reproduce
One chat is enough, and no dependencies are needed. Save this as echo-server.js — it is a complete MCP server whose stderr goes to ~/Library/Logs/Claude/mcp-server-echo-min.log, so the log itself shows whether a call arrived:
// Minimal MCP server over stdio. No dependencies. One tool: echo.
const send = m => process.stdout.write(JSON.stringify(m) + '\n');
let buf = '';
process.stdin.on('data', chunk => {
buf += chunk;
let i;
while ((i = buf.indexOf('\n')) >= 0) {
const line = buf.slice(0, i); buf = buf.slice(i + 1);
if (!line.trim()) continue;
console.error(new Date().toISOString(), 'IN', line);
const m = JSON.parse(line);
if (m.method === 'initialize') send({ jsonrpc: '2.0', id: m.id, result: {
protocolVersion: m.params.protocolVersion,
capabilities: { tools: {} },
serverInfo: { name: 'echo-min', version: '1.0.0' } } });
else if (m.method === 'tools/list') send({ jsonrpc: '2.0', id: m.id, result: {
tools: [{ name: 'echo', description: 'Returns its text argument.', inputSchema: {
type: 'object', properties: { text: { type: 'string' } }, required: ['text'] } }] } });
else if (m.method === 'tools/call') send({ jsonrpc: '2.0', id: m.id, result: {
content: [{ type: 'text', text: String(m.params.arguments.text) }] } });
else if (m.id !== undefined) send({ jsonrpc: '2.0', id: m.id,
error: { code: -32601, message: 'not implemented' } });
}
});
Add it to claude_desktop_config.json:
{
"mcpServers": {
"echo-min": {
"command": "/absolute/path/to/node",
"args": ["/absolute/path/to/echo-server.js"]
}
}
}
Both paths must be absolute. command is the output of which node (where node on Windows) — a bare "node" is resolved against a PATH the app does not necessarily share with your shell.
Tested with Node v25.9.0. The script uses nothing version-specific, so any Node that Claude Desktop can launch should do.
Then:
- Restart Claude Desktop, then open Customize → Connectors → echo-min and set the
Echotool to Always allow. Otherwise the approval dialog holds you in the chat and suppresses the failure. - Open a new chat and submit: Call the
echotool with the textprobe-1. Answer with nothing but the tool's result. - Immediately open a different chat. Do not wait to see whether anything happens — two seconds of watching can be enough for the call to go through.
- Stay away until the failure appears. The dispatch happens 5–45 s after submission and you have to be gone when it does — coming back early can let the call through and cost you the run.
- Return to the first chat and watch
~/Library/Logs/Claude/mcp-server-echo-min.log.
Expected: a tools/call line in the log and the result in the chat.
Actual: no tools/call line ever appears. After about four minutes the chat reports that the local MCP server may be unresponsive, crashed or not running. Returning to the chat does not recover it.
Two contrasts, both of which succeed — without them a single failed run proves nothing:
- Leave for another application instead of another chat, for the same 60 seconds. The call is delivered while the window is occluded.
- Leave for another chat for three seconds, then come back. The call is delivered, because the dispatch had not happened yet.
One condition matters. With only a handful of connected tools the dispatch is nearly immediate and there is barely any window to leave in. The run above had 6 servers and 177 tools in total, so tools were deferred and the model called tool_search first — that is what stretches the delay to tens of seconds. If it does not reproduce, add servers and repeat.
A fuller instrumented server, the complete protocol and the captured run are at https://github.com/rsascha/mcp-debug-claude-desktop.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
Claude Desktop 1.24012.11, Electron 42.7.0
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Other
Additional Information
Repository demonstrating the issue: https://github.com/rsascha/mcp-debug-claude-desktop
It contains an instrumented MCP server, the reproduction protocol, and the complete capture of the run quoted above in samples/reference-run-2026-08-04/. Running npm run analyse -- samples/reference-run-2026-08-04 prints the per-message table from it.
Screenshot: the failure as the user sees it, and what the model then does with it — the error text is delivered as the tool result, and the model relays its restart advice into the conversation.
Configuration: nothing beyond the four-line mcpServers entry in the reproduction steps is needed. The failure occurs with a single connected server, though a larger tool count widens the window in which it can be triggered.
One distinction I cannot resolve
#66726 describes a wedge: all servers stall simultaneously and only a full restart recovers. What I measured is a per-call race — control calls succeed in the same window, seconds after a failure, with no restart. These may be two defects sharing one symptom, or two severities of one. I cannot tell from outside the client and do not claim to.
Limits of this measurement
- The dispatch path inside the client is not observable from outside it. "Never dispatched" is inferred from the absence of any request at the MCP layer and of any entry in the client's own logs — not from watching the dispatch.
- The exact moment of dispatch cannot be observed before it happens, so the boundary between safe and unsafe was established by outcome, not by direct measurement.
- One machine, one OS, one app version, 6 servers, deferred tools active. Whether any of those is a necessary condition has not been tested.
- Sixteen calls is enough to show the pattern without exception, not enough to quantify a failure rate.
Related
- #22451 — canonical, closed as fixed in February, now locked
- #66726 — same signature, two-sided logs, auto-closed as a duplicate of the above by a bot
- modelcontextprotocol#816 — "NO
tools/callentry anywhere", tied to starting a new conversation, no response - #22127 — the return path gated on UI interaction: results not rendered until the user expands the tool panel. Closed not planned
- #79926, #79971, #80002 — the July 2026 outage:
tools/listrejected by client-side Zod validation when a tool declares$schema. Total, instant, resolved server-side on 2026-07-23. Listed to keep the two apart
<img width="1137" height="1104" alt="Image" src="https://github.com/user-attachments/assets/fc94e9c6-9808-4019-9512-206e9e1f0900" />