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
|
||||
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
|
||||
|
|
|
|||
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/
|
||||
├── 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
183
README.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
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