From 79d2caef45cd49df600925289086ce8c4f1de1e4 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Sun, 28 Dec 2025 11:15:28 -0700 Subject: [PATCH] fix: use FastMCP get_access_token() for OAuth claims extraction The previous implementation tried to access OAuth token claims via context.request_context.access_token.claims, which doesn't exist in FastMCP's context structure. FastMCP stores access tokens in Python's ContextVars, accessible via the get_access_token() dependency function. This fix updates both extract_user_from_context() and RBACMiddleware._extract_user_from_context() to use this correct approach. Before: Users appeared as "anonymous" with no groups (RBAC denied all) After: User identity and groups correctly extracted from OAuth claims --- src/mcvsphere/middleware.py | 51 ++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/mcvsphere/middleware.py b/src/mcvsphere/middleware.py index c61aa34..2b048e6 100644 --- a/src/mcvsphere/middleware.py +++ b/src/mcvsphere/middleware.py @@ -92,24 +92,26 @@ def with_permission_check(tool_name: str) -> Callable: def extract_user_from_context(ctx) -> dict[str, Any] | None: """Extract user information from FastMCP context. + Uses FastMCP's dependency injection to get the access token from + the current request's context variable. + Args: - ctx: FastMCP Context object. + ctx: FastMCP Context object (unused, kept for API compatibility). Returns: User info dict from OAuth token claims, or None if not authenticated. """ - if ctx is None: - return None - - # Try to get access token from context try: - # FastMCP stores the access token in request_context - if hasattr(ctx, "request_context") and ctx.request_context: - token = getattr(ctx.request_context, "access_token", None) - if token and hasattr(token, "claims"): - return token.claims - except Exception: - pass + # FastMCP stores access token in a context variable, accessed via dependency + from fastmcp.server.dependencies import get_access_token + + access_token = get_access_token() + if access_token and hasattr(access_token, "claims"): + return access_token.claims + except (RuntimeError, ImportError) as e: + # RuntimeError: No active HTTP request context + # ImportError: FastMCP auth dependencies not available + logger.debug("Could not get access token: %s", e) return None @@ -208,23 +210,26 @@ class RBACMiddleware(Middleware): ) -> dict[str, Any] | None: """Extract user claims from FastMCP context. + Uses FastMCP's dependency injection to retrieve the access token + from the current request's context variable. + Args: - fastmcp_ctx: FastMCP Context object from middleware context. + fastmcp_ctx: FastMCP Context object (unused, kept for API compatibility). Returns: User claims dict from OAuth token, or None if not authenticated. """ - if fastmcp_ctx is None: - return None - try: - # FastMCP stores access token in request_context - if hasattr(fastmcp_ctx, "request_context") and fastmcp_ctx.request_context: - token = getattr(fastmcp_ctx.request_context, "access_token", None) - if token and hasattr(token, "claims"): - return token.claims - except Exception as e: - logger.debug("Failed to extract user from context: %s", e) + # FastMCP stores access token in a context variable, accessed via dependency + from fastmcp.server.dependencies import get_access_token + + access_token = get_access_token() + if access_token and hasattr(access_token, "claims"): + return access_token.claims + except (RuntimeError, ImportError) as e: + # RuntimeError: No active HTTP request context + # ImportError: FastMCP auth dependencies not available + logger.debug("Could not get access token: %s", e) return None