unclecode/crawl4ai · error · HTTPException
upstream request timed out
Error message
upstream request timed out
What it means
The MCP bridge wraps upstream calls with an httpx.AsyncClient(timeout=timeout); when the upstream exceeds that timeout, httpx.TimeoutException is caught and re-raised as HTTPException(504, 'upstream request timed out'). The timeout comes from attach_mcp(timeout=...); None means no limit, so the 504 only fires when a finite timeout was configured.
Source
Thrown at deploy/docker/mcp_bridge.py:81
path = path.replace(placeholder, str(v))
kwargs.pop(k)
url = base_url.rstrip("/") + path
headers = _service_auth_headers()
async with httpx.AsyncClient(timeout=timeout) as client:
try:
r = (
await client.get(url, params=kwargs, headers=headers)
if method == "GET"
else await client.request(method, url, json=kwargs, headers=headers)
)
r.raise_for_status()
return r.text if method == "GET" else r.json()
except httpx.HTTPStatusError as e:
# surface FastAPI error details instead of plain 500
raise HTTPException(e.response.status_code, e.response.text)
except httpx.TimeoutException:
raise HTTPException(504, "upstream request timed out")
return proxy
# ── main entry point ────────────────────────────────────────────
def attach_mcp(
app: FastAPI,
*, # keyword‑only
base: str = "/mcp",
name: str | None = None,
base_url: str, # eg. "http://127.0.0.1:8020"
timeout: float | None = None, # httpx timeout in seconds; None = no limit
) -> None:
"""Call once after all routes are declared to expose WS+SSE MCP endpoints."""
server_name = name or app.title or "FastAPI-MCP"
mcp = Server(server_name)
# tools: Dict[str, Callable] = {}
tools: Dict[str, Tuple[Callable, Callable]] = {}
resources: Dict[str, Callable] = {}View on GitHub (pinned to 7e80152142)
Solutions
- Raise attach_mcp(timeout=...) to exceed the slowest tool's expected duration (or None to disable).
- Check upstream service health/latency — fix the slowness rather than only extending the deadline.
- For long jobs, switch to async patterns: submit the job via one tool, poll status via another, so each call is short.
Example fix
# before attach_mcp(app, base_url="http://127.0.0.1:8020", timeout=5.0) # after attach_mcp(app, base_url="http://127.0.0.1:8020", timeout=300.0)
Defensive patterns
Strategy: retry
Try / catch
from fastapi import HTTPException
for attempt in range(3):
try:
return await proxy(**kwargs)
except HTTPException as e:
if e.status_code == 504 and attempt < 2:
await asyncio.sleep(2 ** attempt)
continue
raise Prevention
- Size attach_mcp(timeout=...) above the slowest tool's p99 latency.
- Prefer submit-then-poll job patterns over single long MCP calls.
- Alert on 504 rate from the bridge to catch upstream degradation early.
When it happens
Trigger: A proxied tool call (e.g. a long crawl or LLM job status poll) taking longer than the attach_mcp timeout value; upstream service slow/hung under load; timeout set aggressively (e.g. 5s) for tools that legitimately take minutes.
Common situations: Default proxy timeouts too small for crawl-class operations; upstream saturated so p95 latency crosses the threshold; network stalls between bridge and base_url service.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Failed on navigating ACS-GOTO: {str(e)}
- Request timed out: {str(e)}
- Request timed out: {str(e)}
- Timeout downloading PDF from {url}: {str(e)}
- e.response.text
AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14).
Data as JSON: /api/errors/2fb6a27e0e296234.
Report an issue: GitHub.