Skip to content

feat(client): add idle timeout to SSE stream reader#1963

Open
MukundaKatta wants to merge 1 commit intomodelcontextprotocol:mainfrom
MukundaKatta:feat/sse-idle-timeout
Open

feat(client): add idle timeout to SSE stream reader#1963
MukundaKatta wants to merge 1 commit intomodelcontextprotocol:mainfrom
MukundaKatta:feat/sse-idle-timeout

Conversation

@MukundaKatta
Copy link
Copy Markdown

Why

StreamableHTTPClientTransport._handleSseStream reads from the SSE stream in a tight loop:

while (true) {
  const { value: event, done } = await reader.read();
  ...
}

If the connection goes silent (half-open TCP, proxy timeout, server crash mid-stream), reader.read() blocks forever. There is no chunk timeout, no idle timer, and the caller's AbortSignal does not reach this loop — send() builds its own _abortController.signal for the fetch, so a prompt-level timeout never recovers the stream. The agent shows "thinking" indefinitely until the user kills it.

This is the explicit-timeout knob requested in #1883.

What

Adds an opt-in idleTimeoutMs option to StreamableHTTPClientTransport:

  • If unset (default), nothing changes — same behavior as today.
  • If set, a per-chunk inactivity timer is armed before each reader.read() and reset on every chunk arrival. If the timer fires, the reader is cancel()ed with a clear SSE idle timeout after Nms error. The cancellation makes the next read reject, which falls through to the existing catch path — same as a network drop — so reconnection logic continues to work normally.
  • Constructor validates the value (positive, finite) and throws otherwise.
  • A finally block clears any pending timer on every exit path so we never leak handles.

The change is small (~30 LOC in streamableHttp.ts, plus tests and a changeset).

Tested

Three new tests in packages/client/test/client/streamableHttp.test.ts under describe('idleTimeoutMs'):

  • Constructor rejects 0, negative, Infinity, and NaN.
  • Default (no idleTimeoutMs) leaves a silent stream alone — onerror is never called, preserving today's behavior.
  • A silent SSE source triggers onerror with SSE idle timeout after Nms within the configured window.
  • A stream that keeps producing chunks within the window does not trigger the timeout (timer reset works).

Notes

Related to #1959 / #1961 (open) which targets the same file but a different bug — that PR releases the reader lock to fix a ~50MB memory leak when the underlying socket disconnects without done: true. This PR addresses the explicit timeout option asked for in #1883.

Closes #1883.

Adds an optional `idleTimeoutMs` to `StreamableHTTPClientTransport`. When set,
the SSE stream reader cancels itself if no chunk arrives within the configured
window, and the existing disconnect/reconnect path runs (same as a network
drop). The timer resets on every chunk, so this is a per-chunk inactivity
timeout, not a total stream lifetime.

Without this option, `reader.read()` blocks indefinitely when the server
stalls (half-open TCP, proxy timeout, server crash mid-stream). The caller's
AbortSignal does not reach the SSE reader, so prompt-level timeouts cannot
recover the agent.

Defaults to undefined (no timeout, behavior unchanged).

Closes modelcontextprotocol#1883.
@MukundaKatta MukundaKatta requested a review from a team as a code owner April 26, 2026 21:42
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 26, 2026

🦋 Changeset detected

Latest commit: 7794974

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@modelcontextprotocol/client Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 26, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@1963

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@1963

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@1963

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@1963

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@1963

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@1963

commit: 7794974

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: add idle timeout to SSE stream reader in StreamableHTTPClientTransport

1 participant