{"record":{"id":"32946929a09ccb69","repo":"PrefectHQ/fastmcp","slug":"error-fetching-url-last-error","errorCode":null,"errorMessage":"Error fetching {url}: {last_error}","messagePattern":"Error fetching (.+?): (.+?)","errorType":"exception","errorClass":"SSRFFetchError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/ssrf.py","lineNumber":538,"sourceCode":"                    chunks.append(chunk)\n\n                return SSRFFetchResponse(\n                    content=b\"\".join(chunks),\n                    status_code=response.status_code,\n                    headers=dict(response.headers),\n                )\n\n        except httpx2.TimeoutException as e:\n            last_error = e\n            continue\n        except httpx2.RequestError as e:\n            last_error = e\n            continue\n\n    if last_error is not None:\n        if isinstance(last_error, httpx2.TimeoutException):\n            raise SSRFFetchError(f\"Timeout fetching {url}\") from last_error\n        raise SSRFFetchError(f\"Error fetching {url}: {last_error}\") from last_error\n\n    raise SSRFFetchError(f\"Error fetching {url}: no fetch targets succeeded\")\n","sourceCodeStart":520,"sourceCodeEnd":541,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/ssrf.py#L520-L541","documentation":"When all fetch targets fail with a non-timeout httpx RequestError (connection refused, TLS failure, DNS error at connect time, etc.), ssrf_safe_fetch_response raises SSRFFetchError('Error fetching {url}: {last_error}') chained from the final underlying exception. The message embeds the last error seen across all tried targets.","triggerScenarios":"Connection refused (port closed), TLS certificate verification failure (verify=True is always on), SSL handshake errors, network unreachable, or proxy connection errors — on every pinned IP.","commonSituations":"Fetching HTTPS metadata from a host with a self-signed/expired certificate; wrong port in the URL; server actively refusing connections (service not running); corporate proxy interference in proxy mode; stale DNS to a torn-down host.","solutions":["Read the chained cause (e.__cause__) or the embedded last_error for the specific network failure.","Check certificate validity of the target host (expired, self-signed, missing chain) — verification cannot be disabled by design.","Confirm the URL's scheme/port is correct and the service is listening.","If behind a proxy environment, test whether trust_env proxy settings interfere with the pinned-IP connection.","Catch SSRFFetchError and apply retry-with-backoff for transient connection errors."],"exampleFix":"// before\ncontent = await ssrf_safe_fetch(issuer_url)  # SSRFFetchError: ... certificate verify failed\n// after\ntry:\n    content = await ssrf_safe_fetch(issuer_url)\nexcept SSRFFetchError as e:\n    if \"certificate\" in str(e):\n        raise RuntimeError(f\"TLS misconfiguration at {issuer_url}\") from e\n    raise","handlingStrategy":"try-catch","validationCode":"# pre-check TLS out-of-band\nimport ssl, socket\nctx = ssl.create_default_context()\nwith socket.create_connection((host, 443), timeout=5) as s:\n    with ctx.wrap_socket(s, server_hostname=host):\n        pass  # raises ssl.SSLError on bad certs before the guarded fetch","typeGuard":null,"tryCatchPattern":"try:\n    content = await ssrf_safe_fetch(url)\nexcept SSRFFetchError as e:\n    logger.error(\"fetch failed: %s (cause: %r)\", e, e.__cause__)\n    raise","preventionTips":["Keep certificates valid and include the full chain","Test the exact scheme/port with curl before wiring URLs in","Inspect e.__cause__ (the httpx.RequestError) for the precise network failure"],"tags":["network","tls","ssrf","httpx","connection"],"backgroundTag":"connection-refused","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}