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

  1. Check the server logs — logger.exception records the real traceback
  2. Temporarily disable error masking (mask_error_details=False) in a dev environment to see the underlying message
  3. Fix the bug in the tool handler that raised the original exception
  4. Add explicit handling/validation in the tool for the failing input
  5. 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

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


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