fix(fetch): retry transient errors with exponential backoff - #4637
Open
magai2002 wants to merge 1 commit into
Open
fix(fetch): retry transient errors with exponential backoff#4637magai2002 wants to merge 1 commit into
magai2002 wants to merge 1 commit into
Conversation
The fetch server returned 429/5xx and network errors straight to the agent with no retry, forcing every caller to reimplement its own retry logic for errors that a short backoff would usually clear. Retry 429/500/502/503/504 and network errors with exponential backoff: 3 attempts by default, 1s initial delay, doubling each retry, capped at 10s. A numeric Retry-After header on a 429 is honored over the backoff delay. Non-retryable statuses (404, 401, etc.) still fail immediately. Configurable via FETCH_MAX_RETRIES and FETCH_RETRY_DELAY_MS env vars. Closes modelcontextprotocol#4449
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The
fetchMCP server returns transient HTTP errors (429,5xx) and network errors straight to the calling agent, with no retry. A short backoff would clear most of these, but every caller has to reimplement that logic itself.Server Details
fetchtool and prompt, environment variablesMotivation and Context
Closes #4449. Transient errors are the norm for public APIs and documentation sites under load. Handling retries in the server keeps agent prompts clean and avoids wasted LLM turns handling errors a retry would have resolved.
429,500,502,503,504, and network-level errors (connection reset, timeout, DNS failure, etc. — anythinghttpxraises asHTTPError)400,401,403,404,405,409,410,422, ...) — fails on the first attempt, no wasted delayRetry-Afterheader on a429response overrides the backoff delay for that wait; a non-numeric value (e.g. an HTTP-date) falls back to the computed backoffFETCH_MAX_RETRIESandFETCH_RETRY_DELAY_MSenvironment variablesScope note: the fetch server only ever issues GET requests, so the "don't retry non-idempotent methods" concern from the issue doesn't apply here — there's nothing else to opt out of.
How Has This Been Tested?
Retry-Afteroverride, network-error (HTTPError) retry, and direct unit tests of the backoff-delay helper (exponential growth, max-delay cap, numeric vs. non-numericRetry-After).uv run pytest: 29 passed, all fast (asyncio.sleepis mocked so no test actually waits).uv run ruff check .: clean.uv run pyright: 0 errors.FETCH_MAX_RETRIES/FETCH_RETRY_DELAY_MSreachserve()correctly, and that defaults (3 / 1000ms) apply when unset.Breaking Changes
Behavioral, not interface-breaking: a request that previously failed immediately on a transient error will now retry up to 3 times before failing, adding up to ~3s of latency in the worst case. No config changes required for existing users; opt out isn't currently exposed (
FETCH_MAX_RETRIES=1achieves the same effect if needed).Types of changes
Checklist
Additional context
This PR is independent of #4636 (the
--timeoutPR, also againstfetch) — built on a separate branch off currentmain, no shared commits. They touch adjacent but non-overlapping code infetch_url/serve, so either can merge first without blocking the other; happy to rebase if needed.