Repository navigation
fix: reset relay request-response waits on progress notifications (#2028) - #2363
Conversation
…ns (#2028) The web client drives its MCP Client through a two-hop relay (RemoteClientTransport -> Node backend -> upstream transport). The relay added two request-response waits that the direct transports don't have — the backend's RemoteSession.waitForRequestResponse and the browser's RemoteClientTransport.waitForSseResponse — and both were hardcoded to 60s and blind to notifications/progress. The SDK Client's own request timeout resets on progress (resetTimeoutOnProgress, which the Inspector enables), but the relay's progress-blind waits fire first and shadow it, so a long-running tool that reports progress is cancelled at 60s on the web client alone while it completes over the CLI/TUI and other MCP clients. Reset both relay waits on a notifications/progress whose progressToken matches the pending request id, mirroring the SDK's resetTimeoutOnProgress. A shared browser-safe progressTokenOf helper extracts the token. Log messages (notifications/message, i.e. ctx.info) deliberately do not reset the deadline, matching the SDK and every other client. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp Signed-off-by: cliffhall <cliff@futurescale.com>
There was a problem hiding this comment.
🟡 Changes recommended
Progress-token validation accepts invalid numeric tokens, and the new browser timeout tests are timing-dependent.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Fixes #2028 by making both web relay timeout layers restart when matching progress notifications arrive.
Changes:
- Adds shared progress-token extraction.
- Re-arms backend and browser SSE response waits.
- Adds regression and validation tests.
File summaries
| File | Description |
|---|---|
core/mcp/remote/remoteClientTransport.ts |
Resets browser relay waits on progress. |
core/mcp/remote/progressToken.ts |
Extracts progress tokens from notifications. |
core/mcp/remote/node/remote-session.ts |
Resets backend relay waits on progress. |
clients/web/src/test/integration/mcp/remote/remote-session.test.ts |
Tests backend timeout behavior. |
clients/web/src/test/core/mcp/remote/remoteClientTransport.test.ts |
Tests browser relay timeout behavior. |
clients/web/src/test/core/mcp/remote/progressToken.test.ts |
Tests token extraction. |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 5
- Review effort level: Balanced
…ake relay tests (#2028) - progressTokenOf now delegates to the SDK's ProgressTokenSchema instead of a bare typeof check, so fractional and past-MAX_SAFE_INTEGER numbers are rejected (matching isProgressToken in inspectorClient.ts); tests cover both. - Browser relay timeout tests use fake timers with advanceTimersByTimeAsync and flush the SSE consumer between pushes, replacing real-sleep windows that a loaded runner could race (#1596). - The log-only timeout test holds and awaits its rejection rather than swallowing it in an empty catch. - progressToken.test.ts gains a file header; the remote-session malformed-token fixture uses a single-cast helper instead of `as unknown as`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp Signed-off-by: cliffhall <cliff@futurescale.com>
|
Thanks for the review — all five comments addressed in fb28b0f (replied inline on each thread):
I also mutation-checked both regression tests (disabling each relay's reset makes its test fail), so they genuinely detect the fix rather than passing vacuously. |
There was a problem hiding this comment.
🔵 Needs a closer look
A newly added unjustified double cast bypasses type safety and should be removed before approval.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
clients/web/src/test/core/mcp/remote/remoteClientTransport.test.ts:726
- This new
as unknown as typeof fetchis unnecessary because the mock is already declared withvi.fn<typeof fetch>(); it also bypasses the repository's type-safety rule for double casts without a safety justification. Keep the typed mock result directly.
- Files reviewed: 6/6 changed files
- Comments generated: 0 new
- Review effort level: Balanced
…er (#2028) The backendHoldingSend fetch mock is already declared with vi.fn<typeof fetch>(), so the trailing `as unknown as typeof fetch` was redundant and tripped the repo's no-double-cast rule. Return the typed mock directly; the test project typechecks without it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp Signed-off-by: cliffhall <cliff@futurescale.com>
|
Round 2 addressed in 5165316: removed the |
Closes #2028
Problem
A long-running tool that reports progress (
ctx.report_progressin FastMCP) times out at ~60s in the web client, while the same tool completes over the CLI/TUI and in other MCP clients. The web relay is the only surface with the defect.The browser drives its MCP
Clientthrough a two-hop relay: the browser'sRemoteClientTransport→ the Node backend (/api/mcp/send+ the/api/mcp/eventsSSE stream) → the real upstream transport. The relay adds two request-response waits that the direct transports don't have, and both were hardcoded to 60s and blind tonotifications/progress:RemoteSession.waitForRequestResponse(awaited by/api/mcp/send)RemoteClientTransport.waitForSseResponseThe SDK
Client's own request timeout resets on progress (resetTimeoutOnProgress, which the Inspector already enables). But the relay's progress-blind waits fire first and shadow it, so on the web path a progress-reporting call is cancelled at 60s no matter how recently progress arrived.Fix
Make both relay waits reset their deadline on a
notifications/progresswhoseprogressTokenmatches the pending request id — mirroring the SDK'sresetTimeoutOnProgress. A shared, browser-safeprogressTokenOfhelper (core/mcp/remote/progressToken.ts) extracts the token; both waits re-arm their full window when one arrives.Log messages (
notifications/message, i.e.ctx.info) deliberately do not reset the deadline — matching the SDK and every other client, so a purely-logging tool that runs past the timeout still times out as before.Not addressed (and why)
The issue also reports responses arriving 44–173s late on the reporter's machine while the server's notifications arrive promptly over the same pipe. That is a server-side stall (FastMCP/Python 3.14 on their host), not an Inspector defect: it does not reproduce on the identical package stack (fastmcp 3.4.7 / mcp 1.30.0 / Python 3.14.7) across a raw stdio reader, the CLI, or the full web relay. The Inspector faithfully sends, correlates, and times out per the SDK's 60s default. See the issue comment for the full analysis.
Tests
core/mcp/remote/progressToken.ts— unit tests for every branch of the token extractor.RemoteSession— a progress note re-arms the wait past its original deadline; a log message does not; no-ops for an unknown id, a timerless wait, and a malformed token.RemoteClientTransport— progress keeps the SSE wait alive pastsseResponseTimeoutMs; a log-only stream still times out.Verified end to end against a real FastMCP stdio server over the relay: a 75s progress-reporting call now completes; a 75s log-only call still times out at ~60s.
🤖 Generated with Claude Code
https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp