PrefectHQ/fastmcp · error · ToolError
Error calling tool {name!r}
Error message
Error calling tool {name!r} What it means
When a tool raises an unexpected exception and the server has error masking enabled (_mask_error_details=True), call_tool raises ToolError(f'Error calling tool {name!r}') with the real cause hidden but chained (from e). This protects internal details (stack traces, messages, URLs) from reaching the client while still logging the full exception server-side.
Source
Thrown at fastmcp_slim/fastmcp/server/server.py:1554
"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.
Args:
uri: The resource URI
version: Specific version to read. If None, reads highest version.
run_middleware: If True (default), apply the middleware chain.View on GitHub (pinned to 1f02114297)
Solutions
- Check the server logs — logger.exception records the real traceback
- Temporarily disable error masking (mask_error_details=False) in a dev environment to see the underlying message
- Fix the bug in the tool handler that raised the original exception
- Add explicit handling/validation in the tool for the failing input
- Raise FastMCPError/ToolError inside the tool deliberately — these pass through unmasked
Example fix
// before: unexpected crash leaks nothing and helps no one
def my_tool(q: str) -> str:
return upstream.call(q)['result']
// after: controlled error with safe detail
def my_tool(q: str) -> str:
try:
return upstream.call(q)['result']
except upstream.UpstreamError as e:
raise ToolError(f'Upstream lookup failed: {e}') from e Defensive patterns
Strategy: try-catch
Type guard
def is_masked_tool_error(e: BaseException) -> bool:
return isinstance(e, ToolError) and str(e).startswith("Error calling tool") Try / catch
try:
result = await client.call_tool('my_tool', args)
except ToolError as e:
logger.error('tool failed (masked): %s', e)
result = degraded_response() Prevention
- Check server logs for the real traceback — masking hides it from clients
- Test tools with masking off in dev so failures are visible early
- Validate inputs in the tool signature to catch bad calls as ValidationError
- Raise deliberate ToolError/FastMCPError inside tools for expected failures
When it happens
Trigger: Any unhandled exception inside a tool handler while error masking is on — a bug in the tool function, an unhandled upstream error that is neither 429 nor a timeout, a TypeError from bad internal state.
Common situations: Production deployments with masking enabled; debugging why a tool 'mysteriously' fails — the client only sees the generic message; unexpected input shapes slipping past validation.
Related errors
- Error calling tool {name!r}: {e}
- Authorization failed for tool '{tool_name}': not found or no
- Ambiguous app tool {tool_name!r}: {len(matches)} components
- To decorate a classmethod, use @classmethod above @tool. See
- The function '{fn_name}' has '{params[0]}' as its first para
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/ab16b142e2c2d645.
Report an issue: GitHub.