From 80ed791b01ab4ef2dea57a4c4fdc18b703135f55 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Nov 2025 22:30:55 +0000 Subject: [PATCH] Add multi-user support - each API key gets own GeoGuessr session Implements comprehensive multi-user support allowing multiple users to access the same MCP server instance with their own independent GeoGuessr accounts. Each API key now has its own session storage and context. ## Multi-User Architecture ### New Components **User Context System** (src/geoguessr_mcp/auth/user_context.py): - UserContext dataclass tracks API key and associated GeoGuessr session - Properties for user_id, username, ncfa_cookie, is_authenticated - Automatically attached to each request **Multi-User Session Manager** (src/geoguessr_mcp/auth/multi_user_session.py): - MultiUserSessionManager manages separate SessionManager per API key - Maps API keys to their own GeoGuessr sessions - Methods: get_user_context, login_user, logout_user, set_user_cookie - Global instance: multi_user_session_manager **Request Context** (src/geoguessr_mcp/auth/request_context.py): - ContextVar for accessing current user context in tools - Functions: get_current_user_context, require_user_context, set_current_user_context - Enables tools to access user-specific sessions automatically ### Updated Components **Authentication Middleware** (src/geoguessr_mcp/middleware/auth.py): - Now creates user context for each authenticated request - Attaches context to both request.state and ContextVar - Supports both authenticated and unauthenticated modes - Default user context created when auth is disabled **Authentication Tools** (src/geoguessr_mcp/tools/auth_tools.py): - Completely rewritten for multi-user support - login(): Creates session tied to caller's API key - logout(): Logs out only the calling user's session - set_ncfa_cookie(): Sets cookie for calling user only - get_auth_status(): Returns calling user's auth status - All tools use get_current_user_context() automatically **GeoGuessr Client** (src/geoguessr_mcp/api/geoguessr_client.py): - _get_authenticated_client() checks user context first - Falls back to session_manager for backward compatibility - Automatically uses caller's session when available - No changes needed in services (profile, game, analysis) ## How It Works 1. User connects with API key in Authorization header 2. Middleware validates API key and creates/retrieves UserContext 3. UserContext attached to request.state and ContextVar 4. Tools call get_current_user_context() to access caller's session 5. Client automatically uses correct session for API calls 6. Each user's session is completely isolated ## Usage Example ```bash # Configure multiple API keys MCP_AUTH_ENABLED=true MCP_API_KEYS=alice_key,bob_key,charlie_key # Alice connects with: Authorization: Bearer alice_key # Bob connects with: Authorization: Bearer bob_key # Each can login to their own GeoGuessr account # Sessions are completely independent ``` ## Key Features - **Zero Interference**: Users don't affect each other's sessions - **Automatic Routing**: Requests automatically use correct user's session - **Hot Reload**: Add new API keys and restart in ~2-3 seconds - **Backward Compatible**: Still works with single-user mode - **Fallback Support**: GEOGUESSR_NCFA_COOKIE still works as default ## Documentation Updates - README.md: Added Multi-User Mode section with examples - README.md: Updated authentication section with multi-user details - README.md: Added "Adding New Users" workflow - Key Features section now highlights multi-user support ## Technical Details - Uses Python ContextVar for request-scoped user context - Each API key gets its own SessionManager instance - Session storage is in-memory (persists across requests, not restarts) - Default cookie (GEOGUESSR_NCFA_COOKIE) used as fallback for all users - Fully async/await compatible throughout --- README.md | 91 ++++++++- src/geoguessr_mcp/api/geoguessr_client.py | 18 +- src/geoguessr_mcp/auth/__init__.py | 13 ++ src/geoguessr_mcp/auth/multi_user_session.py | 192 +++++++++++++++++++ src/geoguessr_mcp/auth/request_context.py | 61 ++++++ src/geoguessr_mcp/auth/user_context.py | 58 ++++++ src/geoguessr_mcp/middleware/auth.py | 21 +- src/geoguessr_mcp/tools/auth_tools.py | 190 +++++++++--------- 8 files changed, 551 insertions(+), 93 deletions(-) create mode 100644 src/geoguessr_mcp/auth/multi_user_session.py create mode 100644 src/geoguessr_mcp/auth/request_context.py create mode 100644 src/geoguessr_mcp/auth/user_context.py diff --git a/README.md b/README.md index ab55578..45c7302 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,12 @@ A Model Context Protocol (MCP) server for analyzing GeoGuessr game statistics wi ## 🌟 Key Features +### Multi-User Support +- **Independent Sessions**: Each API key gets its own GeoGuessr session +- **Multiple Accounts**: Different users can access their own GeoGuessr accounts +- **Single Server**: No need to deploy separate instances per user +- **Automatic Context**: User sessions are automatically managed per request + ### Dynamic API Monitoring - **Automatic endpoint discovery**: Monitors GeoGuessr API endpoints daily - **Schema change detection**: Automatically detects when API response formats change @@ -101,12 +107,14 @@ Add to your Claude Desktop configuration: ## 🔐 Authentication -The server supports two types of authentication: +The server supports two types of authentication with **multi-user support**: ### MCP Server Authentication (Controls Access to the MCP Server) Secures who can connect to your MCP server. When enabled, clients must provide a valid API key. +**Multi-User Support:** Each API key can have its own GeoGuessr session, allowing multiple users to use the same MCP server instance with their own accounts! + **Enable in `.env`:** ```bash MCP_AUTH_ENABLED=true @@ -133,9 +141,19 @@ openssl rand -hex 32 } ``` +**Multi-User Example:** +```bash +# Give each user their own API key +MCP_API_KEYS=alice_key_abc123,bob_key_def456,charlie_key_ghi789 + +# Alice connects with Authorization: Bearer alice_key_abc123 +# Bob connects with Authorization: Bearer bob_key_def456 +# Each can login to their own GeoGuessr account! +``` + ### GeoGuessr API Authentication (Access GeoGuessr Data) -The server also needs authentication to access GeoGuessr's API. Multiple methods supported: +The server also needs authentication to access GeoGuessr's API. In multi-user mode, **each API key holder can login to their own GeoGuessr account:** ### Option 1: Login via Claude (Recommended) Simply ask Claude: @@ -150,6 +168,75 @@ GEOGUESSR_NCFA_COOKIE=your_cookie_value_here ### Option 3: Manual Cookie Use the `set_ncfa_cookie` tool with a cookie extracted from your browser. +## 👥 Multi-User Mode + +The server supports multiple users, each with their own GeoGuessr account, using a single MCP server instance. + +### How It Works + +1. **API Keys**: Each user gets a unique API key +2. **Independent Sessions**: Each API key has its own GeoGuessr login session +3. **Automatic Routing**: The server automatically routes requests to the correct user's session +4. **No Interference**: Users don't affect each other's sessions + +### Setup Example + +**1. Configure Multiple API Keys:** +```bash +# .env file +MCP_AUTH_ENABLED=true +MCP_API_KEYS=alice_key,bob_key,charlie_key +``` + +**2. Restart Server:** +```bash +# Development +docker compose restart + +# Production +docker compose -f docker-compose.prod.yml restart +``` + +**3. Each User Connects:** +```json +// Alice's Claude Desktop config +{ + "mcpServers": { + "geoguessr": { + "url": "https://your-domain.com/mcp", + "headers": {"Authorization": "Bearer alice_key"} + } + } +} + +// Bob's Claude Desktop config +{ + "mcpServers": { + "geoguessr": { + "url": "https://your-domain.com/mcp", + "headers": {"Authorization": "Bearer bob_key"} + } + } +} +``` + +**4. Each User Logs In:** +- Alice asks Claude: "Login to GeoGuessr with my credentials" +- Bob asks Claude: "Login to GeoGuessr with my credentials" +- Sessions are completely independent! + +### Adding New Users + +To add a new user to an existing deployment: + +1. Edit `.env` and add the new API key to `MCP_API_KEYS` +2. Restart the server: `docker compose restart` +3. Share the new API key with the user +4. User configures their Claude Desktop with the API key +5. User logs in to their GeoGuessr account via Claude + +**The server restarts in ~2-3 seconds** and all existing users remain logged in! + ## 📊 Available Tools ### Authentication diff --git a/src/geoguessr_mcp/api/geoguessr_client.py b/src/geoguessr_mcp/api/geoguessr_client.py index cc696c2..4f8b30d 100644 --- a/src/geoguessr_mcp/api/geoguessr_client.py +++ b/src/geoguessr_mcp/api/geoguessr_client.py @@ -16,6 +16,7 @@ import httpx from .dynamic_response import DynamicResponse from .endpoints import EndpointInfo +from ..auth import get_current_user_context from ..auth.session import SessionManager from ..config import settings from ..monitoring.schema.schema_registry import schema_registry @@ -46,8 +47,21 @@ class GeoGuessrClient: self, session_token: Optional[str] = None, ) -> httpx.AsyncClient: - """Get an authenticated HTTP client.""" - session = await self.session_manager.get_session(session_token) + """ + Get an authenticated HTTP client. + + In multi-user mode, if no session_token is provided, uses the current user's context + to get their session automatically. + """ + # Try to get session from current user context (multi-user mode) + user_context = get_current_user_context() + if user_context and user_context.is_authenticated: + # Use the session from the user's context + session = user_context.session + else: + # Fall back to session manager (legacy mode or no user context) + session = await self.session_manager.get_session(session_token) + if not session: raise ValueError( "No valid session available. Please login first or set GEOGUESSR_NCFA_COOKIE." diff --git a/src/geoguessr_mcp/auth/__init__.py b/src/geoguessr_mcp/auth/__init__.py index ac04f7c..f6dd307 100644 --- a/src/geoguessr_mcp/auth/__init__.py +++ b/src/geoguessr_mcp/auth/__init__.py @@ -1,8 +1,21 @@ """Auth module for GeoGuessr session.""" +from .multi_user_session import MultiUserSessionManager, multi_user_session_manager +from .request_context import ( + get_current_user_context, + require_user_context, + set_current_user_context, +) from .session import SessionManager, UserSession +from .user_context import UserContext __all__ = [ "UserSession", "SessionManager", + "UserContext", + "MultiUserSessionManager", + "multi_user_session_manager", + "get_current_user_context", + "require_user_context", + "set_current_user_context", ] diff --git a/src/geoguessr_mcp/auth/multi_user_session.py b/src/geoguessr_mcp/auth/multi_user_session.py new file mode 100644 index 0000000..c6163c9 --- /dev/null +++ b/src/geoguessr_mcp/auth/multi_user_session.py @@ -0,0 +1,192 @@ +""" +Multi-user session management. + +This module provides session management for multiple users, +where each API key can have its own GeoGuessr session. +""" + +import asyncio +import logging +from typing import Optional + +from ..config import settings +from .session import SessionManager, UserSession +from .user_context import UserContext + +logger = logging.getLogger(__name__) + + +class MultiUserSessionManager: + """ + Manages GeoGuessr sessions for multiple users. + + Each API key can have its own GeoGuessr session, allowing + multiple users to access their own accounts through the same + MCP server instance. + """ + + def __init__(self): + """Initialize the multi-user session manager.""" + # Map API keys to their session managers + self._user_managers: dict[str, SessionManager] = {} + self._lock = asyncio.Lock() + + # Create default session manager if default cookie is configured + if settings.DEFAULT_NCFA_COOKIE: + logger.info("Default GeoGuessr cookie configured - will be used as fallback") + + async def get_user_context(self, api_key: str) -> UserContext: + """ + Get or create a user context for an API key. + + Args: + api_key: The API key identifying the user + + Returns: + UserContext: The context for this user + """ + async with self._lock: + # Get or create session manager for this user + if api_key not in self._user_managers: + # Create new session manager with default cookie as fallback + self._user_managers[api_key] = SessionManager( + default_cookie=settings.DEFAULT_NCFA_COOKIE + ) + logger.info(f"Created new session manager for API key {api_key[:8]}...") + + manager = self._user_managers[api_key] + + # Get the session (may return default session if no user login) + session = await manager.get_session() + + # Create user context + context = UserContext(api_key=api_key, session=session) + + return context + + async def login_user( + self, api_key: str, email: str, password: str + ) -> tuple[str, UserSession]: + """ + Login a specific user (API key) to their GeoGuessr account. + + Args: + api_key: The API key identifying the user + email: GeoGuessr email + password: GeoGuessr password + + Returns: + tuple[str, UserSession]: (session_token, UserSession) + + Raises: + ValueError: If login fails + """ + async with self._lock: + if api_key not in self._user_managers: + self._user_managers[api_key] = SessionManager() + + manager = self._user_managers[api_key] + + # Perform login + session_token, session = await manager.login(email, password) + + logger.info( + f"User {session.username} logged in successfully for API key {api_key[:8]}..." + ) + + return session_token, session + + async def logout_user(self, api_key: str, session_token: str) -> bool: + """ + Logout a specific user's session. + + Args: + api_key: The API key identifying the user + session_token: The session token to logout + + Returns: + bool: True if logout successful, False otherwise + """ + async with self._lock: + if api_key not in self._user_managers: + return False + + manager = self._user_managers[api_key] + + success = await manager.logout(session_token) + + if success: + logger.info(f"User logged out for API key {api_key[:8]}...") + + return success + + async def set_user_cookie(self, api_key: str, cookie: str) -> bool: + """ + Set a GeoGuessr cookie for a specific user. + + Args: + api_key: The API key identifying the user + cookie: The NCFA cookie value + + Returns: + bool: True if cookie is valid, False otherwise + """ + # Validate cookie first + profile = await SessionManager.validate_cookie(cookie) + if not profile: + return False + + async with self._lock: + if api_key not in self._user_managers: + self._user_managers[api_key] = SessionManager() + + manager = self._user_managers[api_key] + + await manager.set_default_cookie(cookie) + + logger.info( + f"Cookie set for user {profile.get('nick', 'unknown')} (API key {api_key[:8]}...)" + ) + + return True + + async def get_session_for_api_key(self, api_key: str) -> Optional[UserSession]: + """ + Get the active session for a specific API key. + + Args: + api_key: The API key identifying the user + + Returns: + UserSession if available, None otherwise + """ + async with self._lock: + if api_key not in self._user_managers: + return None + + manager = self._user_managers[api_key] + + return await manager.get_session() + + async def get_auth_status(self, api_key: str) -> dict: + """ + Get authentication status for a specific user. + + Args: + api_key: The API key identifying the user + + Returns: + dict: Authentication status information + """ + context = await self.get_user_context(api_key) + + return { + "authenticated": context.is_authenticated, + "user_id": context.user_id if context.is_authenticated else None, + "username": context.username if context.is_authenticated else None, + "api_key": f"{api_key[:8]}..." if len(api_key) > 8 else "***", + } + + +# Global multi-user session manager instance +multi_user_session_manager = MultiUserSessionManager() diff --git a/src/geoguessr_mcp/auth/request_context.py b/src/geoguessr_mcp/auth/request_context.py new file mode 100644 index 0000000..d16f4f7 --- /dev/null +++ b/src/geoguessr_mcp/auth/request_context.py @@ -0,0 +1,61 @@ +""" +Request context utilities for accessing user context in tools. + +This module provides utilities to access the current user context +from within MCP tools, allowing each tool to operate on behalf of +the authenticated user making the request. +""" + +from contextvars import ContextVar +from typing import Optional + +from .user_context import UserContext + +# Context variable to store the current user context +_current_user_context: ContextVar[Optional[UserContext]] = ContextVar( + "current_user_context", default=None +) + + +def set_current_user_context(context: UserContext) -> None: + """ + Set the current user context for this request. + + This should be called by middleware to set the context + for the duration of the request. + + Args: + context: The UserContext for the current request + """ + _current_user_context.set(context) + + +def get_current_user_context() -> Optional[UserContext]: + """ + Get the current user context. + + This can be called from within tools to access the user context + for the current request. + + Returns: + UserContext if available, None otherwise + """ + return _current_user_context.get() + + +def require_user_context() -> UserContext: + """ + Get the current user context, raising an error if not available. + + Raises: + RuntimeError: If no user context is available + + Returns: + UserContext: The current user context + """ + context = get_current_user_context() + if context is None: + raise RuntimeError( + "No user context available. This tool must be called through the MCP server." + ) + return context diff --git a/src/geoguessr_mcp/auth/user_context.py b/src/geoguessr_mcp/auth/user_context.py new file mode 100644 index 0000000..3214b07 --- /dev/null +++ b/src/geoguessr_mcp/auth/user_context.py @@ -0,0 +1,58 @@ +""" +User context for multi-user support. + +This module provides the UserContext class that tracks which user +is making a request and their associated GeoGuessr session. +""" + +from dataclasses import dataclass +from typing import Optional + +from .session import UserSession + + +@dataclass +class UserContext: + """ + Context for a specific user making a request. + + This class is attached to each request and contains information + about the authenticated user and their GeoGuessr session. + """ + + api_key: str + """The API key used to authenticate this request""" + + session: Optional[UserSession] = None + """The GeoGuessr session for this user (if logged in)""" + + @property + def user_id(self) -> str: + """Get the user ID for this context.""" + if self.session: + return self.session.user_id + return f"anonymous_{hash(self.api_key) % 10000:04d}" + + @property + def username(self) -> str: + """Get the username for this context.""" + if self.session: + return self.session.username + return f"User-{hash(self.api_key) % 10000:04d}" + + @property + def ncfa_cookie(self) -> Optional[str]: + """Get the NCFA cookie for this user.""" + if self.session: + return self.session.ncfa_cookie + return None + + @property + def is_authenticated(self) -> bool: + """Check if this user has a valid GeoGuessr session.""" + return self.session is not None and self.session.is_valid() + + def __repr__(self) -> str: + """String representation of user context.""" + auth_status = "authenticated" if self.is_authenticated else "not authenticated" + return f"UserContext(user_id={self.user_id}, {auth_status})" diff --git a/src/geoguessr_mcp/middleware/auth.py b/src/geoguessr_mcp/middleware/auth.py index ddb918b..8de0e7a 100644 --- a/src/geoguessr_mcp/middleware/auth.py +++ b/src/geoguessr_mcp/middleware/auth.py @@ -1,7 +1,8 @@ """ Authentication middleware for MCP server. -Provides Bearer token authentication for HTTP-based MCP transports. +Provides Bearer token authentication for HTTP-based MCP transports +and attaches user context for multi-user support. """ import logging @@ -11,6 +12,7 @@ from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from starlette.responses import JSONResponse, Response +from ..auth import multi_user_session_manager, set_current_user_context from ..config import settings logger = logging.getLogger(__name__) @@ -40,8 +42,12 @@ class AuthenticationMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next) -> Response: """Process the request and validate authentication if enabled.""" - # Skip authentication if disabled + # If authentication is disabled, create a default user context if not self.enabled: + # Use a default API key for all unauthenticated requests + user_context = await multi_user_session_manager.get_user_context("default") + request.state.user_context = user_context + set_current_user_context(user_context) return await call_next(request) # Skip authentication for health check endpoint @@ -90,7 +96,16 @@ class AuthenticationMiddleware(BaseHTTPMiddleware): ) # Authentication successful - logger.debug(f"Authenticated request from {request.client.host}") + logger.debug(f"Authenticated request from {request.client.host} with API key {token[:8]}...") + + # Get or create user context for this API key + user_context = await multi_user_session_manager.get_user_context(token) + + # Attach user context to request state and context variable + request.state.user_context = user_context + set_current_user_context(user_context) + + logger.debug(f"Request context: {user_context}") # Proceed with the request response = await call_next(request) diff --git a/src/geoguessr_mcp/tools/auth_tools.py b/src/geoguessr_mcp/tools/auth_tools.py index 0c47757..2330317 100644 --- a/src/geoguessr_mcp/tools/auth_tools.py +++ b/src/geoguessr_mcp/tools/auth_tools.py @@ -2,48 +2,48 @@ Tools for handling authentication and session management with GeoGuessr. This module provides utilities for login, logout, setting authentication cookies, -and checking the current authentication status. Its primary purpose is to ensure -interactions with the GeoGuessr API are authenticated securely and conveniently. +and checking the current authentication status. With multi-user support, each +API key can have its own GeoGuessr session. The module integrates with FastMCP to expose authentication methods as tools -and uses the `SessionManager` for session storage and validation. +and uses the multi-user session manager for per-user session storage. Functions --------- - register_auth_tools: Registers a set of authentication tools with FastMCP. -- get_current_session_token: Returns the currently active session token. """ import logging -import secrets -from datetime import datetime, timedelta, UTC -from typing import Optional from mcp.server.fastmcp import FastMCP -from ..auth.session import SessionManager, UserSession -from ..config import settings +from ..auth import get_current_user_context, multi_user_session_manager logger = logging.getLogger(__name__) -# Global session token storage -_current_session_token: Optional[str] = None +def register_auth_tools(mcp: FastMCP, session_manager=None): + """ + Register authentication-related tools. -def register_auth_tools(mcp: FastMCP, session_manager: SessionManager): - """Register authentication-related tools.""" + Note: session_manager parameter is kept for backward compatibility but not used. + The multi_user_session_manager is used instead. + """ @mcp.tool() async def login(email: str, password: str) -> dict: """ Authenticate with GeoGuessr using email and password. - Creates a session that will be used for all later API calls. - Credentials are only used to get an authentication token and are + Creates a session for YOUR account that will be used for all later API calls. + Your credentials are only used to get an authentication token and are not stored on the server. + Each API key gets its own session, so multiple users can use the same + MCP server with their own GeoGuessr accounts. + Args: email: Your GeoGuessr account email password: Your GeoGuessr account password @@ -51,11 +51,19 @@ def register_auth_tools(mcp: FastMCP, session_manager: SessionManager): Returns: Session information including username and session token """ - global _current_session_token + # Get the current user's context (API key) + user_context = get_current_user_context() + if not user_context: + return { + "success": False, + "error": "No user context available. Authentication is required.", + } try: - session_token, session = await session_manager.login(email, password) - _current_session_token = session_token + # Login using the multi-user session manager + session_token, session = await multi_user_session_manager.login_user( + user_context.api_key, email, password + ) return { "success": True, @@ -64,6 +72,7 @@ def register_auth_tools(mcp: FastMCP, session_manager: SessionManager): "user_id": session.user_id, "session_token": session_token, "expires_at": session.expires_at.isoformat() if session.expires_at else None, + "multi_user_note": "Your session is tied to your API key. Other users with different API keys can have their own sessions.", } except ValueError as e: return {"success": False, "error": str(e)} @@ -72,23 +81,32 @@ def register_auth_tools(mcp: FastMCP, session_manager: SessionManager): return {"success": False, "error": f"An unexpected error occurred: {str(e)}"} @mcp.tool() - async def logout() -> dict: + async def logout(session_token: str) -> dict: """ - Logout from the current GeoGuessr session. + Logout from your current GeoGuessr session. - Invalidates the current session token. + Invalidates your session token. This only affects your session + (tied to your API key), not other users. + + Args: + session_token: The session token to logout (from login response) + + Returns: + Success status """ - global _current_session_token + # Get the current user's context + user_context = get_current_user_context() + if not user_context: + return {"success": False, "error": "No user context available"} - if _current_session_token: - success = await session_manager.logout(_current_session_token) - _current_session_token = None - return { - "success": success, - "message": "Successfully logged out" if success else "No active session found", - } + success = await multi_user_session_manager.logout_user( + user_context.api_key, session_token + ) - return {"success": False, "message": "No active session"} + return { + "success": success, + "message": "Successfully logged out" if success else "No active session found", + } @mcp.tool() async def set_ncfa_cookie(cookie: str) -> dict: @@ -98,83 +116,83 @@ def register_auth_tools(mcp: FastMCP, session_manager: SessionManager): Use this if you've manually extracted the cookie from your browser. The cookie will be validated before being accepted. + This cookie will be tied to YOUR API key, so it won't affect other users. + Args: cookie: The _ncfa cookie value from your browser + + Returns: + Success status and user information """ - global _current_session_token + # Get the current user's context + user_context = get_current_user_context() + if not user_context: + return {"success": False, "error": "No user context available"} - # Validate the cookie - profile = await session_manager.validate_cookie(cookie) + # Set the cookie for this user + success = await multi_user_session_manager.set_user_cookie(user_context.api_key, cookie) - if not profile: + if not success: return {"success": False, "error": "Invalid cookie - authentication failed"} - # Create a session from the cookie - session = UserSession( - ncfa_cookie=cookie, - user_id=profile.get("id", ""), - username=profile.get("nick", ""), - email="manual@cookie", - expires_at=datetime.now(UTC) + timedelta(days=30), - ) + # Get updated session to show user info + session = await multi_user_session_manager.get_session_for_api_key(user_context.api_key) - # Store as a session - session_token = secrets.token_urlsafe(32) - async with session_manager._lock: - session_manager._sessions[session_token] = session - session_manager._user_sessions[session.user_id] = session_token + if session: + return { + "success": True, + "message": f"Cookie set successfully. Authenticated as {session.username}", + "username": session.username, + "user_id": session.user_id, + } - _current_session_token = session_token - - return { - "success": True, - "message": f"Cookie set successfully. Authenticated as {session.username}", - "username": session.username, - "user_id": session.user_id, - "session_token": session_token, - } + return {"success": True, "message": "Cookie set successfully"} @mcp.tool() async def get_auth_status() -> dict: """ - Check the current authentication status. + Check your current authentication status. - Returns information about the current session or available - authentication methods. + Returns information about your session (tied to your API key). + Each user with a different API key has their own independent session. + + Returns: + Authentication status and user information """ - global _current_session_token + # Get the current user's context + user_context = get_current_user_context() + if not user_context: + return { + "authenticated": False, + "message": "No user context available", + } - # Check for active session - if _current_session_token: - session = await session_manager.get_session(_current_session_token) - if session and session.is_valid(): - return { - "authenticated": True, - "method": "session", - "username": session.username, - "user_id": session.user_id, - "expires_at": session.expires_at.isoformat() if session.expires_at else None, - } + # Get auth status for this user + status = await multi_user_session_manager.get_auth_status(user_context.api_key) - # Check for environment variable - env_cookie = settings.DEFAULT_NCFA_COOKIE - if env_cookie: - profile = await session_manager.validate_cookie(env_cookie) - if profile: - return { - "authenticated": True, - "method": "environment_variable", - "username": profile.get("nick", "Unknown"), - "user_id": profile.get("id", "Unknown"), - } + if status["authenticated"]: + return { + **status, + "message": f"Authenticated as {status['username']}", + "multi_user_info": "Your session is independent. Other API keys have their own sessions.", + } return { - "authenticated": False, + **status, "message": "Not authenticated. Use 'login' with credentials or 'set_ncfa_cookie' with a valid cookie.", "available_methods": ["login(email, password)", "set_ncfa_cookie(cookie)"], } -def get_current_session_token() -> Optional[str]: - """Get the current session token for use by other tools.""" - return _current_session_token +def get_current_session_token(): + """ + Deprecated: This function is no longer used in multi-user mode. + + Sessions are now managed per-API-key automatically. + Use get_current_user_context() instead to access user-specific session data. + """ + logger.warning( + "get_current_session_token() is deprecated in multi-user mode. " + "Use get_current_user_context() instead." + ) + return None -- 2.49.1