"""Authentication and environment helper utilities for MCP servers. This module centralizes logic for: - Checking an environment variable against an expected value - Resolving a token from (in priority order): env var, HTTP headers, legacy context headers, or Env var sampling fallback. Functions are intentionally side-effect free (aside from env reads) and return either primitive values or small error dictionaries the caller can propagate directly to the MCP tool response. """ from __future__ import annotations from typing import Any, Optional import os from fastmcp.server.dependencies import get_http_headers def check_env_var(var_name: str, match_value: Optional[Any] = None) -> bool: """Return True if env var exists and equals match_value. If match_value is None, this always returns False (explicit comparison required by callers for security clarity). """ env_val = os.getenv(var_name) return ( env_val is not None and match_value is not None and env_val == match_value ) def get_env_var_or_request(var_name: str, ctx=None) -> Any: """Resolve a sensitive value (e.g., token) from multiple sources. Priority order: 1. Environment variable of the given name 2. Standard HTTP Authorization Bearer header via FastMCP dependency 3. Legacy ctx.request_headers (Bearer or 'token' header) 4. Env var sampling fallback (ctx.sample) if context provided Args: var_name: The name of the environment variable to retrieve. ctx: Optional FastMCP context for fallback token retrieval. Returns: The resolved value and its source. """ # 1. Environment variable value = os.getenv(var_name) if value: return {"value": value, "source": "env"} # 2. FastMCP provided HTTP headers (streamable-http mode) try: headers = get_http_headers() if headers: auth_header = headers.get("authorization") if auth_header and auth_header.startswith("Bearer "): return {"value": auth_header[7:], "source": "http"} except Exception: # pragma: no cover - defensive pass # 3. Legacy context headers try: if ctx and hasattr(ctx, "request_headers"): client_headers = ctx.request_headers # type: ignore[attr-defined] if isinstance(client_headers, dict): auth_header = ( client_headers.get("authorization") or client_headers.get("Authorization") ) if auth_header and auth_header.startswith("Bearer "): return {"value": auth_header[7:], "source": "http"} if client_headers.get("token"): return {"value": client_headers["token"], "source": "http"} except Exception as exc: # pragma: no cover raise Exception("Failed to process client headers") from exc # 4. Env var sampling fallback if ctx is not None: try: return ctx.sample( "Please provide the value for environment variable: " f"{var_name}" ) except Exception as exc: # pragma: no cover raise Exception("Sampling failed.") from exc raise ValueError(f"Missing required context: {var_name}") __all__ = ["check_env_var", "get_env_var_or_request"]