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

  1. Retry the tool call — the error is transient by design
  2. Increase the timeout in the tool's internal HTTP/DB client configuration
  3. Add circuit-breaking/deadline budgets inside the tool and partial results on timeout
  4. Check upstream service health/status pages
  5. 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

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

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/e6c7166ecfaa73cc. Report an issue: GitHub.