PrefectHQ/fastmcp · error · MCPError
-32000
-32000
Error message
Tool '{self.name}' execution timed out after {self.timeout}s What it means
When a tool execution exceeds its configured timeout, _run_body catches the anyio TimeoutError and re-raises it as an MCPError with code -32000 and a message naming the tool and the timeout value. This turns a low-level cancellation into a protocol-level error clients can recognize.
Source
Thrown at fastmcp_slim/fastmcp/tools/function_tool.py:425
arguments: dict[str, Any],
*,
strict: bool,
) -> Any:
"""Validate arguments and execute the body, applying any timeout."""
try:
if self.timeout is not None:
try:
with anyio.fail_after(self.timeout):
result = await self._execute(
type_adapter, exec_is_async, arguments, strict=strict
)
except TimeoutError:
logger.warning(
f"Tool '{self.name}' timed out after {self.timeout}s. "
f"Consider using task=True for long-running operations. "
f"See https://gofastmcp.com/servers/tasks"
)
raise MCPError(
code=-32000,
message=f"Tool '{self.name}' execution timed out after {self.timeout}s",
) from None
else:
result = await self._execute(
type_adapter, exec_is_async, arguments, strict=strict
)
except PydanticValidationError as e:
# Body errors are re-raised as _ToolBodyError, so a bare pydantic
# ValidationError here is an argument-validation failure (a bad call).
# Convert it to fastmcp's ValidationError so the middleware chain and
# downstream error taxonomy (e.g. Sentry filters) can treat it as a
# client error rather than a server bug.
raise ValidationError(str(e), log_level=logging.WARNING) from e
except _ToolBodyError as e:
# The tool's own body raised a pydantic ValidationError. Surface the
# original so it is treated as a server-side error, hiding the
# internal sentinel while preserving the error's own chained cause.View on GitHub (pinned to 1f02114297)
Solutions
- Increase tool.timeout to accommodate realistic execution time
- Optimize the tool body (cache, pagination, async I/O instead of blocking calls)
- Run the call with task=True as a background task for long-running operations
- Handle MCPError code -32000 client-side with retry/backoff if the operation is transiently slow
Example fix
// before
FunctionTool.from_function(slow_api_call, timeout=2.0)
// after
FunctionTool.from_function(slow_api_call, timeout=30.0)
// or for long ops:
await client.call_tool("slow_api_call", {...}, task=True) Defensive patterns
Strategy: try-catch
Validate before calling
# before calling, ensure the operation fits the budget
import time
t0 = time.monotonic()
probe = quick_health_check() # optional precheck
if time.monotonic() - t0 > tool.timeout * 0.5:
raise RuntimeError('downstream too slow for tool timeout') Try / catch
from mcp.types import McpError
try:
result = await client.call_tool('my_tool', args)
except McpError as e:
if e.error.code == -32000 and 'timed out' in e.error.message:
result = await retry_with_backoff(lambda: client.call_tool('my_tool', args)) Prevention
- Budget timeouts from measured p95 latency, not guesses
- Use task=True for operations that may exceed ~10s
- Monitor for -32000 occurrences to right-size timeouts
- Avoid blocking sync calls inside async tools
When it happens
Trigger: Calling tool.run()/calling the tool with arguments whose execution time exceeds tool.timeout; sync functions dispatched to a worker thread (or async functions) that block or take longer than the configured timeout.
Common situations: Slow downstream HTTP calls or DB queries inside a tool; large inputs causing long computation; timeout set optimistically low; forgetting the suggested task=True pattern for long-running work.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Tool {func_name!r}: timeout cannot be enforced when run_in_t
- User server did not start on port {mcp_port}
- The device authorization request expired
- Could not lock CLI state
- OAuth callback timed out after {self._callback_timeout} seco
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/f934a1f35cba4626.
Report an issue: GitHub.