Add MCP server authentication and update Docker configuration

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
This commit is contained in:
Claude 2025-11-29 22:16:01 +00:00
parent 52d2f864a8
commit 07b1cb84b2
No known key found for this signature in database
10 changed files with 346 additions and 151 deletions

View file

@ -31,6 +31,19 @@ MCP_HOST=0.0.0.0
# Port to expose the server on
MCP_PORT=8000
# =============================================================================
# MCP Server Authentication
# =============================================================================
# Enable authentication for MCP server access (true/false)
# When enabled, clients must provide a valid API key in the Authorization header
MCP_AUTH_ENABLED=false
# Comma-separated list of valid API keys for MCP server access
# Example: MCP_API_KEYS=key1,key2,key3
# Clients connect using: Authorization: Bearer YOUR_API_KEY
# Generate secure keys with: openssl rand -hex 32
MCP_API_KEYS=
# =============================================================================
# API Monitoring Configuration
# =============================================================================
@ -57,12 +70,3 @@ REQUEST_TIMEOUT=30.0
# Maximum retry attempts for failed requests
MAX_RETRIES=3
# =============================================================================
# Docker Configuration (for Docker Compose deployments)
# =============================================================================
# Your Docker Hub username (used when pulling pre-built images)
DOCKER_USERNAME=yourusername
# Docker image tag to use (e.g., latest, v1.0.0, dev)
IMAGE_TAG=latest

View file

@ -12,7 +12,8 @@ This document provides context for AI assistants (like Claude) working on the Ge
geoguessr-mcp/
├── src/geoguessr_mcp/
│ ├── api/ # GeoGuessr API client and response handling
│ ├── auth/ # Authentication management
│ ├── auth/ # GeoGuessr authentication management
│ ├── middleware/ # MCP server authentication middleware
│ ├── models/ # Data models (Profile, Stats, Games, etc.)
│ ├── monitoring/ # Dynamic schema detection and API monitoring
│ │ ├── endpoint/ # Endpoint monitoring logic
@ -21,6 +22,8 @@ geoguessr-mcp/
│ ├── tools/ # MCP tool definitions
│ ├── config.py # Configuration and settings
│ └── main.py # Application entry point
├── scripts/ # Deployment scripts
│ └── deploy.sh # Automated production deployment
├── tests/ # Unit and integration tests
├── Dockerfile # Container definition
├── docker-compose.yml # Development deployment
@ -54,9 +57,19 @@ Tools are organized by domain:
Each tool returns a `DynamicResponse` which includes both the data and schema information.
### 3. Authentication Flow
### 3. Authentication Systems
The server supports three authentication methods:
The server has two authentication layers:
#### MCP Server Authentication (Access Control)
Controls who can connect to the MCP server:
- **Bearer Token**: API key-based authentication via `Authorization` header
- **Configuration**: `MCP_AUTH_ENABLED` and `MCP_API_KEYS` environment variables
- **Middleware**: `src/geoguessr_mcp/middleware/auth.py` - Validates API keys
- **Optional**: Can be disabled for local/trusted deployments
#### GeoGuessr API Authentication (Data Access)
The server supports three methods to access GeoGuessr's API:
1. **Environment variable**: `GEOGUESSR_NCFA_COOKIE` in .env
2. **Login tool**: Email/password authentication via MCP
3. **Manual cookie**: Direct cookie setting via tool
@ -65,6 +78,10 @@ Session state is managed in `src/geoguessr_mcp/auth/session.py`.
## Docker Deployment
### Official Docker Image
The server is available as a pre-built image: **`nyxiumyuuki/geoguessr-mcp:latest`**
### Build Process
The Dockerfile uses a multi-stage approach:
@ -76,9 +93,19 @@ The Dockerfile uses a multi-stage approach:
### Deployment Options
1. **Local Build**: `docker compose up -d --build`
2. **Docker Hub**: Build, tag, push, then pull on VPS
1. **Pre-built Image**: `docker compose up -d` (uses nyxiumyuuki/geoguessr-mcp:latest)
2. **Local Build**: Uncomment build section in docker-compose.yml
3. **Production**: Use `docker-compose.prod.yml` with nginx reverse proxy
4. **Automated**: Use `./scripts/deploy.sh` for production deployment
### MCP Server Authentication Configuration
The server supports optional Bearer token authentication:
- Set `MCP_AUTH_ENABLED=true` to enable
- Set `MCP_API_KEYS=key1,key2,key3` for comma-separated keys
- Generate secure keys with `openssl rand -hex 32`
- Clients connect with `Authorization: Bearer YOUR_API_KEY` header
- Middleware is in `src/geoguessr_mcp/middleware/auth.py`
### Monitoring Configuration

