PrefectHQ/fastmcp · warning · ToolError
Upstream request timed out, please retry
Error message
Upstream request timed out, please retry
What it means
call_tool's exception handler converts any tool exception matching is_timeout_error into ToolError('Upstream request timed out, please retry'). Like the 429 case, timeouts are treated as actionable and reach the client even when _mask_error_details is on, signaling a transient failure worth retrying.
Source
Thrown at fastmcp_slim/fastmcp/server/server.py:1549
if (
isinstance(e, MCPError)
and e.error.code == MISSING_REQUIRED_CLIENT_CAPABILITY
):
logger.debug(
"Tool %r requires a client capability the client did "
"not declare",
name,
)
raise
logger.exception(f"Error calling tool {name!r}")
# Handle actionable errors that should reach the LLM
# even when masking is enabled
if get_http_status_code(e) == 429:
raise ToolError(
"Rate limited by upstream API, please retry later"
) from e
if is_timeout_error(e):
raise ToolError(
"Upstream request timed out, please retry"
) from e
# Standard masking logic
if self._mask_error_details:
raise ToolError(f"Error calling tool {name!r}") from e
raise ToolError(f"Error calling tool {name!r}: {e}") from e
async def read_resource(
self,
uri: str,
*,
version: VersionSpec | None = None,
run_middleware: bool = True,
) -> ResourceResult:
"""Read a resource by URI.
This is the public API for reading resources. By default, middleware is applied.
Checks concrete resources first, then templates.View on GitHub (pinned to 1f02114297)
Solutions
- Retry the tool call — the error is transient by design
- Increase the timeout in the tool's internal HTTP/DB client configuration
- Add circuit-breaking/deadline budgets inside the tool and partial results on timeout
- Check upstream service health/status pages
- Move long work behind an async job pattern instead of blocking the tool call
Example fix
// inside the tool: before
def fetch():
return httpx.get(url)
// after
def fetch():
return httpx.get(url, timeout=httpx.Timeout(30.0)) Defensive patterns
Strategy: retry
Type guard
def is_timeout(e: BaseException) -> bool:
return isinstance(e, ToolError) and 'timed out' in str(e) Try / catch
import asyncio
for attempt in range(3):
try:
return await client.call_tool('my_tool', args)
except ToolError as e:
if 'timed out' not in str(e):
raise
await asyncio.sleep(2 ** attempt)
raise RuntimeError('tool kept timing out') Prevention
- Set generous but bounded timeouts in tool HTTP clients
- Use bounded retries with exponential backoff
- Monitor upstream latency and alert on degradation
- Prefer async job patterns for work that can exceed request deadlines
When it happens
Trigger: A tool's handler makes an upstream network call (HTTP, DB, socket) that times out — httpx.TimeoutException, asyncio.TimeoutError, socket.timeout, etc. — and the exception escapes tool._run.
Common situations: Upstream API slow or degraded; too-short client timeouts in the tool's HTTP client; large payloads/slow queries; network partitions between the MCP server and its dependencies.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- User server did not start on port {mcp_port}
- OIDC discovery for issuer {issuer!r} recently failed; backin
- Overall timeout exceeded: {url}
- Timeout fetching {url}
- HTTP request timed out ({type(exc).__name__})
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/e6c7166ecfaa73cc.
Report an issue: GitHub.