{"record":{"id":"320b3b67b5fe5ecd","repo":"PrefectHQ/fastmcp","slug":"authorization-failed-for-tool-tool-name-missi","errorCode":null,"errorMessage":"Authorization failed for tool '{tool_name}': missing context","messagePattern":"Authorization failed for tool '(.+?)': missing context","errorType":"exception","errorClass":"AuthorizationError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/middleware/authorization.py","lineNumber":220,"sourceCode":"    ) -> ToolResult:\n        \"\"\"Check auth before tool execution.\"\"\"\n        # STDIO has no auth concept, skip enforcement\n        # Late import to avoid circular import with context.py\n        from fastmcp.server.context import _current_transport\n\n        if _current_transport.get() == \"stdio\":\n            return await call_next(context)\n\n        # Get the tool being called\n        tool_name = context.message.name\n        fastmcp = context.fastmcp_context\n        if fastmcp is None:\n            # Fail closed: deny access when context is missing\n            logger.warning(\n                f\"AuthMiddleware: fastmcp_context is None for tool '{tool_name}'. \"\n                \"Denying access for security.\"\n            )\n            raise AuthorizationError(\n                f\"Authorization failed for tool '{tool_name}': missing context\"\n            )\n\n        # get_tool returns None both when the tool does not exist and when\n        # component-level auth denied access, so the two cases are\n        # indistinguishable here. Keep the message ambiguous to avoid\n        # disclosing existence of tools the caller is not authorized to see.\n        version = _requested_version(context.message.meta)\n        tool = await fastmcp.fastmcp.get_tool(tool_name, version=version)\n        if tool is None:\n            raise AuthorizationError(\n                f\"Authorization failed for tool '{tool_name}': \"\n                \"not found or not authorized\"\n            )\n\n        # Global auth check\n        token = get_access_token()\n        ctx = AuthContext(token=token, component=tool)","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/middleware/authorization.py#L202-L238","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Route the call through the normal server dispatch (client -> server) so FastMCP establishes the request Context, instead of invoking middleware directly.","In tests, build the context properly: `async with Context(fastmcp=mcp, session=...) as ctx:` and pass `fastmcp_context=ctx` into MiddlewareContext.","If you have a custom transport, ensure it wraps handler execution in a FastMCP Context as the built-in transports do.","Confirm the transport is detected correctly — requests with _current_transport unset/not stdio still require auth context."],"exampleFix":"# before (test)\nctx = MiddlewareContext(message=params, fastmcp_context=None)\nawait mw.on_call_tool(ctx, call_next)  # AuthorizationError: missing context\n# after\nasync with Context(fastmcp=mcp, session=session) as fctx:\n    ctx = MiddlewareContext(message=params, fastmcp_context=fctx)\n    await mw.on_call_tool(ctx, call_next)","handlingStrategy":"try-catch","validationCode":"from fastmcp.server.context import Context\nasync with Context(fastmcp=mcp, session=session) as fctx:\n    assert fctx is not None, 'middleware requires a FastMCP context'","typeGuard":null,"tryCatchPattern":"from fastmcp.exceptions import AuthorizationError\ntry:\n    result = await client.call_tool('my_tool', {'arg': 1})\nexcept AuthorizationError as e:\n    if 'missing context' in str(e):\n        logger.error('Request bypassed context establishment; fix transport/test harness')\n    else:\n        raise","preventionTips":["Always dispatch requests through the client/server transport stack, never straight into middleware.","In tests, use Context(fastmcp=..., session=...) when building MiddlewareContext.","Keep custom transports in sync with FastMCP's context-establishment pattern after upgrades.","Treat 'missing context' warnings in logs as harness bugs, not auth policy issues."],"tags":["auth","middleware","security","fail-closed"],"backgroundTag":"authorization-failed","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}