This commit implements several key improvements to the GeoGuessr MCP server: ## MCP Server Authentication - Add Bearer token authentication for MCP server access control - New middleware in src/geoguessr_mcp/middleware/auth.py - Configuration via MCP_AUTH_ENABLED and MCP_API_KEYS environment variables - Support for multiple API keys (comma-separated) - Optional authentication - can be disabled for trusted deployments - Clients connect using Authorization: Bearer YOUR_API_KEY header ## Docker Configuration Updates - Update to use official pre-built image: nyxiumyuuki/geoguessr-mcp:latest - Remove DOCKER_USERNAME and IMAGE_TAG from environment variables - Simplify docker-compose.yml and docker-compose.prod.yml - Remove healthcheck configuration (not necessary for the deployment) ## Deployment Improvements - Move deploy.sh to scripts/deploy.sh for better organization - Update deploy.sh to use official Docker image - Add authentication validation in deployment script - Improve deployment logging and error messages ## Documentation Updates - Update README.md with authentication configuration examples - Add MCP server authentication section with setup instructions - Update environment variables table - Simplify deployment instructions - Update CLAUDE.md with new authentication architecture - Add .env.example configuration for MCP authentication ## Technical Details - Authentication middleware integrates with FastMCP's Starlette ASGI app - Middleware validates Bearer tokens on all requests except /health - Logs authentication attempts and failures - Returns proper 401/403 HTTP status codes - Validates configuration on startup to prevent misconfiguration Resolves TODO items: - [x] Fix Docker username in compose files and env vars - [x] Add authentication to MCP server to allow access only to specific users
70 lines
2.6 KiB
Python
70 lines
2.6 KiB
Python
"""Configuration management for GeoGuessr MCP Server."""
|
|
|
|
import os
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
|
|
|
|
@dataclass
|
|
class Settings:
|
|
"""Application settings loaded from environment variables."""
|
|
|
|
# MCP Server Configuration
|
|
HOST: str = field(default_factory=lambda: os.getenv("MCP_HOST", "0.0.0.0"))
|
|
PORT: int = field(default_factory=lambda: int(os.getenv("MCP_PORT", "8000")))
|
|
TRANSPORT: str = field(default_factory=lambda: os.getenv("MCP_TRANSPORT", "streamable-http"))
|
|
|
|
# GeoGuessr API Configuration
|
|
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_factory=lambda: os.getenv("GEOGUESSR_NCFA_COOKIE")
|
|
)
|
|
|
|
# API Monitoring Configuration
|
|
MONITORING_ENABLED: bool = field(
|
|
default_factory=lambda: os.getenv("MONITORING_ENABLED", "true").lower() == "true"
|
|
)
|
|
MONITORING_INTERVAL_HOURS: int = field(
|
|
default_factory=lambda: int(os.getenv("MONITORING_INTERVAL_HOURS", "24"))
|
|
)
|
|
SCHEMA_CACHE_DIR: str = field(
|
|
default_factory=lambda: os.getenv("SCHEMA_CACHE_DIR", "/app/data/schemas")
|
|
)
|
|
|
|
# Authentication Configuration
|
|
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")
|
|
)
|
|
|
|
# Logging Configuration
|
|
LOG_LEVEL: str = field(default_factory=lambda: os.getenv("LOG_LEVEL", "INFO"))
|
|
|
|
# Request Configuration
|
|
REQUEST_TIMEOUT: float = field(
|
|
default_factory=lambda: float(os.getenv("REQUEST_TIMEOUT", "30.0"))
|
|
)
|
|
MAX_RETRIES: int = field(default_factory=lambda: int(os.getenv("MAX_RETRIES", "3")))
|
|
|
|
def __post_init__(self):
|
|
"""Validate configuration after initialization."""
|
|
if self.PORT < 1 or self.PORT > 65535:
|
|
raise ValueError(f"Invalid port number: {self.PORT}")
|
|
if self.MONITORING_INTERVAL_HOURS < 1:
|
|
raise ValueError("Monitoring interval must be at least 1 hour")
|
|
if self.MCP_AUTH_ENABLED and not self.MCP_API_KEYS:
|
|
raise ValueError("MCP_AUTH_ENABLED is true but MCP_API_KEYS is not set")
|
|
|
|
def get_api_keys(self) -> set[str]:
|
|
"""Parse and return the set of valid API keys."""
|
|
if not self.MCP_API_KEYS:
|
|
return set()
|
|
# Support comma-separated API keys
|
|
return {key.strip() for key in self.MCP_API_KEYS.split(",") if key.strip()}
|
|
|
|
|
|
settings = Settings()
|