fix: reset the relay's request-response waits on progress notifications (#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>
This commit is contained in:
cliffhall 2026-09-13 21:13:30 -04:00
commit cd31d76371

View file

@ -0,0 +1,33 @@
/**
* Shared helper for the remote relay's progress-aware request-response waits
* (#2028). Pure and browser-safe, so both the browser `RemoteClientTransport`
* and the Node `RemoteSession` import it rather than each carrying a copy.
*/
import type { JSONRPCMessage } from "@modelcontextprotocol/client";
/**
* The id of the request a `notifications/progress` belongs to, or `undefined`
* for any other message. The SDK stamps `progressToken: messageId` on the
* outbound request, so a progress notification's `progressToken` is the id of
* the request whose wait should be re-armed. A JSON-RPC progress token is a
* string or a number; anything else is ignored so a malformed token can't be
* coerced into a map key.
*/
export function progressTokenOf(
message: JSONRPCMessage,
): string | number | undefined {
if (
"method" in message &&
message.method === "notifications/progress" &&
"params" in message &&
typeof message.params === "object" &&
message.params !== null
) {
const token = (message.params as { progressToken?: unknown }).progressToken;
if (typeof token === "string" || typeof token === "number") {
return token;
}
}
return undefined;
}