diff --git a/README.md b/README.md index 7f2c7d5..45c7302 100644 --- a/README.md +++ b/README.md @@ -465,16 +465,6 @@ geoguessr-mcp/ └── Dockerfile ``` -### MCP Inspector Tool Test - -Tool to inspect MCP server and test connectivity: -Using the [Inspector](https://github.com/modelcontextprotocol/inspector) tool from the MCP project. -Via Docker: - -```bash -docker run --rm -p 6274:6274 -p 6277:6277 -e HOST=0.0.0.0 ghcr.io/modelcontextprotocol/inspector:latest -``` - ## 🤝 Contributing Contributions are welcome! Please: diff --git a/src/geoguessr_mcp/api/endpoints.py b/src/geoguessr_mcp/api/endpoints.py index b282858..c2f90c9 100644 --- a/src/geoguessr_mcp/api/endpoints.py +++ b/src/geoguessr_mcp/api/endpoints.py @@ -4,8 +4,8 @@ GeoGuessr API Endpoints Registry. Centralized endpoint definitions with metadata for dynamic discovery and routing. """ +from collections.abc import Callable from dataclasses import dataclass -from typing import Callable, Optional from ..config import settings @@ -19,7 +19,7 @@ class EndpointInfo: description: str = "" auth_required: bool = True use_game_server: bool = False - params_builder: Optional[Callable[..., dict]] = None + params_builder: Callable[..., dict] | None = None class Endpoints: diff --git a/src/geoguessr_mcp/api/geoguessr_client.py b/src/geoguessr_mcp/api/geoguessr_client.py index 4f8b30d..23e4c44 100644 --- a/src/geoguessr_mcp/api/geoguessr_client.py +++ b/src/geoguessr_mcp/api/geoguessr_client.py @@ -10,7 +10,6 @@ Classes: """ import logging -from typing import Optional import httpx @@ -45,7 +44,7 @@ class GeoGuessrClient: async def _get_authenticated_client( self, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> httpx.AsyncClient: """ Get an authenticated HTTP client. @@ -79,9 +78,9 @@ class GeoGuessrClient: async def request( self, endpoint: EndpointInfo, - session_token: Optional[str] = None, - params: Optional[dict] = None, - json_data: Optional[dict] = None, + session_token: str | None = None, + params: dict | None = None, + json_data: dict | None = None, **kwargs, ) -> DynamicResponse: """ @@ -154,8 +153,8 @@ class GeoGuessrClient: async def get( self, endpoint: EndpointInfo, - session_token: Optional[str] = None, - params: Optional[dict] = None, + session_token: str | None = None, + params: dict | None = None, **kwargs, ) -> DynamicResponse: """Make a GET request.""" @@ -164,8 +163,8 @@ class GeoGuessrClient: async def post( self, endpoint: EndpointInfo, - session_token: Optional[str] = None, - json_data: Optional[dict] = None, + session_token: str | None = None, + json_data: dict | None = None, **kwargs, ) -> DynamicResponse: """Make a POST request.""" @@ -174,9 +173,9 @@ class GeoGuessrClient: async def get_raw( self, path: str, - session_token: Optional[str] = None, + session_token: str | None = None, use_game_server: bool = False, - params: Optional[dict] = None, + params: dict | None = None, ) -> DynamicResponse: """ Make a raw GET request to any path. diff --git a/src/geoguessr_mcp/auth/multi_user_session.py b/src/geoguessr_mcp/auth/multi_user_session.py index bef299d..11cd5a5 100644 --- a/src/geoguessr_mcp/auth/multi_user_session.py +++ b/src/geoguessr_mcp/auth/multi_user_session.py @@ -7,11 +7,10 @@ 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 +from ..config import settings logger = logging.getLogger(__name__) @@ -146,7 +145,7 @@ class MultiUserSessionManager: return True - async def get_session_for_api_key(self, api_key: str) -> Optional[UserSession]: + async def get_session_for_api_key(self, api_key: str) -> UserSession | None: """ Get the active session for a specific API key. diff --git a/src/geoguessr_mcp/auth/request_context.py b/src/geoguessr_mcp/auth/request_context.py index d16f4f7..afa48f0 100644 --- a/src/geoguessr_mcp/auth/request_context.py +++ b/src/geoguessr_mcp/auth/request_context.py @@ -7,12 +7,11 @@ 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: ContextVar[UserContext | None] = ContextVar( "current_user_context", default=None ) @@ -30,7 +29,7 @@ def set_current_user_context(context: UserContext) -> None: _current_user_context.set(context) -def get_current_user_context() -> Optional[UserContext]: +def get_current_user_context() -> UserContext | None: """ Get the current user context. diff --git a/src/geoguessr_mcp/auth/session.py b/src/geoguessr_mcp/auth/session.py index 314df40..61dfebe 100644 --- a/src/geoguessr_mcp/auth/session.py +++ b/src/geoguessr_mcp/auth/session.py @@ -7,7 +7,6 @@ import logging import secrets from dataclasses import dataclass, field from datetime import UTC, datetime, timedelta -from typing import Optional import httpx @@ -25,7 +24,7 @@ class UserSession: username: str email: str created_at: datetime = field(default_factory=datetime.now) - expires_at: Optional[datetime] = None + expires_at: datetime | None = None def is_valid(self) -> bool: """Check if the session is still valid.""" @@ -37,10 +36,10 @@ class UserSession: class SessionManager: """Manages user sessions for the MCP server.""" - def __init__(self, default_cookie: Optional[str] = None): + def __init__(self, default_cookie: str | None = None): self._sessions: dict[str, UserSession] = {} self._user_sessions: dict[str, str] = {} - self._default_cookie: Optional[str] = default_cookie or settings.DEFAULT_NCFA_COOKIE + self._default_cookie: str | None = default_cookie or settings.DEFAULT_NCFA_COOKIE self._lock = asyncio.Lock() @staticmethod @@ -111,7 +110,7 @@ class SessionManager: return session_token, session @staticmethod - def _extract_ncfa_cookie(response: httpx.Response) -> Optional[str]: + def _extract_ncfa_cookie(response: httpx.Response) -> str | None: """Extract _ncfa cookie from response.""" # Try cookies jar first for cookie in response.cookies.jar: @@ -160,7 +159,7 @@ class SessionManager: return True return False - async def get_session(self, session_token: Optional[str] = None) -> Optional[UserSession]: + async def get_session(self, session_token: str | None = None) -> UserSession | None: """ Get a session by token or return default if available. @@ -203,7 +202,7 @@ class SessionManager: logger.info("Default NCFA cookie updated") @staticmethod - async def validate_cookie(cookie: str) -> Optional[dict]: + async def validate_cookie(cookie: str) -> dict | None: """ Validate a cookie by making a test request. diff --git a/src/geoguessr_mcp/auth/user_context.py b/src/geoguessr_mcp/auth/user_context.py index 3214b07..dbaae77 100644 --- a/src/geoguessr_mcp/auth/user_context.py +++ b/src/geoguessr_mcp/auth/user_context.py @@ -6,7 +6,6 @@ is making a request and their associated GeoGuessr session. """ from dataclasses import dataclass -from typing import Optional from .session import UserSession @@ -23,7 +22,7 @@ class UserContext: api_key: str """The API key used to authenticate this request""" - session: Optional[UserSession] = None + session: UserSession | None = None """The GeoGuessr session for this user (if logged in)""" @property @@ -41,7 +40,7 @@ class UserContext: return f"User-{hash(self.api_key) % 10000:04d}" @property - def ncfa_cookie(self) -> Optional[str]: + def ncfa_cookie(self) -> str | None: """Get the NCFA cookie for this user.""" if self.session: return self.session.ncfa_cookie diff --git a/src/geoguessr_mcp/config.py b/src/geoguessr_mcp/config.py index 6e94d18..0c16769 100644 --- a/src/geoguessr_mcp/config.py +++ b/src/geoguessr_mcp/config.py @@ -2,7 +2,6 @@ import os from dataclasses import dataclass, field -from typing import Optional @dataclass @@ -18,7 +17,7 @@ class Settings: GEOGUESSR_DOMAIN_NAME: str = "geoguessr.com" GEOGUESSR_API_URL: str = "https://www.geoguessr.com/api" GAME_SERVER_URL: str = "https://game-server.geoguessr.com/api" - DEFAULT_NCFA_COOKIE: Optional[str] = field( + DEFAULT_NCFA_COOKIE: str | None = field( default_factory=lambda: os.getenv("GEOGUESSR_NCFA_COOKIE") ) @@ -37,7 +36,7 @@ class Settings: MCP_AUTH_ENABLED: bool = field( default_factory=lambda: os.getenv("MCP_AUTH_ENABLED", "false").lower() == "true" ) - MCP_API_KEYS: Optional[str] = field(default_factory=lambda: os.getenv("MCP_API_KEYS")) + MCP_API_KEYS: str | None = field(default_factory=lambda: os.getenv("MCP_API_KEYS")) # Logging Configuration LOG_LEVEL: str = field(default_factory=lambda: os.getenv("LOG_LEVEL", "INFO")) diff --git a/src/geoguessr_mcp/middleware/auth.py b/src/geoguessr_mcp/middleware/auth.py index 7476226..72f0aa3 100644 --- a/src/geoguessr_mcp/middleware/auth.py +++ b/src/geoguessr_mcp/middleware/auth.py @@ -6,7 +6,6 @@ and attaches user context for multi-user support. """ import logging -from typing import Optional from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request @@ -26,7 +25,7 @@ class AuthenticationMiddleware(BaseHTTPMiddleware): Authorization header with a Bearer token matching one of the configured API keys. """ - def __init__(self, app, valid_api_keys: Optional[set[str]] = None): + def __init__(self, app, valid_api_keys: set[str] | None = None): super().__init__(app) self.valid_api_keys = valid_api_keys or settings.get_api_keys() self.enabled = settings.MCP_AUTH_ENABLED diff --git a/src/geoguessr_mcp/models/achievement.py b/src/geoguessr_mcp/models/achievement.py index 41943e3..500ef2a 100644 --- a/src/geoguessr_mcp/models/achievement.py +++ b/src/geoguessr_mcp/models/achievement.py @@ -1,7 +1,6 @@ """Achievement-related data models.""" from dataclasses import dataclass -from typing import Optional @dataclass @@ -12,9 +11,9 @@ class Achievement: name: str description: str = "" unlocked: bool = False - unlocked_at: Optional[str] = None + unlocked_at: str | None = None progress: float = 0.0 - icon_url: Optional[str] = None + icon_url: str | None = None @classmethod def from_api_response(cls, data: dict) -> "Achievement": diff --git a/src/geoguessr_mcp/models/daily_challenge.py b/src/geoguessr_mcp/models/daily_challenge.py index e0c648e..8889948 100644 --- a/src/geoguessr_mcp/models/daily_challenge.py +++ b/src/geoguessr_mcp/models/daily_challenge.py @@ -1,7 +1,6 @@ """DailyChallenge-related data models.""" from dataclasses import dataclass, field -from typing import Optional @dataclass @@ -13,7 +12,7 @@ class DailyChallenge: date: str = "" time_limit: int = 0 completed: bool = False - score: Optional[int] = None + score: int | None = None raw_data: dict = field(default_factory=dict) @classmethod diff --git a/src/geoguessr_mcp/models/game.py b/src/geoguessr_mcp/models/game.py index 3711fea..f18c5ee 100644 --- a/src/geoguessr_mcp/models/game.py +++ b/src/geoguessr_mcp/models/game.py @@ -1,7 +1,6 @@ """Game-related data models.""" from dataclasses import dataclass, field -from typing import Optional from .round_guess import RoundGuess @@ -15,7 +14,7 @@ class Game: mode: str total_score: int rounds: list[RoundGuess] = field(default_factory=list) - created_at: Optional[str] = None + created_at: str | None = None finished: bool = False raw_data: dict = field(default_factory=dict) diff --git a/src/geoguessr_mcp/models/round_guess.py b/src/geoguessr_mcp/models/round_guess.py index c157d15..8304811 100644 --- a/src/geoguessr_mcp/models/round_guess.py +++ b/src/geoguessr_mcp/models/round_guess.py @@ -1,7 +1,6 @@ """RoundGuess-related data models.""" from dataclasses import dataclass -from typing import Optional @dataclass @@ -12,8 +11,8 @@ class RoundGuess: score: int distance_meters: float time_seconds: int - lat: Optional[float] = None - lng: Optional[float] = None + lat: float | None = None + lng: float | None = None country: str = "" @classmethod diff --git a/src/geoguessr_mcp/models/user_profile.py b/src/geoguessr_mcp/models/user_profile.py index f4ae6cc..af9d217 100644 --- a/src/geoguessr_mcp/models/user_profile.py +++ b/src/geoguessr_mcp/models/user_profile.py @@ -1,7 +1,6 @@ """UserProfile-related data models.""" from dataclasses import dataclass, field -from typing import Optional @dataclass @@ -16,7 +15,7 @@ class UserProfile: created: str = "" is_verified: bool = False is_pro: bool = False - avatar_url: Optional[str] = None + avatar_url: str | None = None raw_data: dict = field(default_factory=dict) @classmethod diff --git a/src/geoguessr_mcp/monitoring/endpoint/endpoint_monitor.py b/src/geoguessr_mcp/monitoring/endpoint/endpoint_monitor.py index 45ed0a8..a98473c 100644 --- a/src/geoguessr_mcp/monitoring/endpoint/endpoint_monitor.py +++ b/src/geoguessr_mcp/monitoring/endpoint/endpoint_monitor.py @@ -12,9 +12,9 @@ Classes: """ import asyncio +import contextlib import logging from datetime import UTC, datetime -from typing import Optional import httpx @@ -119,14 +119,14 @@ class EndpointMonitor: def __init__( self, - registry: Optional[SchemaRegistry] = None, - ncfa_cookie: Optional[str] = None, + registry: SchemaRegistry | None = None, + ncfa_cookie: str | None = None, ): self.registry = registry or schema_registry self.ncfa_cookie = ncfa_cookie or settings.DEFAULT_NCFA_COOKIE self.results: list[MonitoringResult] = [] self._running = False - self._task: Optional[asyncio.Task] = None + self._task: asyncio.Task | None = None async def check_endpoint( self, @@ -286,10 +286,8 @@ class EndpointMonitor: self._running = False if self._task: self._task.cancel() - try: + with contextlib.suppress(asyncio.CancelledError): await self._task - except asyncio.CancelledError: - pass logger.info("Stopped periodic monitoring") async def _monitoring_loop(self) -> None: diff --git a/src/geoguessr_mcp/monitoring/endpoint/endpoint_monitoring_result.py b/src/geoguessr_mcp/monitoring/endpoint/endpoint_monitoring_result.py index 0028a66..59d97c8 100644 --- a/src/geoguessr_mcp/monitoring/endpoint/endpoint_monitoring_result.py +++ b/src/geoguessr_mcp/monitoring/endpoint/endpoint_monitoring_result.py @@ -8,7 +8,6 @@ an endpoint, including its availability, response time, and any errors encounter from dataclasses import dataclass, field from datetime import UTC, datetime -from typing import Optional @dataclass @@ -20,5 +19,5 @@ class MonitoringResult: response_code: int response_time_ms: float schema_changed: bool - error_message: Optional[str] = None + error_message: str | None = None timestamp: datetime = field(default_factory=lambda: datetime.now(UTC)) diff --git a/src/geoguessr_mcp/monitoring/schema/endpoint_schema.py b/src/geoguessr_mcp/monitoring/schema/endpoint_schema.py index c556b72..920a26a 100644 --- a/src/geoguessr_mcp/monitoring/schema/endpoint_schema.py +++ b/src/geoguessr_mcp/monitoring/schema/endpoint_schema.py @@ -11,7 +11,7 @@ schema information. import logging from dataclasses import dataclass, field from datetime import UTC, datetime -from typing import Any, Optional +from typing import Any from .schema_field import SchemaField @@ -29,8 +29,8 @@ class EndpointSchema: schema_hash: str = "" response_code: int = 200 is_available: bool = True - error_message: Optional[str] = None - sample_response: Optional[dict] = None + error_message: str | None = None + sample_response: dict | None = None def to_dict(self) -> dict: """Convert to dictionary for serialization.""" diff --git a/src/geoguessr_mcp/monitoring/schema/schema_field.py b/src/geoguessr_mcp/monitoring/schema/schema_field.py index db7a844..c753d24 100644 --- a/src/geoguessr_mcp/monitoring/schema/schema_field.py +++ b/src/geoguessr_mcp/monitoring/schema/schema_field.py @@ -6,7 +6,7 @@ a schema field, including its name, type, and other relevant metadata. """ from dataclasses import dataclass -from typing import Any, Optional +from typing import Any @dataclass @@ -16,6 +16,6 @@ class SchemaField: name: str field_type: str nullable: bool = False - nested_schema: Optional[dict] = None + nested_schema: dict | None = None example_value: Any = None description: str = "" diff --git a/src/geoguessr_mcp/monitoring/schema/schema_registry.py b/src/geoguessr_mcp/monitoring/schema/schema_registry.py index 2f08269..0b0dfcd 100644 --- a/src/geoguessr_mcp/monitoring/schema/schema_registry.py +++ b/src/geoguessr_mcp/monitoring/schema/schema_registry.py @@ -15,7 +15,7 @@ import logging import tempfile from datetime import UTC, datetime from pathlib import Path -from typing import Any, Optional +from typing import Any from .endpoint_schema import EndpointSchema from .schema_detector import SchemaDetector @@ -32,7 +32,7 @@ class SchemaRegistry: to track changes over time and adapt automatically. """ - def __init__(self, cache_dir: Optional[str] = None): + def __init__(self, cache_dir: str | None = None): self.cache_dir = Path(cache_dir or settings.SCHEMA_CACHE_DIR) # Try to create the cache directory, fall back to temp if permission denied @@ -187,7 +187,7 @@ class SchemaRegistry: ) self._save_schemas() - def get_schema(self, endpoint: str) -> Optional[EndpointSchema]: + def get_schema(self, endpoint: str) -> EndpointSchema | None: """Get the current schema for an endpoint.""" return self.schemas.get(endpoint) diff --git a/src/geoguessr_mcp/services/analysis_service.py b/src/geoguessr_mcp/services/analysis_service.py index c74091f..7b25307 100644 --- a/src/geoguessr_mcp/services/analysis_service.py +++ b/src/geoguessr_mcp/services/analysis_service.py @@ -7,7 +7,6 @@ dynamic data handling and LLM-friendly output formatting. import logging from dataclasses import dataclass, field -from typing import Optional from .game_service import GameService from .profile_service import ProfileService @@ -61,8 +60,8 @@ class AnalysisService: def __init__( self, client: GeoGuessrClient, - game_service: Optional[GameService] = None, - profile_service: Optional[ProfileService] = None, + game_service: GameService | None = None, + profile_service: ProfileService | None = None, ): self.client = client self.game_service = game_service or GameService(client) @@ -155,7 +154,7 @@ class AnalysisService: async def analyze_recent_games( self, count: int = 10, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> dict: """ Analyze recent games and provide statistics summary. @@ -181,7 +180,7 @@ class AnalysisService: async def get_performance_summary( self, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> dict: """ Get a comprehensive performance summary. @@ -246,7 +245,7 @@ class AnalysisService: async def get_strategy_recommendations( self, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> dict: """ Generate strategy recommendations based on performance analysis. diff --git a/src/geoguessr_mcp/services/game_service.py b/src/geoguessr_mcp/services/game_service.py index d60f5e6..4bdcf2c 100644 --- a/src/geoguessr_mcp/services/game_service.py +++ b/src/geoguessr_mcp/services/game_service.py @@ -5,9 +5,8 @@ Handles game history, details, and competitive data with dynamic schema support. """ import logging -from typing import Optional -from ..api import Endpoints, DynamicResponse, GeoGuessrClient +from ..api import DynamicResponse, Endpoints, GeoGuessrClient from ..models import DailyChallenge, Game, SeasonStats logger = logging.getLogger(__name__) @@ -22,7 +21,7 @@ class GameService: async def get_game_details( self, game_token: str, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> tuple[Game, DynamicResponse]: """ Get details for a specific game. @@ -45,7 +44,7 @@ class GameService: async def get_unfinished_games( self, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> DynamicResponse: """Get list of unfinished games.""" return await self.client.get(Endpoints.GAMES.GET_UNFINISHED_GAMES, session_token) @@ -53,7 +52,7 @@ class GameService: async def get_streak_game( self, game_token: str, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> DynamicResponse: """Get streak game details.""" endpoint = Endpoints.GAMES.get_streak_game(game_token) @@ -63,7 +62,7 @@ class GameService: self, count: int = 10, page: int = 0, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> DynamicResponse: """ Get the activity feed. @@ -82,7 +81,7 @@ class GameService: async def get_recent_games( self, count: int = 10, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> list[Game]: """ Get recent games from the activity feed. @@ -122,7 +121,7 @@ class GameService: async def get_season_stats( self, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> tuple[SeasonStats, DynamicResponse]: """Get active season statistics.""" response = await self.client.get( @@ -138,7 +137,7 @@ class GameService: async def get_daily_challenge( self, day: str = "today", - session_token: Optional[str] = None, + session_token: str | None = None, ) -> tuple[DailyChallenge, DynamicResponse]: """ Get daily challenge. @@ -162,7 +161,7 @@ class GameService: async def get_battle_royale( self, game_id: str, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> DynamicResponse: """Get battle royale game details.""" endpoint = Endpoints.GAME_SERVER.get_battle_royale(game_id) @@ -171,7 +170,7 @@ class GameService: async def get_duel( self, duel_id: str, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> DynamicResponse: """Get duel game details.""" endpoint = Endpoints.GAME_SERVER.get_duel(duel_id) @@ -179,7 +178,7 @@ class GameService: async def get_tournaments( self, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> DynamicResponse: """Get tournament information.""" return await self.client.get(Endpoints.GAME_SERVER.GET_TOURNAMENTS, session_token) diff --git a/src/geoguessr_mcp/services/profile_service.py b/src/geoguessr_mcp/services/profile_service.py index 5d784ff..39cf4d2 100644 --- a/src/geoguessr_mcp/services/profile_service.py +++ b/src/geoguessr_mcp/services/profile_service.py @@ -6,9 +6,8 @@ dynamic schema adaptation. """ import logging -from typing import Optional -from ..api import DynamicResponse, GeoGuessrClient, Endpoints +from ..api import DynamicResponse, Endpoints, GeoGuessrClient from ..models import Achievement, UserProfile, UserStats logger = logging.getLogger(__name__) @@ -22,7 +21,7 @@ class ProfileService: async def get_profile( self, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> tuple[UserProfile, DynamicResponse]: """ Get current user's profile. @@ -40,7 +39,7 @@ class ProfileService: async def get_stats( self, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> tuple[UserStats, DynamicResponse]: """ Get user statistics. @@ -58,7 +57,7 @@ class ProfileService: async def get_extended_stats( self, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> DynamicResponse: """ Get extended statistics. @@ -69,7 +68,7 @@ class ProfileService: async def get_achievements( self, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> tuple[list[Achievement], DynamicResponse]: """ Get user achievements. @@ -96,7 +95,7 @@ class ProfileService: async def get_public_profile( self, user_id: str, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> tuple[UserProfile, DynamicResponse]: """Get another user's public profile.""" endpoint = Endpoints.PROFILES.get_public_profile(user_id) @@ -110,14 +109,14 @@ class ProfileService: async def get_user_maps( self, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> DynamicResponse: """Get user's custom maps.""" return await self.client.get(Endpoints.PROFILES.GET_USER_MAPS, session_token) async def get_comprehensive_profile( self, - session_token: Optional[str] = None, + session_token: str | None = None, ) -> dict: """ Get a comprehensive profile combining multiple endpoints. diff --git a/src/geoguessr_mcp/tools/game_tools.py b/src/geoguessr_mcp/tools/game_tools.py index f707e69..d9cd345 100644 --- a/src/geoguessr_mcp/tools/game_tools.py +++ b/src/geoguessr_mcp/tools/game_tools.py @@ -99,7 +99,7 @@ def register_game_tools(mcp: FastMCP, game_service: GameService): "summary": { "total_score": sum(g.total_score for g in games), "average_score": sum(g.total_score for g in games) / len(games) if games else 0, - "maps_played": list(set(g.map_name for g in games)), + "maps_played": list({g.map_name for g in games}), }, } diff --git a/src/tests/conftest.py b/src/tests/conftest.py index e365300..7c15233 100644 --- a/src/tests/conftest.py +++ b/src/tests/conftest.py @@ -9,7 +9,7 @@ from geoguessr_mcp.api import GeoGuessrClient from geoguessr_mcp.api.dynamic_response import DynamicResponse from geoguessr_mcp.auth import SessionManager, UserSession from geoguessr_mcp.config import settings -from geoguessr_mcp.models import RoundGuess, Game +from geoguessr_mcp.models import Game, RoundGuess from geoguessr_mcp.services import AnalysisService, GameService, ProfileService diff --git a/src/tests/integration/test_api_client.py b/src/tests/integration/test_api_client.py index f5b58e6..b961367 100644 --- a/src/tests/integration/test_api_client.py +++ b/src/tests/integration/test_api_client.py @@ -10,7 +10,7 @@ from unittest.mock import AsyncMock, MagicMock, patch import httpx import pytest -from geoguessr_mcp.api import DynamicResponse, GeoGuessrClient, EndpointInfo, Endpoints +from geoguessr_mcp.api import DynamicResponse, EndpointInfo, Endpoints, GeoGuessrClient from geoguessr_mcp.config import settings diff --git a/src/tests/integration/test_auth_flow.py b/src/tests/integration/test_auth_flow.py index c6800d9..7daec2f 100644 --- a/src/tests/integration/test_auth_flow.py +++ b/src/tests/integration/test_auth_flow.py @@ -187,7 +187,7 @@ class TestAuthenticationFlow: assert "expired_user" not in session_manager._user_sessions @pytest.mark.asyncio - async def test_default_cookie_fallback(self, session_manager): + async def test_default_cookie_fallback(self): """Test falling back to default cookie when no session exists.""" # Create manager with default cookie manager_with_default = SessionManager(default_cookie="default_test_cookie") diff --git a/src/tests/unit/auth/test_multi_user_session.py b/src/tests/unit/auth/test_multi_user_session.py index eea2ac6..14dea05 100644 --- a/src/tests/unit/auth/test_multi_user_session.py +++ b/src/tests/unit/auth/test_multi_user_session.py @@ -26,8 +26,8 @@ class TestMultiUserSessionManager: @pytest.mark.asyncio async def test_get_user_context_reuses_existing_manager(self, manager): """Test that getting context for existing API key reuses the same manager.""" - context1 = await manager.get_user_context("existing_key") - context2 = await manager.get_user_context("existing_key") + await manager.get_user_context("existing_key") + await manager.get_user_context("existing_key") # Should use the same manager instance assert manager._user_managers["existing_key"] is manager._user_managers["existing_key"] @@ -36,9 +36,9 @@ class TestMultiUserSessionManager: @pytest.mark.asyncio async def test_multiple_api_keys_get_separate_managers(self, manager): """Test that different API keys get separate session managers.""" - context1 = await manager.get_user_context("key1") - context2 = await manager.get_user_context("key2") - context3 = await manager.get_user_context("key3") + await manager.get_user_context("key1") + await manager.get_user_context("key2") + await manager.get_user_context("key3") assert len(manager._user_managers) == 3 assert manager._user_managers["key1"] is not manager._user_managers["key2"] @@ -61,7 +61,7 @@ class TestMultiUserSessionManager: assert session is None @pytest.mark.asyncio - async def test_login_user_creates_manager_if_not_exists(self, manager): + async def test_login_user_creates_manager_if_not_exists(self): """Test that login_user creates a manager if it doesn't exist.""" # This test requires mocking the HTTP client for GeoGuessr API # We'll mark it as a placeholder for now diff --git a/src/tests/unit/auth/test_user_context.py b/src/tests/unit/auth/test_user_context.py index 8bc569c..cbc5376 100644 --- a/src/tests/unit/auth/test_user_context.py +++ b/src/tests/unit/auth/test_user_context.py @@ -1,10 +1,9 @@ """Tests for UserContext class.""" -import pytest +from datetime import UTC, datetime, timedelta from geoguessr_mcp.auth.session import UserSession from geoguessr_mcp.auth.user_context import UserContext -from datetime import datetime, timedelta, UTC class TestUserContext: diff --git a/src/tests/unit/services/test_game_service.py b/src/tests/unit/services/test_game_service.py index 415b363..d07e31f 100644 --- a/src/tests/unit/services/test_game_service.py +++ b/src/tests/unit/services/test_game_service.py @@ -11,7 +11,7 @@ feeds, recent games, season statistics, and daily challenges. import pytest -from geoguessr_mcp.models import Game, SeasonStats, DailyChallenge +from geoguessr_mcp.models import DailyChallenge, Game, SeasonStats class TestGameService: diff --git a/src/tests/unit/services/test_profile_service.py b/src/tests/unit/services/test_profile_service.py index 294ed33..4492634 100644 --- a/src/tests/unit/services/test_profile_service.py +++ b/src/tests/unit/services/test_profile_service.py @@ -9,7 +9,7 @@ operations. import pytest -from geoguessr_mcp.models import UserProfile, UserStats, Achievement +from geoguessr_mcp.models import Achievement, UserProfile, UserStats class TestProfileService: