[BUG] Channels (research preview): async MCP tool calls hang when the same streamable-HTTP server is bound as a channel source
Summary
With the research-preview Channels feature, if a single MCP server exposed over the streamable-HTTP transport is bound as a channel source and also provides normal tools, async tool calls on that connection hang and never return to Claude Code — even though the same calls return in a few milliseconds to standard MCP clients, and the channel notifications themselves inject correctly.
Environment
- Claude Code CLI (terminal), launched with
--dangerously-load-development-channels server:<name>. - The channel source is an MCP server reached over the streamable-HTTP transport (not a stdio server Claude Code spawns).
- That same server declares
capabilities.experimental["claude/channel"] = {}, emitsnotifications/claude/channel, and also registers ordinary tools.
Symptom
- A sync tool call on that connection returns normally.
- An async tool call (one that returns immediately with an id and continues work in the background) never returns its result to Claude Code: the UI reports
tool "…" is still running after 120s→ moved to background → then fails withsent no response or progress for 300s; aborting. - The
notifications/claude/channelevents from the server do inject into the session correctly (the wake works). It is specifically the tool-call response that is not delivered.
Isolation (server is not at fault)
The identical server and identical async call return in 2–4 ms with Content-Type: application/json to:
curl(POSTtools/call, with and without aprogressTokenin_meta), and- the official
@modelcontextprotocol/sdkstreamable-HTTP client (with and without a progress handler; before and after the connection has received a server notification).
Only Claude Code's MCP client hangs, and only when that server is bound as a channel source. This points to the Claude Code streamable-HTTP client's handling of tool-call responses on a connection that is also a channel subscriber (possible head-of-line blocking between the long-lived channel/SSE stream and pending tool-call responses).
Impact
A streamable-HTTP MCP server cannot be both a tool provider and a channel source on one connection — async tool calls to it become unusable. The workaround is to deliver channel events from a separate MCP server/connection, leaving the tool server not bound as a channel.
Repro sketch
- Stand up a streamable-HTTP MCP server that declares
experimental["claude/channel"], registers a tool that returns immediately with a background job id, and emitsnotifications/claude/channelwhen the job finishes. claude --dangerously-load-development-channels server:that-server.- Call the async tool. Observe the 120s→background→300s-abort hang, while a sync tool on the same server returns fine, and while
curl/SDK get the async result in a few ms.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