183
README.md
View file

@ -4,8 +4,8 @@ A Model Context Protocol (MCP) server for analyzing GeoGuessr game statistics wi
## TODO
- [ ] Fix Docker username in compose files and env vars
- [ ] Add authentication to MCP server to allow access only to specific users
- [x] ~~Fix Docker username in compose files and env vars~~
- [x] ~~Add authentication to MCP server to allow access only to specific users~~
- [ ] Fix Code Quality on tests not running
- [ ] Fix Code Quality on black not formatting
- [ ] Add auto monitoring for new endpoints and send notifications by email
@ -51,13 +51,28 @@ docker compose up -d --build
That's it! The server is now running on port 8000.
### 3. Connect to Claude
### 3. Configure MCP Server Authentication (Optional)
To secure your MCP server with API key authentication, edit `.env`:
```bash
MCP_AUTH_ENABLED=true
MCP_API_KEYS=your-secure-api-key-here
```
Generate a secure API key:
```bash
openssl rand -hex 32
```
### 4. Connect to Claude
Add to your Claude Desktop configuration:
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
**Without Authentication:**
```json
{
"mcpServers": {
@ -69,9 +84,58 @@ Add to your Claude Desktop configuration:
}
```
**With Authentication:**
```json
{
"mcpServers": {
"geoguessr": {
"type": "streamable-http",
"url": "http://YOUR_VPS_IP:8000/mcp",
"headers": {
"Authorization": "Bearer your-secure-api-key-here"
}
}
}
}
```
## 🔐 Authentication
The server supports multiple authentication methods:
The server supports two types of authentication:
### 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.
**Enable in `.env`:**
```bash
MCP_AUTH_ENABLED=true
MCP_API_KEYS=key1,key2,key3 # Comma-separated for multiple users
```
**Generate secure keys:**
```bash
openssl rand -hex 32
```
**Configure Claude Desktop with authentication:**
```json
{
"mcpServers": {
"geoguessr": {
"type": "streamable-http",
"url": "https://your-domain.com/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
### GeoGuessr API Authentication (Access GeoGuessr Data)
The server also needs authentication to access GeoGuessr's API. Multiple methods supported:
### Option 1: Login via Claude (Recommended)
Simply ask Claude:
@ -174,68 +238,49 @@ Claude uses explore_endpoint tool:
## 🏭 Production Deployment
### Method 1: Build and Push Docker Image
The server is available as a pre-built Docker image: **`nyxiumyuuki/geoguessr-mcp:latest`**
For VPS deployment, it's recommended to build and push your image to Docker Hub:
### Method 1: Quick Deploy with Script
For VPS deployment with existing nginx-proxy-manager:
```bash
# 1. Build the image
docker build -t yourusername/geoguessr-mcp:latest .
# Clone repository on VPS
git clone https://github.com/NyxiumYuuki/GeoGuessrMCP.git
cd GeoGuessrMCP
# 2. Login to Docker Hub
docker login
# Configure environment
cp .env.example .env
# Edit .env with your settings:
# - GEOGUESSR_NCFA_COOKIE (for GeoGuessr API access)
# - MCP_AUTH_ENABLED=true (optional, for MCP server security)
# - MCP_API_KEYS (if authentication enabled)
# 3. Push the image
docker push yourusername/geoguessr-mcp:latest
# 4. On your VPS, pull and run
docker pull yourusername/geoguessr-mcp:latest
# Run deployment script
./scripts/deploy.sh
```
### Method 2: Deploy with Docker Compose on VPS
### Method 2: Manual Docker Compose Deploy
#### Development/Testing Setup
```bash
# Clone repository on VPS
git clone https://github.com/yourusername/geoguessr-mcp.git
cd geoguessr-mcp
# Create .env file
cat > .env << EOF
GEOGUESSR_NCFA_COOKIE=your_cookie_here
DOCKER_USERNAME=yourusername
IMAGE_TAG=latest
EOF
# Deploy
# Using docker-compose.yml (development)
docker compose up -d
```
#### Production Setup with SSL (VPS with nginx-proxy-manager)
#### Production Setup with nginx-proxy-manager
If you have an existing nginx-proxy-manager setup (like with Firefly III), you can easily deploy this alongside it:
1. **Build and push your image to Docker Hub:**
```bash
docker build -t yourusername/geoguessr-mcp:latest .
docker push yourusername/geoguessr-mcp:latest
# Using docker-compose.prod.yml (production)
docker compose -f docker-compose.prod.yml up -d
```
2. **Deploy on VPS using the automated script:**
```bash
# On your VPS
cd /geoguessr-mcp
cp .env.production .env
# Edit .env with your DOCKER_USERNAME and GEOGUESSR_NCFA_COOKIE
./deploy.sh
```
3. **Configure SSL in nginx-proxy-manager:**
- Access admin panel: `http://your-vps-ip:81`
- Add Proxy Host for your domain
- Forward to: `geoguessr-mcp-server:8000`
- Enable SSL with Let's Encrypt
**Configure SSL in nginx-proxy-manager:**
- Access admin panel: `http://your-vps-ip:81`
- Add Proxy Host for your domain
- Forward to: `geoguessr-mcp-server:8000`
- Enable SSL with Let's Encrypt
**📖 For detailed VPS deployment instructions, see [DEPLOYMENT.md](DEPLOYMENT.md)**
@ -245,52 +290,52 @@ If you prefer not to use Docker Compose:
```bash
# Pull the image
docker pull yourusername/geoguessr-mcp:latest
docker pull nyxiumyuuki/geoguessr-mcp:latest
# Create a volume for schema cache
docker volume create geoguessr-schemas
# Run the container
# Run the container (without authentication)
docker run -d \
--name geoguessr-mcp \
--restart unless-stopped \
-p 8000:8000 \
-e GEOGUESSR_NCFA_COOKIE=your_cookie \
-e MCP_AUTH_ENABLED=false \
-e MONITORING_ENABLED=true \
-e MONITORING_INTERVAL_HOURS=24 \
-e LOG_LEVEL=INFO \
-v geoguessr-schemas:/app/data/schemas \
yourusername/geoguessr-mcp:latest
```
nyxiumyuuki/geoguessr-mcp:latest
### Building Multi-Architecture Images
For deployment on different CPU architectures (ARM64, AMD64):
```bash
# Enable buildx
docker buildx create --use
# Build and push multi-arch image
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t yourusername/geoguessr-mcp:latest \
--push .
# Run with MCP authentication enabled
docker run -d \
--name geoguessr-mcp \
--restart unless-stopped \
-p 8000:8000 \
-e GEOGUESSR_NCFA_COOKIE=your_cookie \
-e MCP_AUTH_ENABLED=true \
-e MCP_API_KEYS=your-api-key-1,your-api-key-2 \
-e MONITORING_ENABLED=true \
-e MONITORING_INTERVAL_HOURS=24 \
-e LOG_LEVEL=INFO \
-v geoguessr-schemas:/app/data/schemas \
nyxiumyuuki/geoguessr-mcp:latest
```
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `GEOGUESSR_NCFA_COOKIE` | - | Default authentication cookie |
| `GEOGUESSR_NCFA_COOKIE` | - | GeoGuessr API authentication cookie |
| `MCP_AUTH_ENABLED` | false | Enable MCP server authentication |
| `MCP_API_KEYS` | - | Comma-separated API keys for MCP access |
| `MCP_PORT` | 8000 | Server port |
| `MCP_TRANSPORT` | streamable-http | MCP transport protocol |
| `MONITORING_ENABLED` | true | Enable API monitoring |
| `MONITORING_INTERVAL_HOURS` | 24 | Monitoring check interval (runs every 24h) |
| `SCHEMA_CACHE_DIR` | /app/data/schemas | Directory for schema persistence |
| `LOG_LEVEL` | INFO | Logging verbosity |
| `DOCKER_USERNAME` | yourusername | Your Docker Hub username (for compose files) |
| `IMAGE_TAG` | latest | Docker image tag |
## 🧪 Development

