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:
parent
52d2f864a8
commit
07b1cb84b2
10 changed files with 346 additions and 151 deletions
22
.env.example
22
.env.example
|
|
@ -31,6 +31,19 @@ MCP_HOST=0.0.0.0
|
||||||
# Port to expose the server on
|
# Port to expose the server on
|
||||||
MCP_PORT=8000
|
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
|
# API Monitoring Configuration
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
@ -57,12 +70,3 @@ REQUEST_TIMEOUT=30.0
|
||||||
|
|
||||||
# Maximum retry attempts for failed requests
|
# Maximum retry attempts for failed requests
|
||||||
MAX_RETRIES=3
|
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
|
|
||||||
|
|
|
||||||
37
CLAUDE.md
37
CLAUDE.md
|
|
@ -12,7 +12,8 @@ This document provides context for AI assistants (like Claude) working on the Ge
|
||||||
geoguessr-mcp/
|
geoguessr-mcp/
|
||||||
├── src/geoguessr_mcp/
|
├── src/geoguessr_mcp/
|
||||||
│ ├── api/ # GeoGuessr API client and response handling
|
│ ├── 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.)
|
│ ├── models/ # Data models (Profile, Stats, Games, etc.)
|
||||||
│ ├── monitoring/ # Dynamic schema detection and API monitoring
|
│ ├── monitoring/ # Dynamic schema detection and API monitoring
|
||||||
│ │ ├── endpoint/ # Endpoint monitoring logic
|
│ │ ├── endpoint/ # Endpoint monitoring logic
|
||||||
|
|
@ -21,6 +22,8 @@ geoguessr-mcp/
|
||||||
│ ├── tools/ # MCP tool definitions
|
│ ├── tools/ # MCP tool definitions
|
||||||
│ ├── config.py # Configuration and settings
|
│ ├── config.py # Configuration and settings
|
||||||
│ └── main.py # Application entry point
|
│ └── main.py # Application entry point
|
||||||
|
├── scripts/ # Deployment scripts
|
||||||
|
│ └── deploy.sh # Automated production deployment
|
||||||
├── tests/ # Unit and integration tests
|
├── tests/ # Unit and integration tests
|
||||||
├── Dockerfile # Container definition
|
├── Dockerfile # Container definition
|
||||||
├── docker-compose.yml # Development deployment
|
├── 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.
|
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
|
1. **Environment variable**: `GEOGUESSR_NCFA_COOKIE` in .env
|
||||||
2. **Login tool**: Email/password authentication via MCP
|
2. **Login tool**: Email/password authentication via MCP
|
||||||
3. **Manual cookie**: Direct cookie setting via tool
|
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
|
## Docker Deployment
|
||||||
|
|
||||||
|
### Official Docker Image
|
||||||
|
|
||||||
|
The server is available as a pre-built image: **`nyxiumyuuki/geoguessr-mcp:latest`**
|
||||||
|
|
||||||
### Build Process
|
### Build Process
|
||||||
|
|
||||||
The Dockerfile uses a multi-stage approach:
|
The Dockerfile uses a multi-stage approach:
|
||||||
|
|
@ -76,9 +93,19 @@ The Dockerfile uses a multi-stage approach:
|
||||||
|
|
||||||
### Deployment Options
|
### Deployment Options
|
||||||
|
|
||||||
1. **Local Build**: `docker compose up -d --build`
|
1. **Pre-built Image**: `docker compose up -d` (uses nyxiumyuuki/geoguessr-mcp:latest)
|
||||||
2. **Docker Hub**: Build, tag, push, then pull on VPS
|
2. **Local Build**: Uncomment build section in docker-compose.yml
|
||||||
3. **Production**: Use `docker-compose.prod.yml` with nginx reverse proxy
|
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
|
### Monitoring Configuration
|
||||||
|
|
||||||
|
|
|
||||||
181
README.md
181
README.md
|
|
@ -4,8 +4,8 @@ A Model Context Protocol (MCP) server for analyzing GeoGuessr game statistics wi
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [ ] Fix Docker username in compose files and env vars
|
- [x] ~~Fix Docker username in compose files and env vars~~
|
||||||
- [ ] Add authentication to MCP server to allow access only to specific users
|
- [x] ~~Add authentication to MCP server to allow access only to specific users~~
|
||||||
- [ ] Fix Code Quality on tests not running
|
- [ ] Fix Code Quality on tests not running
|
||||||
- [ ] Fix Code Quality on black not formatting
|
- [ ] Fix Code Quality on black not formatting
|
||||||
- [ ] Add auto monitoring for new endpoints and send notifications by email
|
- [ ] 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.
|
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:
|
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`
|
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
||||||
|
|
||||||
|
**Without Authentication:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"mcpServers": {
|
"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
|
## 🔐 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)
|
### Option 1: Login via Claude (Recommended)
|
||||||
Simply ask Claude:
|
Simply ask Claude:
|
||||||
|
|
@ -174,68 +238,49 @@ Claude uses explore_endpoint tool:
|
||||||
|
|
||||||
## 🏭 Production Deployment
|
## 🏭 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
|
```bash
|
||||||
# 1. Build the image
|
# Clone repository on VPS
|
||||||
docker build -t yourusername/geoguessr-mcp:latest .
|
git clone https://github.com/NyxiumYuuki/GeoGuessrMCP.git
|
||||||
|
cd GeoGuessrMCP
|
||||||
|
|
||||||
# 2. Login to Docker Hub
|
# Configure environment
|
||||||
docker login
|
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
|
# Run deployment script
|
||||||
docker push yourusername/geoguessr-mcp:latest
|
./scripts/deploy.sh
|
||||||
|
|
||||||
# 4. On your VPS, pull and run
|
|
||||||
docker pull yourusername/geoguessr-mcp:latest
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Method 2: Deploy with Docker Compose on VPS
|
### Method 2: Manual Docker Compose Deploy
|
||||||
|
|
||||||
#### Development/Testing Setup
|
#### Development/Testing Setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone repository on VPS
|
# Using docker-compose.yml (development)
|
||||||
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
|
|
||||||
docker compose up -d
|
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
|
```bash
|
||||||
docker build -t yourusername/geoguessr-mcp:latest .
|
# Using docker-compose.prod.yml (production)
|
||||||
docker push yourusername/geoguessr-mcp:latest
|
docker compose -f docker-compose.prod.yml up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Deploy on VPS using the automated script:**
|
**Configure SSL in nginx-proxy-manager:**
|
||||||
```bash
|
- Access admin panel: `http://your-vps-ip:81`
|
||||||
# On your VPS
|
- Add Proxy Host for your domain
|
||||||
cd /geoguessr-mcp
|
- Forward to: `geoguessr-mcp-server:8000`
|
||||||
cp .env.production .env
|
- Enable SSL with Let's Encrypt
|
||||||
# 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
|
|
||||||
|
|
||||||
**📖 For detailed VPS deployment instructions, see [DEPLOYMENT.md](DEPLOYMENT.md)**
|
**📖 For detailed VPS deployment instructions, see [DEPLOYMENT.md](DEPLOYMENT.md)**
|
||||||
|
|
||||||
|
|
@ -245,52 +290,52 @@ If you prefer not to use Docker Compose:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Pull the image
|
# Pull the image
|
||||||
docker pull yourusername/geoguessr-mcp:latest
|
docker pull nyxiumyuuki/geoguessr-mcp:latest
|
||||||
|
|
||||||
# Create a volume for schema cache
|
# Create a volume for schema cache
|
||||||
docker volume create geoguessr-schemas
|
docker volume create geoguessr-schemas
|
||||||
|
|
||||||
# Run the container
|
# Run the container (without authentication)
|
||||||
docker run -d \
|
docker run -d \
|
||||||
--name geoguessr-mcp \
|
--name geoguessr-mcp \
|
||||||
--restart unless-stopped \
|
--restart unless-stopped \
|
||||||
-p 8000:8000 \
|
-p 8000:8000 \
|
||||||
-e GEOGUESSR_NCFA_COOKIE=your_cookie \
|
-e GEOGUESSR_NCFA_COOKIE=your_cookie \
|
||||||
|
-e MCP_AUTH_ENABLED=false \
|
||||||
-e MONITORING_ENABLED=true \
|
-e MONITORING_ENABLED=true \
|
||||||
-e MONITORING_INTERVAL_HOURS=24 \
|
-e MONITORING_INTERVAL_HOURS=24 \
|
||||||
-e LOG_LEVEL=INFO \
|
-e LOG_LEVEL=INFO \
|
||||||
-v geoguessr-schemas:/app/data/schemas \
|
-v geoguessr-schemas:/app/data/schemas \
|
||||||
yourusername/geoguessr-mcp:latest
|
nyxiumyuuki/geoguessr-mcp:latest
|
||||||
```
|
|
||||||
|
|
||||||
### Building Multi-Architecture Images
|
# Run with MCP authentication enabled
|
||||||
|
docker run -d \
|
||||||
For deployment on different CPU architectures (ARM64, AMD64):
|
--name geoguessr-mcp \
|
||||||
|
--restart unless-stopped \
|
||||||
```bash
|
-p 8000:8000 \
|
||||||
# Enable buildx
|
-e GEOGUESSR_NCFA_COOKIE=your_cookie \
|
||||||
docker buildx create --use
|
-e MCP_AUTH_ENABLED=true \
|
||||||
|
-e MCP_API_KEYS=your-api-key-1,your-api-key-2 \
|
||||||
# Build and push multi-arch image
|
-e MONITORING_ENABLED=true \
|
||||||
docker buildx build \
|
-e MONITORING_INTERVAL_HOURS=24 \
|
||||||
--platform linux/amd64,linux/arm64 \
|
-e LOG_LEVEL=INFO \
|
||||||
-t yourusername/geoguessr-mcp:latest \
|
-v geoguessr-schemas:/app/data/schemas \
|
||||||
--push .
|
nyxiumyuuki/geoguessr-mcp:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
| Variable | Default | Description |
|
| 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_PORT` | 8000 | Server port |
|
||||||
| `MCP_TRANSPORT` | streamable-http | MCP transport protocol |
|
| `MCP_TRANSPORT` | streamable-http | MCP transport protocol |
|
||||||
| `MONITORING_ENABLED` | true | Enable API monitoring |
|
| `MONITORING_ENABLED` | true | Enable API monitoring |
|
||||||
| `MONITORING_INTERVAL_HOURS` | 24 | Monitoring check interval (runs every 24h) |
|
| `MONITORING_INTERVAL_HOURS` | 24 | Monitoring check interval (runs every 24h) |
|
||||||
| `SCHEMA_CACHE_DIR` | /app/data/schemas | Directory for schema persistence |
|
| `SCHEMA_CACHE_DIR` | /app/data/schemas | Directory for schema persistence |
|
||||||
| `LOG_LEVEL` | INFO | Logging verbosity |
|
| `LOG_LEVEL` | INFO | Logging verbosity |
|
||||||
| `DOCKER_USERNAME` | yourusername | Your Docker Hub username (for compose files) |
|
|
||||||
| `IMAGE_TAG` | latest | Docker image tag |
|
|
||||||
|
|
||||||
## 🧪 Development
|
## 🧪 Development
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,8 @@
|
||||||
|
|
||||||
services:
|
services:
|
||||||
geoguessr-mcp:
|
geoguessr-mcp:
|
||||||
# Option 1: Build locally (use for initial setup or development)
|
# Use pre-built image from Docker Hub
|
||||||
# build:
|
image: nyxiumyuuki/geoguessr-mcp:latest
|
||||||
# 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}
|
|
||||||
|
|
||||||
container_name: geoguessr-mcp-server
|
container_name: geoguessr-mcp-server
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
@ -25,13 +19,6 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- geoguessr-schemas:/app/data/schemas
|
- geoguessr-schemas:/app/data/schemas
|
||||||
|
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD", "curl", "-f", "http://localhost:8000/health" ]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
start_period: 15s
|
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
driver: "json-file"
|
driver: "json-file"
|
||||||
options:
|
options:
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
services:
|
services:
|
||||||
geoguessr-mcp:
|
geoguessr-mcp:
|
||||||
# Option 1: Build locally
|
# Option 1: Build locally (for development)
|
||||||
build:
|
# build:
|
||||||
context: .
|
# context: .
|
||||||
dockerfile: Dockerfile
|
# dockerfile: Dockerfile
|
||||||
# Option 2: Use pre-built image from Docker Hub (uncomment to use)
|
|
||||||
# image: ${DOCKER_USERNAME:-yourusername}/geoguessr-mcp:${IMAGE_TAG:-latest}
|
# Option 2: Use pre-built image from Docker Hub (recommended)
|
||||||
|
image: nyxiumyuuki/geoguessr-mcp:latest
|
||||||
|
|
||||||
container_name: geoguessr-mcp-server
|
container_name: geoguessr-mcp-server
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
|
|
@ -15,12 +17,6 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
# Persist schema cache between restarts
|
# Persist schema cache between restarts
|
||||||
- geoguessr-schemas:${SCHEMA_CACHE_DIR:-/app/data/schemas}
|
- 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:
|
logging:
|
||||||
driver: "json-file"
|
driver: "json-file"
|
||||||
options:
|
options:
|
||||||
|
|
|
||||||
|
|
@ -50,14 +50,14 @@ check_environment() {
|
||||||
print_success "Found $COMPOSE_FILE"
|
print_success "Found $COMPOSE_FILE"
|
||||||
|
|
||||||
if [ ! -f "$ENV_FILE" ]; then
|
if [ ! -f "$ENV_FILE" ]; then
|
||||||
print_warning "$ENV_FILE not found. Creating from example..."
|
print_warning "$ENV_FILE not found. Creating from .env.example..."
|
||||||
if [ -f "$ENV_EXAMPLE" ]; then
|
if [ -f ".env.example" ]; then
|
||||||
cp "$ENV_EXAMPLE" "$ENV_FILE"
|
cp ".env.example" "$ENV_FILE"
|
||||||
print_warning "Please edit $ENV_FILE with your actual credentials!"
|
print_warning "Please edit $ENV_FILE with your actual configuration!"
|
||||||
print_info "Required: DOCKER_USERNAME and GEOGUESSR_NCFA_COOKIE"
|
print_info "Configure GEOGUESSR_NCFA_COOKIE and MCP_API_KEYS if using authentication"
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
print_error "No .env.production example found!"
|
print_error "No .env.example found!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
@ -84,23 +84,25 @@ check_config() {
|
||||||
|
|
||||||
source "$ENV_FILE"
|
source "$ENV_FILE"
|
||||||
|
|
||||||
if [ -z "$DOCKER_USERNAME" ] || [ "$DOCKER_USERNAME" == "yourusername" ]; then
|
print_info "Using Docker image: nyxiumyuuki/geoguessr-mcp:latest"
|
||||||
print_error "DOCKER_USERNAME not configured in $ENV_FILE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
print_success "DOCKER_USERNAME is set: $DOCKER_USERNAME"
|
|
||||||
|
|
||||||
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_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
|
else
|
||||||
print_success "GEOGUESSR_NCFA_COOKIE is configured"
|
print_success "GEOGUESSR_NCFA_COOKIE is configured"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$IMAGE_TAG" ]; then
|
if [ "$MCP_AUTH_ENABLED" == "true" ]; then
|
||||||
IMAGE_TAG="latest"
|
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
|
fi
|
||||||
print_info "Using image tag: $IMAGE_TAG"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if firefly_network exists
|
# Check if firefly_network exists
|
||||||
|
|
@ -121,7 +123,7 @@ check_network() {
|
||||||
pull_image() {
|
pull_image() {
|
||||||
print_header "Step 4: Pull Docker 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
|
docker compose -f "$COMPOSE_FILE" pull
|
||||||
print_success "Image pulled successfully"
|
print_success "Image pulled successfully"
|
||||||
}
|
}
|
||||||
|
|
@ -147,25 +149,16 @@ start_new() {
|
||||||
docker compose -f "$COMPOSE_FILE" up -d
|
docker compose -f "$COMPOSE_FILE" up -d
|
||||||
print_success "Container started"
|
print_success "Container started"
|
||||||
|
|
||||||
print_info "Waiting for container to be healthy..."
|
print_info "Waiting for container to start..."
|
||||||
sleep 5
|
sleep 3
|
||||||
|
|
||||||
# Check health
|
# Check if running
|
||||||
HEALTH=$(docker inspect geoguessr-mcp-server --format='{{.State.Health.Status}}' 2>/dev/null || echo "unknown")
|
if docker ps | grep -q geoguessr-mcp-server; then
|
||||||
if [ "$HEALTH" == "healthy" ]; then
|
print_success "Container is running"
|
||||||
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"
|
|
||||||
else
|
else
|
||||||
print_warning "Container health check not yet available. Checking if running..."
|
print_error "Container is not running!"
|
||||||
if docker ps | grep -q geoguessr-mcp-server; then
|
print_info "Check logs with: docker compose -f $COMPOSE_FILE logs"
|
||||||
print_success "Container is running"
|
exit 1
|
||||||
else
|
|
||||||
print_error "Container is not running!"
|
|
||||||
print_info "Check logs with: docker compose -f $COMPOSE_FILE logs"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,12 +184,23 @@ show_next_steps() {
|
||||||
echo " - Forward Port: 8000"
|
echo " - Forward Port: 8000"
|
||||||
echo " - Enable SSL with Let's Encrypt"
|
echo " - Enable SSL with Let's Encrypt"
|
||||||
echo ""
|
echo ""
|
||||||
echo "2. Test the health endpoint:"
|
echo "2. Test the MCP endpoint:"
|
||||||
echo " curl https://your-domain.com/health"
|
echo " curl https://your-domain.com/mcp"
|
||||||
|
echo " (With auth): curl -H 'Authorization: Bearer YOUR_API_KEY' https://your-domain.com/mcp"
|
||||||
echo ""
|
echo ""
|
||||||
echo "3. Connect Claude Desktop:"
|
echo "3. Connect Claude Desktop:"
|
||||||
echo " Add to claude_desktop_config.json:"
|
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 ""
|
echo ""
|
||||||
|
|
||||||
print_info "Useful Commands:"
|
print_info "Useful Commands:"
|
||||||
|
|
@ -204,14 +208,14 @@ show_next_steps() {
|
||||||
echo " View logs: docker compose -f $COMPOSE_FILE logs -f"
|
echo " View logs: docker compose -f $COMPOSE_FILE logs -f"
|
||||||
echo " Restart: docker compose -f $COMPOSE_FILE restart"
|
echo " Restart: docker compose -f $COMPOSE_FILE restart"
|
||||||
echo " Stop: docker compose -f $COMPOSE_FILE down"
|
echo " Stop: docker compose -f $COMPOSE_FILE down"
|
||||||
echo " Update: ./deploy.sh"
|
echo " Update: scripts/deploy.sh"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
print_info "Troubleshooting:"
|
print_info "Troubleshooting:"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Check status: docker ps | grep geoguessr-mcp"
|
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 " Enter container: docker exec -it geoguessr-mcp-server /bin/bash"
|
||||||
|
echo " View all logs: docker compose -f $COMPOSE_FILE logs --tail=100"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
print_info "For detailed documentation, see: DEPLOYMENT.md"
|
print_info "For detailed documentation, see: DEPLOYMENT.md"
|
||||||
|
|
@ -33,6 +33,14 @@ class Settings:
|
||||||
default_factory=lambda: os.getenv("SCHEMA_CACHE_DIR", "/app/data/schemas")
|
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
|
# Logging Configuration
|
||||||
LOG_LEVEL: str = field(default_factory=lambda: os.getenv("LOG_LEVEL", "INFO"))
|
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}")
|
raise ValueError(f"Invalid port number: {self.PORT}")
|
||||||
if self.MONITORING_INTERVAL_HOURS < 1:
|
if self.MONITORING_INTERVAL_HOURS < 1:
|
||||||
raise ValueError("Monitoring interval must be at least 1 hour")
|
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()
|
settings = Settings()
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import sys
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
from .config import settings
|
from .config import settings
|
||||||
|
from .middleware import AuthenticationMiddleware
|
||||||
from .monitoring import endpoint_monitor
|
from .monitoring import endpoint_monitor
|
||||||
from .tools import register_all_tools
|
from .tools import register_all_tools
|
||||||
|
|
||||||
|
|
@ -58,6 +59,12 @@ mcp = FastMCP(
|
||||||
# Register all tools
|
# Register all tools
|
||||||
services = register_all_tools(mcp)
|
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():
|
async def start_background_tasks():
|
||||||
"""Start background monitoring tasks."""
|
"""Start background monitoring tasks."""
|
||||||
|
|
@ -79,11 +86,17 @@ def main():
|
||||||
f"with {settings.TRANSPORT} transport"
|
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:
|
if settings.DEFAULT_NCFA_COOKIE:
|
||||||
logger.info("Default authentication cookie configured from environment")
|
logger.info("Default GeoGuessr authentication cookie configured from environment")
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
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
|
# Run the server
|
||||||
|
|
|
||||||
5
src/geoguessr_mcp/middleware/__init__.py
Normal file
5
src/geoguessr_mcp/middleware/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
"""Middleware for GeoGuessr MCP Server."""
|
||||||
|
|
||||||
|
from .auth import AuthenticationMiddleware
|
||||||
|
|
||||||
|
__all__ = ["AuthenticationMiddleware"]
|
||||||
97
src/geoguessr_mcp/middleware/auth.py
Normal file
97
src/geoguessr_mcp/middleware/auth.py
Normal 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue