{"record":{"id":"2fb6a27e0e296234","repo":"unclecode/crawl4ai","slug":"upstream-request-timed-out","errorCode":null,"errorMessage":"upstream request timed out","messagePattern":"upstream request timed out","errorType":"http","errorClass":"HTTPException","httpStatus":504,"severity":"error","filePath":"deploy/docker/mcp_bridge.py","lineNumber":81,"sourceCode":"                path = path.replace(placeholder, str(v))\n                kwargs.pop(k)\n        url = base_url.rstrip(\"/\") + path\n\n        headers = _service_auth_headers()\n        async with httpx.AsyncClient(timeout=timeout) as client:\n            try:\n                r = (\n                    await client.get(url, params=kwargs, headers=headers)\n                    if method == \"GET\"\n                    else await client.request(method, url, json=kwargs, headers=headers)\n                )\n                r.raise_for_status()\n                return r.text if method == \"GET\" else r.json()\n            except httpx.HTTPStatusError as e:\n                # surface FastAPI error details instead of plain 500\n                raise HTTPException(e.response.status_code, e.response.text)\n            except httpx.TimeoutException:\n                raise HTTPException(504, \"upstream request timed out\")\n    return proxy\n\n# ── main entry point ────────────────────────────────────────────\ndef attach_mcp(\n    app: FastAPI,\n    *,                          # keyword‑only\n    base: str = \"/mcp\",\n    name: str | None = None,\n    base_url: str,              # eg. \"http://127.0.0.1:8020\"\n    timeout: float | None = None,  # httpx timeout in seconds; None = no limit\n) -> None:\n    \"\"\"Call once after all routes are declared to expose WS+SSE MCP endpoints.\"\"\"\n    server_name = name or app.title or \"FastAPI-MCP\"\n    mcp = Server(server_name)\n\n    # tools: Dict[str, Callable] = {}\n    tools: Dict[str, Tuple[Callable, Callable]] = {}\n    resources: Dict[str, Callable] = {}","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/unclecode/crawl4ai/blob/7e801521428ee12509994d39151006f64055ebe3/deploy/docker/mcp_bridge.py#L63-L99","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nattach_mcp(app, base_url=\"http://127.0.0.1:8020\", timeout=5.0)\n\n# after\nattach_mcp(app, base_url=\"http://127.0.0.1:8020\", timeout=300.0)","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"from fastapi import HTTPException\n\nfor attempt in range(3):\n    try:\n        return await proxy(**kwargs)\n    except HTTPException as e:\n        if e.status_code == 504 and attempt < 2:\n            await asyncio.sleep(2 ** attempt)\n            continue\n        raise","preventionTips":["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."],"tags":["mcp","timeout","network","http-proxy"],"backgroundTag":null,"analyzedSha":"7e801521428ee12509994d39151006f64055ebe3","analyzedAt":"2026-08-14T20:46:20.673Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}