View file

@ -3,14 +3,8 @@
services:
geoguessr-mcp:
# Option 1: Build locally (use for initial setup or development)
# build:
# context: .
# dockerfile: Dockerfile
# Option 2: Use pre-built image from Docker Hub (recommended for production)
# Update this with your Docker Hub username
image: ${DOCKER_USERNAME:-yourusername}/geoguessr-mcp:${IMAGE_TAG:-latest}
# Use pre-built image from Docker Hub
image: nyxiumyuuki/geoguessr-mcp:latest
container_name: geoguessr-mcp-server
restart: unless-stopped
@ -25,13 +19,6 @@ services:
volumes:
- geoguessr-schemas:/app/data/schemas
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8000/health" ]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
logging:
driver: "json-file"
options:

View file

@ -1,11 +1,13 @@
services:
geoguessr-mcp:
# Option 1: Build locally
build:
context: .
dockerfile: Dockerfile
# Option 2: Use pre-built image from Docker Hub (uncomment to use)
# image: ${DOCKER_USERNAME:-yourusername}/geoguessr-mcp:${IMAGE_TAG:-latest}
# Option 1: Build locally (for development)
# build:
# context: .
# dockerfile: Dockerfile
# Option 2: Use pre-built image from Docker Hub (recommended)
image: nyxiumyuuki/geoguessr-mcp:latest
container_name: geoguessr-mcp-server
restart: unless-stopped
ports:
@ -15,12 +17,6 @@ services:
volumes:
# Persist schema cache between restarts
- geoguessr-schemas:${SCHEMA_CACHE_DIR:-/app/data/schemas}
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:${MCP_PORT:-8000}/health" ]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
logging:
driver: "json-file"
options:

