PrefectHQ/fastmcp · error · ResourceError

Error reading resource

Error message

Error reading resource {uri!r}

What it means

FastMCP raises ResourceError("Error reading resource {uri!r}") when reading a resource raised an unexpected exception and error masking is enabled (`mask_error_details=True`). The original exception is hidden and chained as __cause__; details remain in server logs via logger.exception. It is the library's way of preventing internal details from leaking to LLM clients.

Solutions

  1. Check the server logs for 'Error reading resource <uri>' with the full traceback to find the underlying exception.
  2. Fix the root cause in the resource handler (file path, network endpoint, credentials).
  3. During debugging, disable masking (mask_error_details=False) so the client message includes the underlying error text.
  4. If the failure is expected, raise a FastMCPError/ResourceError subclass from the handler with a client-appropriate message so it passes through unmasked.

Example fix

// before: server = FastMCP(mask_error_details=True)  # client sees only "Error reading resource 'file:///data.csv'"
// after: server = FastMCP(mask_error_details=False)  # or fix handler:
# before
def load():
    return open(MISSING_PATH).read()
# after
@resource("data://csv")
def load():
    try:
        return open(DATA_PATH).read()
    except FileNotFoundError as e:
        raise ResourceError("CSV data file is not yet generated") from e
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure the resource exists before reading
resources = await client.list_resources()
assert any(str(r.uri) == uri for r in resources), f"{uri} not exposed by server"

Try / catch

try:
    result = await client.read_resource(uri)
except ResourceError as e:
    if 'Error reading resource' in str(e):
        logger.error(f"masked resource failure for {uri}; check server logs", exc_info=e)
        raise

Prevention

When it happens

Trigger: Calling `await server.read_resource(uri)` (directly or via `_on_read_resource`/client `read_resource`) where the concrete resource's `_read()` raises a non-FastMCP, non-MCPError exception (e.g. OSError, HTTP error that isn't 429 or a timeout) and the server was constructed with mask_error_details=True.

Common situations: Resource functions hitting a filesystem/DB/network failure in production with error masking on; developers confused why the client shows a generic message while logs hold the real cause; environments where an upstream API URL/key is misconfigured.

Related errors


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

Appendix: source

Thrown at fastmcp_slim/fastmcp/server/server.py:1653

                        )
                        raise
                    except MCPError:
                        logger.exception(f"Error reading resource {uri!r}")
                        raise
                    except Exception as e:
                        logger.exception(f"Error reading resource {uri!r}")
                        # Handle actionable errors that should reach the LLM
                        if get_http_status_code(e) == 429:
                            raise ResourceError(
                                "Rate limited by upstream API, please retry later"
                            ) from e
                        if is_timeout_error(e):
                            raise ResourceError(
                                "Upstream request timed out, please retry"
                            ) from e
                        # Standard masking logic
                        if self._mask_error_details:
                            raise ResourceError(
                                f"Error reading resource {uri!r}"
                            ) from e
                        raise ResourceError(
                            f"Error reading resource {uri!r}: {e}"
                        ) from e

                # Try templates (transforms + auth via get_resource_template)
                template = await self.get_resource_template(uri, version=version)
                if template is None:
                    if version is None:
                        raise NotFoundError(f"Unknown resource: {uri!r}")
                    raise NotFoundError(
                        f"Unknown resource: {uri!r} version {version!r}"
                    )
                span.set_attributes(template.get_span_attributes())
                params = template.matches(uri)
                assert params is not None

View on GitHub (pinned to 1f02114297)