AgentFixboard

SSE connect retries a 401 forever when onUnauthorized keeps resolving

SSEClientTransport.start() never settles when the EventSource GET keeps returning 401 and onUnauthorized() keeps resolving. The transport rebuilds the EventSource with no cap.

From GitHub

modelcontextprotocol/typescript-sdk

Difficulty
Easy
Time
~5 min
Steps
2
Fixed in
@modelcontextprotocol/client@2.3.0
Last checked

Verified fix: PR #2905 merged on . Fixed in @modelcontextprotocol/client@2.3.0 (first published release whose tag contains the merge commit). Last checked .

Symptom

SSEClientTransport.start() never settles when the EventSource GET keeps returning 401 and onUnauthorized() keeps resolving. The transport rebuilds the EventSource with no cap.

Evidence what you'll see

  • On main at dd22ba25, a local server that always answers 401 and a provider that always resolves measured EventSource GET attempts (1.5 s) : 25 and onUnauthorized() invocations : 25, with start() never settled. The 25 was a backstop; without it the event loop is starved.
  • The same transport's POST path stops: POST attempts : 2, onUnauthorized() invocations : 1, send() rejected with SdkHttpError: Server returned 401 after re-authentication.
  • A comment on 30 Sep 2026 reproduced the loop at attempts: 13873 before pull request 2905, and attempts: 2 with SdkHttpError: Server returned 401 after re-authentication after it.

Why it happens

The documented contract on onUnauthorized is retry once, then throw. The SSE stream called _startOrAuth() fresh on every 401, so a resolving no-op provider rebuilt the connection forever. Pull request 2905 adds _connectAuthRetried, retries once, and on a second 401 before the stream opens rejects with SdkHttpError (ClientHttpAuthentication, Server returned 401 after re-authentication). The guard clears when the stream opens, so a later reconnect still gets one refresh.

Affected versions

@modelcontextprotocol/sdk on main at dd22ba25, Node 22, macOS, local HTTP server. The reproduction in the 30 Sep 2026 comment imports SSEClientTransport from @modelcontextprotocol/client.

Fix steps

0 of 2 done

Your check marks stay in this browser only.

How to check it worked

  • Against a server that always returns 401, start() rejects with SdkHttpError: Server returned 401 after re-authentication, onUnauthorized runs once, and there are two EventSource GETs.

If it still fails

  • A later 401 after the stream has opened still refreshes once. The pull request says that is intended.
  • The linked issue does not describe an unbounded 401 loop on @modelcontextprotocol/client 2.3.0.

Sources and credit

Summarized in our own words from these public reports. Credit goes to their authors.

Provided as is, without warranty. Try changes in a non-production environment and back up first. Security-related steps: verify against the official docs. Terms · Request removal or attribution change

Hit an error we don't cover? Submit it