View file

@ -50,14 +50,14 @@ check_environment() {
print_success "Found $COMPOSE_FILE"
if [ ! -f "$ENV_FILE" ]; then
print_warning "$ENV_FILE not found. Creating from example..."
if [ -f "$ENV_EXAMPLE" ]; then
cp "$ENV_EXAMPLE" "$ENV_FILE"
print_warning "Please edit $ENV_FILE with your actual credentials!"
print_info "Required: DOCKER_USERNAME and GEOGUESSR_NCFA_COOKIE"
print_warning "$ENV_FILE not found. Creating from .env.example..."
if [ -f ".env.example" ]; then
cp ".env.example" "$ENV_FILE"
print_warning "Please edit $ENV_FILE with your actual configuration!"
print_info "Configure GEOGUESSR_NCFA_COOKIE and MCP_API_KEYS if using authentication"
exit 1
else
print_error "No .env.production example found!"
print_error "No .env.example found!"
exit 1
fi
fi
@ -84,23 +84,25 @@ check_config() {
source "$ENV_FILE"
if [ -z "$DOCKER_USERNAME" ] || [ "$DOCKER_USERNAME" == "yourusername" ]; then
print_error "DOCKER_USERNAME not configured in $ENV_FILE"
exit 1
fi
print_success "DOCKER_USERNAME is set: $DOCKER_USERNAME"
print_info "Using Docker image: nyxiumyuuki/geoguessr-mcp:latest"
if [ -z "$GEOGUESSR_NCFA_COOKIE" ] || [ "$GEOGUESSR_NCFA_COOKIE" == "your_actual_ncfa_cookie_value_here" ]; then
if [ -z "$GEOGUESSR_NCFA_COOKIE" ]; then
print_warning "GEOGUESSR_NCFA_COOKIE not configured"
print_info "Most features require authentication. You can set this later."
print_info "Most GeoGuessr features require authentication. You can set this later."
else
print_success "GEOGUESSR_NCFA_COOKIE is configured"
fi
if [ -z "$IMAGE_TAG" ]; then
IMAGE_TAG="latest"
if [ "$MCP_AUTH_ENABLED" == "true" ]; then
if [ -z "$MCP_API_KEYS" ]; then
print_error "MCP_AUTH_ENABLED is true but MCP_API_KEYS is not set!"
print_info "Either disable authentication or configure API keys"
exit 1
fi
print_success "MCP server authentication is ENABLED"
else
print_warning "MCP server authentication is DISABLED - server will be publicly accessible"
fi
print_info "Using image tag: $IMAGE_TAG"
}
# Check if firefly_network exists
@ -121,7 +123,7 @@ check_network() {
pull_image() {
print_header "Step 4: Pull Docker Image"
print_info "Pulling image: $DOCKER_USERNAME/geoguessr-mcp:$IMAGE_TAG"
print_info "Pulling image: nyxiumyuuki/geoguessr-mcp:latest"
docker compose -f "$COMPOSE_FILE" pull
print_success "Image pulled successfully"
}
@ -147,25 +149,16 @@ start_new() {
docker compose -f "$COMPOSE_FILE" up -d
print_success "Container started"
print_info "Waiting for container to be healthy..."
sleep 5
print_info "Waiting for container to start..."
sleep 3
# Check health
HEALTH=$(docker inspect geoguessr-mcp-server --format='{{.State.Health.Status}}' 2>/dev/null || echo "unknown")
if [ "$HEALTH" == "healthy" ]; then
print_success "Container is healthy!"
elif [ "$HEALTH" == "starting" ]; then
print_warning "Container is still starting... Check logs with:"
print_info "docker compose -f $COMPOSE_FILE logs -f"
# Check if running
if docker ps | grep -q geoguessr-mcp-server; then
print_success "Container is running"
else
print_warning "Container health check not yet available. Checking if running..."
if docker ps | grep -q geoguessr-mcp-server; then
print_success "Container is running"
else
print_error "Container is not running!"
print_info "Check logs with: docker compose -f $COMPOSE_FILE logs"
exit 1
fi
print_error "Container is not running!"
print_info "Check logs with: docker compose -f $COMPOSE_FILE logs"
exit 1
fi
}
@ -191,12 +184,23 @@ show_next_steps() {
echo " - Forward Port: 8000"
echo " - Enable SSL with Let's Encrypt"
echo ""
echo "2. Test the health endpoint:"
echo " curl https://your-domain.com/health"
echo "2. Test the MCP endpoint:"
echo " curl https://your-domain.com/mcp"
echo " (With auth): curl -H 'Authorization: Bearer YOUR_API_KEY' https://your-domain.com/mcp"
echo ""
echo "3. Connect Claude Desktop:"
echo " Add to claude_desktop_config.json:"
echo ' "geoguessr-mcp": { "url": "https://your-domain.com" }'
echo ' {'
echo ' "mcpServers": {'
echo ' "geoguessr": {'
echo ' "type": "streamable-http",'
echo ' "url": "https://your-domain.com/mcp",'
echo ' "headers": {'
echo ' "Authorization": "Bearer YOUR_API_KEY"'
echo ' }'
echo ' }'
echo ' }'
echo ' }'
echo ""
print_info "Useful Commands:"
@ -204,14 +208,14 @@ show_next_steps() {
echo " View logs: docker compose -f $COMPOSE_FILE logs -f"
echo " Restart: docker compose -f $COMPOSE_FILE restart"
echo " Stop: docker compose -f $COMPOSE_FILE down"
echo " Update: ./deploy.sh"
echo " Update: scripts/deploy.sh"
echo ""
print_info "Troubleshooting:"
echo ""
echo " Check status: docker ps | grep geoguessr-mcp"
echo " Check health: docker inspect geoguessr-mcp-server --format='{{.State.Health.Status}}'"
echo " Enter container: docker exec -it geoguessr-mcp-server /bin/bash"
echo " View all logs: docker compose -f $COMPOSE_FILE logs --tail=100"
echo ""
print_info "For detailed documentation, see: DEPLOYMENT.md"

View file

@ -33,6 +33,14 @@ class Settings:
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"))
@ -48,6 +56,15 @@ class Settings:
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()

View file

@ -11,6 +11,7 @@ import sys
from mcp.server.fastmcp import FastMCP
from .config import settings
from .middleware import AuthenticationMiddleware
from .monitoring import endpoint_monitor
from .tools import register_all_tools
@ -58,6 +59,12 @@ mcp = FastMCP(
# Register all tools
services = register_all_tools(mcp)
# Add authentication middleware if needed
if settings.MCP_AUTH_ENABLED:
logger.info("Registering authentication middleware")
# Add middleware to the underlying ASGI app
mcp.app.add_middleware(AuthenticationMiddleware)
async def start_background_tasks():
"""Start background monitoring tasks."""
@ -79,11 +86,17 @@ def main():
f"with {settings.TRANSPORT} transport"
)
if settings.MCP_AUTH_ENABLED:
api_key_count = len(settings.get_api_keys())
logger.info(f"MCP server authentication is ENABLED with {api_key_count} API key(s)")
else:
logger.warning("MCP server authentication is DISABLED - server is publicly accessible")
if settings.DEFAULT_NCFA_COOKIE:
logger.info("Default authentication cookie configured from environment")
logger.info("Default GeoGuessr authentication cookie configured from environment")
else:
logger.warning(
"No default authentication cookie set. " "Users will need to login or provide a cookie."
"No default GeoGuessr authentication cookie set. " "Users will need to login or provide a cookie."
)
# Run the server

View file

@ -0,0 +1,5 @@
"""Middleware for GeoGuessr MCP Server."""
from .auth import AuthenticationMiddleware
__all__ = ["AuthenticationMiddleware"]

View file

@ -0,0 +1,97 @@
"""
Authentication middleware for MCP server.
Provides Bearer token authentication for HTTP-based MCP transports.
"""
import logging
from typing import Optional
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from ..config import settings
logger = logging.getLogger(__name__)
class AuthenticationMiddleware(BaseHTTPMiddleware):
"""
Middleware to validate API keys via Bearer token authentication.
When MCP_AUTH_ENABLED is true, this middleware checks for a valid
Authorization header with a Bearer token matching one of the configured API keys.
"""
def __init__(self, app, valid_api_keys: Optional[set[str]] = None):
super().__init__(app)
self.valid_api_keys = valid_api_keys or settings.get_api_keys()
self.enabled = settings.MCP_AUTH_ENABLED
if self.enabled:
if not self.valid_api_keys:
logger.warning("Authentication is enabled but no API keys are configured!")
else:
logger.info(f"Authentication enabled with {len(self.valid_api_keys)} API key(s)")
else:
logger.info("Authentication is disabled")
async def dispatch(self, request: Request, call_next) -> Response:
"""Process the request and validate authentication if enabled."""
# Skip authentication if disabled
if not self.enabled:
return await call_next(request)
# Skip authentication for health check endpoint
if request.url.path == "/health":
return await call_next(request)
# Check for Authorization header
auth_header = request.headers.get("Authorization")
if not auth_header:
logger.warning(f"Missing Authorization header from {request.client.host}")
return JSONResponse(
status_code=401,
content={
"error": "Unauthorized",
"message": "Missing Authorization header. Use 'Authorization: Bearer YOUR_API_KEY'"
},
headers={"WWW-Authenticate": "Bearer"}
)
# Parse Bearer token
parts = auth_header.split()
if len(parts) != 2 or parts[0].lower() != "bearer":
logger.warning(f"Invalid Authorization header format from {request.client.host}")
return JSONResponse(
status_code=401,
content={
"error": "Unauthorized",
"message": "Invalid Authorization header format. Use 'Authorization: Bearer YOUR_API_KEY'"
},
headers={"WWW-Authenticate": "Bearer"}
)
token = parts[1]
# Validate token against configured API keys
if token not in self.valid_api_keys:
logger.warning(f"Invalid API key attempt from {request.client.host}")
return JSONResponse(
status_code=403,
content={
"error": "Forbidden",
"message": "Invalid API key"
},
headers={"WWW-Authenticate": "Bearer"}
)
# Authentication successful
logger.debug(f"Authenticated request from {request.client.host}")
# Proceed with the request
response = await call_next(request)
return response