[BUG] Cowork MCP client times out long-running tool calls despite server progress notifications
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?
cowork-mcp-progress-timeout-bug.md
What Should Happen?
When Claude Code (or any host built on it, including Cowork's local-agent
mode) invokes an MCP tools/call against a spec-compliant server that
emits notifications/progress, the request should remain alive as long
as the server is making progress — exactly as the MCP spec and the@modelcontextprotocol/sdk Client are designed to support.
Specifically:
- The MCP client should call
client.callTool(params, schema, options)
with onprogress and resetTimeoutOnProgress: true populated in
options. This causes the SDK to (a) attach a _meta.progressToken
to the outbound tools/call request, and (b) reset the per-request
timer each time a correlated notifications/progress arrives.
- The
MCP_TOOL_TIMEOUTenv var (already documented for Claude Code)
should set the idle timeout between progress notifications, not a
hard wall-clock cap on the total call. A separate maxTotalTimeout
knob (also supported by the SDK) can serve as the optional safety net.
- When the host does time out and emits
notifications/cancelled, any
result that subsequently arrives for the cancelled id over the
transport should be surfaced as a warning in debug logs at minimum —
today it is silently discarded, masking the fact that the work
actually completed.
Net effect for users: long-running MCP tools (LLM-backed servers, large
analytics queries, agentic loops) work correctly out of the box, with
no need to hack around the host's accounting via async polling patterns
or custom timeout env vars.
Error Messages/Logs
Symptom (user-visible error from host):
MCP error -32001: Request timed out
Reproducible via stdio tee shim wrapping mcp-remote between Claude Code
host and remote MCP server. All internal identifiers redacted.
=== Outbound JSON-RPC from host (in.jsonl) ===
{"method":"initialize","params":{"protocolVersion":"2025-11-25",
"capabilities":{"roots":{"listChanged":true},
"extensions":{"io.modelcontextprotocol/ui":{"mimeTypes":["text/html;profile=mcp-app"]}}},
"clientInfo":{"name":"local-agent-mode-<server-id>","version":"1.0.0"}},
"jsonrpc":"2.0","id":0}
{"method":"notifications/initialized","jsonrpc":"2.0"}
{"method":"tools/list","jsonrpc":"2.0","id":1}
{"method":"notifications/roots/list_changed","jsonrpc":"2.0"}
{"method":"tools/call",
"params":{"name":"<tool-name>","arguments":{"message":"<REDACTED>"}},
"jsonrpc":"2.0","id":2}
^^^ Note: params._meta is ABSENT. No progressToken supplied.
{"jsonrpc":"2.0","method":"notifications/cancelled",
"params":{"requestId":2,
"reason":"McpError: MCP error -32001: Request timed out"}}
=== Inbound JSON-RPC to host (out.jsonl) ===
{"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26", ...}}
{"jsonrpc":"2.0","id":1,"result":{"tools":[...]}}
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text",
"text":"<REDACTED — valid full response payload>"}]}}
^^^ Note: the result for the cancelled request id arrived intact AFTER
the host issued notifications/cancelled. Host discarded it silently.
=== mcp-remote bridge stderr (err.log) ===
[pid] Connected to remote server using StreamableHTTPClientTransport
[pid] Local STDIO server running
[pid] Proxy established successfully between local STDIO and remote
StreamableHTTPClientTransport
[pid] [Local→Remote] initialize
[pid] [Remote→Local] 0
[pid] [Local→Remote] notifications/initialized
[pid] [Local→Remote] tools/list
[pid] [Remote→Local] 1
[pid] [Local→Remote] notifications/roots/list_changed
[pid] [Local→Remote] tools/call
[pid] [Local→Remote] notifications/cancelled
[pid] [Remote→Local] 2
[pid]
Shutting down...
=== Independent verification: server emits compliant progress ===
Direct curl against the same MCP endpoint with an explicit progressToken
streams notifications/progress every ~5s with the matching token,
interleaved with SSE ": keepalive" comments. Sample frames:
data: {"jsonrpc":"2.0","method":"notifications/progress",
"params":{"progressToken":"probe-1","progress":1,
"message":"Processing your request..."}}
: keepalive
data: {"jsonrpc":"2.0","method":"notifications/progress",
"params":{"progressToken":"probe-1","progress":2,
"message":"Still working on it, please wait..."}}
...
Server completes well within an extended client timeout (e.g.
`curl --max-time 300`). Confirms the transport, ingress, LB, and server
are healthy — defect is exclusively in the host's invocation of
client.callTool (no onprogress / no resetTimeoutOnProgress).
=== Versions ===
- mcp-remote: 0.1.37
- Transport: StreamableHTTPClientTransport, http-first strategy
- MCP protocol negotiated: 2025-03-26 (server) vs 2025-11-25 (client advertised)
- Host: Claude Code (via Cowork local-agent-mode wrapper; same code path)
Steps to Reproduce
- Configure any remote MCP server in Cowork that has at least one tool whose execution typically exceeds 30s (e.g., an LLM-backed
send_message-style tool that fans out to internal sub-tools). - Ensure the server emits
notifications/progresswith the matchingprogressTokenand proper SSE framing — verifiable independently via a directcurlagainst the server endpoint with_meta.progressTokenset in thetools/callrequest. - Wrap the
mcp-remoteinvocation with a stdio tee shim so both directions of JSON-RPC are captured:
``bash``
#!/usr/bin/env bash
set -uo pipefail
LOG_DIR="$HOME/mcp-debug"; mkdir -p "$LOG_DIR"
TS=$(date +%Y%m%d-%H%M%S)-$$
exec tee -a "$LOG_DIR/in-$TS.jsonl" \
| npx -y mcp-remote@latest "$@" 2>>"$LOG_DIR/err-$TS.log" \
| tee -a "$LOG_DIR/out-$TS.jsonl"
- In Cowork, invoke the slow tool. Observe
MCP error -32001: Request timed outin the UI. - Inspect the captured
in-*.jsonlandout-*.jsonl.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.58 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
_No response_
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