PrefectHQ/fastmcp · error · AuthorizationError

Authorization failed for tool '{tool_name}': missing context

Error message

Authorization failed for tool '{tool_name}': missing context

What it means

AuthMiddleware.on_call_tool fails closed: if `context.fastmcp_context` is None when a tools/call arrives, it cannot run authorization checks, so it denies the request with an AuthorizationError rather than allowing it. This is a defense-in-depth guard — the request reached the middleware without the request-scoped FastMCP Context that normal transports establish.

Source

Thrown at fastmcp_slim/fastmcp/server/middleware/authorization.py:220

    ) -> ToolResult:
        """Check auth before tool execution."""
        # STDIO has no auth concept, skip enforcement
        # Late import to avoid circular import with context.py
        from fastmcp.server.context import _current_transport

        if _current_transport.get() == "stdio":
            return await call_next(context)

        # Get the tool being called
        tool_name = context.message.name
        fastmcp = context.fastmcp_context
        if fastmcp is None:
            # Fail closed: deny access when context is missing
            logger.warning(
                f"AuthMiddleware: fastmcp_context is None for tool '{tool_name}'. "
                "Denying access for security."
            )
            raise AuthorizationError(
                f"Authorization failed for tool '{tool_name}': missing context"
            )

        # get_tool returns None both when the tool does not exist and when
        # component-level auth denied access, so the two cases are
        # indistinguishable here. Keep the message ambiguous to avoid
        # disclosing existence of tools the caller is not authorized to see.
        version = _requested_version(context.message.meta)
        tool = await fastmcp.fastmcp.get_tool(tool_name, version=version)
        if tool is None:
            raise AuthorizationError(
                f"Authorization failed for tool '{tool_name}': "
                "not found or not authorized"
            )

        # Global auth check
        token = get_access_token()
        ctx = AuthContext(token=token, component=tool)

View on GitHub (pinned to 1f02114297)

Solutions

  1. Route the call through the normal server dispatch (client -> server) so FastMCP establishes the request Context, instead of invoking middleware directly.
  2. In tests, build the context properly: `async with Context(fastmcp=mcp, session=...) as ctx:` and pass `fastmcp_context=ctx` into MiddlewareContext.
  3. If you have a custom transport, ensure it wraps handler execution in a FastMCP Context as the built-in transports do.
  4. Confirm the transport is detected correctly — requests with _current_transport unset/not stdio still require auth context.

Example fix

# before (test)
ctx = MiddlewareContext(message=params, fastmcp_context=None)
await mw.on_call_tool(ctx, call_next)  # AuthorizationError: missing context
# after
async with Context(fastmcp=mcp, session=session) as fctx:
    ctx = MiddlewareContext(message=params, fastmcp_context=fctx)
    await mw.on_call_tool(ctx, call_next)
Defensive patterns

Strategy: try-catch

Validate before calling

from fastmcp.server.context import Context
async with Context(fastmcp=mcp, session=session) as fctx:
    assert fctx is not None, 'middleware requires a FastMCP context'

Try / catch

from fastmcp.exceptions import AuthorizationError
try:
    result = await client.call_tool('my_tool', {'arg': 1})
except AuthorizationError as e:
    if 'missing context' in str(e):
        logger.error('Request bypassed context establishment; fix transport/test harness')
    else:
        raise

Prevention

When it happens

Trigger: Calling a tool over a non-stdio transport while the middleware pipeline runs without a populated fastmcp_context — requests injected directly into the middleware stack in tests, custom transports/handlers that bypass `Context` setup, or middleware wrapping that loses the request context. STDIO is exempted earlier, so this only happens on HTTP/SSE-style transports.

Common situations: Integration tests that invoke `middleware.on_call_tool` with a hand-built MiddlewareContext lacking fastmcp_context; embedding the low-level server in a custom ASGI app that doesn't establish FastMCP's request context; upgrading FastMCP so custom transport glue no longer populates the context.

Related errors


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