Skip to content

feat(client): honor Retry-After on HTTP 429 responses#1965

Open
MukundaKatta wants to merge 2 commits intomodelcontextprotocol:mainfrom
MukundaKatta:feat/honor-retry-after-429
Open

feat(client): honor Retry-After on HTTP 429 responses#1965
MukundaKatta wants to merge 2 commits intomodelcontextprotocol:mainfrom
MukundaKatta:feat/honor-retry-after-429

Conversation

@MukundaKatta
Copy link
Copy Markdown

Why

Closes #1892. StreamableHTTPClientTransport falls through 429 to a generic SdkError, so any client talking to a server that rate-limits with 429 Too Many Requests (e.g. the express-rate-limit default, the MiYo Kado MCP gateway, Cloudflare-fronted servers) crashes on the very first throttled response instead of waiting and retrying. Every consumer of this SDK has to reinvent client-side rate-limit handling.

What

  • Parse the Retry-After header on 429 (delta-seconds and HTTP-date forms per RFC 7231 §7.1.3) inside both the POST _send path and the GET _startOrAuthSse path.
  • Sleep for the indicated duration (clamped to a configurable ceiling) and retry the original request, up to a configurable number of times.
  • Fall back to a configurable defaultRetryAfterMs when the header is missing or unparsable.
  • After the cap is hit, throw a typed SdkErrorCode.ClientHttpRateLimited with the original Retry-After value attached to error.data so callers can react.
  • New rateLimitOptions transport option (maxRetries, defaultRetryAfterMs, maxRetryAfterMs). Defaults: 3 retries, 1s fallback, 60s cap. Set maxRetries: 0 to opt out entirely.
  • Honours the existing AbortController so transport.close() cancels in-flight Retry-After sleeps.

Tested

  • vitest unit tests in packages/client/test/client/streamableHttp.test.ts covering:
    • numeric Retry-After: 2 waits ~2s then succeeds
    • HTTP-date Retry-After is parsed and respected
    • missing Retry-After falls back to defaultRetryAfterMs
    • oversized Retry-After is clamped to maxRetryAfterMs
    • 4 consecutive 429s (3 retries) throw ClientHttpRateLimited
    • maxRetries: 0 disables retries
    • static _parseRetryAfter helper across all input shapes (null, undefined, empty, garbage, integer, fractional, future/past HTTP-date)
  • Existing StreamableHTTPClientTransport test suite still passes (no logic changes outside the new 429 branch).

StreamableHTTPClientTransport now treats 429 Too Many Requests as a
retryable signal. When the server returns 429 the transport parses the
Retry-After header (delta-seconds or HTTP-date per RFC 7231 §7.1.3),
waits for the indicated duration (clamped to a configurable ceiling),
and retries the original POST/GET. After a configurable number of
consecutive 429 responses the transport surfaces a typed
SdkErrorCode.ClientHttpRateLimited so callers can react.

Behaviour is configurable via the new rateLimitOptions transport
option. Setting maxRetries to 0 disables automatic retries entirely
for applications that want to handle rate limiting themselves.

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

changeset-bot Bot commented Apr 26, 2026

🦋 Changeset detected

Latest commit: 0d047ed

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

This PR includes changesets to release 2 packages
Name Type
@modelcontextprotocol/client Minor
@modelcontextprotocol/core Patch

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@1965

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: 0d047ed

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.

Client transport: HTTP 429 responses are not retried with Retry-After

1 participant