Init Commit

This commit is contained in:
Yûki VACHOT 2025-11-28 19:24:17 +01:00
commit ce5abcc217
19 changed files with 2526 additions and 0 deletions

View file

@ -0,0 +1,53 @@
# Development Dockerfile for GeoGuessr MCP Server
FROM python:3.13-slim
# Prevent Python from writing pyc files and buffering stdout/stderr
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
git \
ssh \
sudo \
vim \
nano \
htop \
procps \
net-tools \
iputils-ping \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user for development
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN groupadd --gid $USER_GID $USERNAME \
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME
# Install uv for fast package management
RUN pip install --no-cache-dir uv
# Set up workspace
WORKDIR /workspace
# Copy pyproject.toml and poetry.lock (or similar) first for layer caching
COPY pyproject.toml README.md ./
# Install project and dev dependencies using uv
RUN uv pip install --system --no-cache --upgrade pip && \
uv pip install --system --no-cache -e ".[dev]"
COPY . .
# Switch to non-root user
USER $USERNAME
# Set Python path
ENV PYTHONPATH=/workspace
# Default command
CMD ["sleep", "infinity"]

View file

@ -0,0 +1,56 @@
{
"name": "GeoGuessr MCP Server Dev",
"dockerComposeFile": "docker-compose.dev.yml",
"service": "dev",
"workspaceFolder": "/workspace",
// Features to install
"features": {
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {}
},
// Configure tool-specific properties
"customizations": {
// VS Code settings (also works for some PyCharm features)
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.black-formatter",
"charliermarsh.ruff",
"tamasfe.even-better-toml",
"redhat.vscode-yaml"
],
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"python.formatting.provider": "black",
"python.linting.enabled": true,
"python.linting.ruffEnabled": true
}
}
},
// Ports to forward
"forwardPorts": [8000],
// Environment variables
"containerEnv": {
"PYTHONDONTWRITEBYTECODE": "1",
"PYTHONUNBUFFERED": "1"
},
// Run commands after container is created
"postCreateCommand": "pip install -e '.[dev]' && pre-commit install || true",
// Run commands when container starts
"postStartCommand": "echo 'Dev container ready! Run: python src/server.py'",
// Mount the .env file if it exists
"mounts": [
"source=${localWorkspaceFolder}/.env,target=/workspace/.env,type=bind,consistency=cached"
],
// User configuration
"remoteUser": "vscode"
}

View file

@ -0,0 +1,46 @@
version: '3.8'
services:
dev:
build:
context: ..
dockerfile: .devcontainer/Dockerfile.dev
volumes:
# Mount the workspace
- ..:/workspace:cached
# Persist VS Code extensions
- vscode-extensions:/home/vscode/.vscode-server/extensions
# Persist pip cache
- pip-cache:/home/vscode/.cache/pip
# Docker socket for Docker-in-Docker (optional)
# - /var/run/docker.sock:/var/run/docker.sock
# Keep container running
command: sleep infinity
# Environment
environment:
- PYTHONDONTWRITEBYTECODE=1
- PYTHONUNBUFFERED=1
# Load from .env file
- GEOGUESSR_NCFA_COOKIE=${GEOGUESSR_NCFA_COOKIE:-}
- MCP_TRANSPORT=streamable-http
- MCP_HOST=0.0.0.0
- MCP_PORT=8000
# Ports
ports:
- "8000:8000"
- "5678:5678" # debugpy port
# Network
networks:
- dev-network
volumes:
vscode-extensions:
pip-cache:
networks:
dev-network:
name: geoguessr-mcp-dev

36
.env.example Normal file
View file

@ -0,0 +1,36 @@
# GeoGuessr MCP Server Configuration
# Copy this file to .env and fill in your values
# =============================================================================
# REQUIRED: GeoGuessr Authentication
# =============================================================================
# Your GeoGuessr _ncfa cookie for API authentication
#
# How to get your _ncfa cookie:
# 1. Log in to GeoGuessr in your browser
# 2. Open Developer Tools (F12 or Ctrl+Shift+I)
# 3. Go to the "Application" or "Storage" tab
# 4. Under "Cookies", find www.geoguessr.com
# 5. Look for the cookie named "_ncfa"
# 6. Copy its value and paste it below
#
# IMPORTANT: Keep this secret! Anyone with this cookie can access your account.
# The cookie typically expires after some time, so you may need to update it periodically.
GEOGUESSR_NCFA_COOKIE=your_ncfa_cookie_value_here
# =============================================================================
# MCP Server Configuration
# =============================================================================
# Transport protocol: "streamable-http" (recommended) or "sse" (legacy)
MCP_TRANSPORT=streamable-http
# Port to expose the server on
MCP_PORT=8000
# =============================================================================
# Optional: API Key Authentication (recommended for production)
# =============================================================================
# If you want to require API key authentication for accessing the MCP server
# Uncomment and set a secure API key
# API_KEYS=your-secure-api-key-here,another-api-key-if-needed

37
.gitignore vendored Normal file
View file

@ -0,0 +1,37 @@
# Environment files (contain secrets!)
.env
*.env.local
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
.venv/
ENV/
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Docker
docker-compose.override.yml
# Logs
*.log
logs/
# SSL certificates (if stored locally)
ssl/
*.pem
*.crt
*.key

8
.idea/.gitignore generated vendored Normal file
View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

12
.idea/GeoguessrStatsMCP.iml generated Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13 (GeoguessrStatsMCP)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

8
.idea/modules.xml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/GeoguessrStatsMCP.iml" filepath="$PROJECT_DIR$/.idea/GeoguessrStatsMCP.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

387
DEVELOPMENT.md Normal file
View file

@ -0,0 +1,387 @@
# Development Guide: GeoGuessr MCP Server
This guide covers multiple ways to develop the GeoGuessr MCP Server using PyCharm, including remote development options.
## Table of Contents
1. [Option 1: PyCharm Remote Development (Gateway)](#option-1-pycharm-remote-development-gateway)
2. [Option 2: PyCharm with Docker Interpreter](#option-2-pycharm-with-docker-interpreter)
3. [Option 3: Dev Containers](#option-3-dev-containers)
4. [Option 4: SSH Remote Interpreter](#option-4-ssh-remote-interpreter)
5. [Running Tests](#running-tests)
6. [Debugging](#debugging)
---
## Option 1: PyCharm Remote Development (Gateway)
**Best for:** Full IDE experience on a remote server with PyCharm Professional.
### Prerequisites
- PyCharm Professional 2023.2+ or JetBrains Gateway
- SSH access to your VPS
### Setup Steps
1. **Install JetBrains Gateway** (or use PyCharm Professional's remote dev feature)
- Download from: https://www.jetbrains.com/remote-development/gateway/
2. **Connect to your VPS**
```
Gateway → New Connection → SSH
Host: your-vps-ip
User: your-username
Authentication: Key pair or password
```
3. **Clone the project on the server**
```bash
ssh user@your-vps
git clone https://github.com/yourusername/geoguessr-mcp.git
cd geoguessr-mcp
```
4. **Open in Gateway**
- Select IDE: PyCharm Professional
- Project directory: `/home/user/geoguessr-mcp`
- Click "Download and Start IDE"
5. **Configure the environment**
- Gateway will install a remote IDE backend
- Set up your Python interpreter (see below)
- Copy `.env.example` to `.env` and add your cookie
### Configure Python Interpreter in Gateway
1. File → Settings → Project → Python Interpreter
2. Add Interpreter → Add Local Interpreter
3. Select "Virtualenv Environment" → New
4. Location: `/home/user/geoguessr-mcp/.venv`
5. Base interpreter: `/usr/bin/python3.12`
6. Click OK
---
## Option 2: PyCharm with Docker Interpreter
**Best for:** Consistent dev environment, running PyCharm locally with Docker.
### Prerequisites
- PyCharm Professional
- Docker installed locally
- Docker Compose installed
### Setup Steps
1. **Open the project locally in PyCharm**
2. **Build the dev container**
```bash
cd geoguessr-mcp
docker compose -f .devcontainer/docker-compose.dev.yml build
```
3. **Configure Docker Interpreter**
a. File → Settings → Project → Python Interpreter
b. Click the gear icon → Add
c. Select "Docker Compose"
d. Configuration file: `.devcontainer/docker-compose.dev.yml`
e. Service: `dev`
f. Python interpreter: `/usr/local/bin/python`
g. Click OK
4. **Configure Environment Variables**
a. Run → Edit Configurations
b. Select your run configuration
c. Environment variables: Add `GEOGUESSR_NCFA_COOKIE=your_cookie`
Or create a `.env` file:
```bash
cp .env.example .env
# Edit .env with your cookie
```
5. **Run/Debug**
- Use the run configurations provided in `.idea/runConfigurations/`
### Docker Compose Commands
```bash
# Start dev container
docker compose -f .devcontainer/docker-compose.dev.yml up -d
# View logs
docker compose -f .devcontainer/docker-compose.dev.yml logs -f
# Stop
docker compose -f .devcontainer/docker-compose.dev.yml down
# Rebuild
docker compose -f .devcontainer/docker-compose.dev.yml build --no-cache
```
---
## Option 3: Dev Containers
**Best for:** VS Code users or PyCharm 2024.1+ with Dev Containers support.
### Using VS Code
1. Install the "Dev Containers" extension
2. Open the project folder
3. Click "Reopen in Container" when prompted (or F1 → "Dev Containers: Reopen in Container")
4. VS Code will build and connect to the dev container
### Using PyCharm (2024.1+)
PyCharm 2024.1+ has experimental Dev Containers support:
1. File → Remote Development → Dev Containers
2. Select the `.devcontainer/devcontainer.json` file
3. PyCharm will build and connect to the container
### Manual Container Development
If your IDE doesn't support dev containers natively:
```bash
# Start the dev container
docker compose -f .devcontainer/docker-compose.dev.yml up -d
# Exec into the container
docker exec -it geoguessr-mcp-dev-1 bash
# Inside container: run the server
cd /workspace
python server.py
# Inside container: run tests
pytest -v
```
---
## Option 4: SSH Remote Interpreter
**Best for:** PyCharm Professional users who want to run code directly on VPS.
### Setup Steps
1. **Prepare your VPS**
```bash
ssh user@your-vps
# Install Python 3.12
sudo apt update
sudo apt install python3.12 python3.12-venv python3.12-dev
# Clone the project
git clone https://github.com/yourusername/geoguessr-mcp.git
cd geoguessr-mcp
# Create virtual environment
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt
```
2. **Configure PyCharm SSH Interpreter**
a. File → Settings → Project → Python Interpreter
b. Click gear → Add → SSH Interpreter
c. New server configuration:
- Host: your-vps-ip
- Username: your-username
- Authentication: Key pair
d. Next → Interpreter path: `/home/user/geoguessr-mcp/.venv/bin/python`
e. Sync folders:
- Local: Your local project path
- Remote: `/home/user/geoguessr-mcp`
3. **Configure deployment**
a. Tools → Deployment → Configuration
b. Add SFTP server
c. Enable automatic upload (Tools → Deployment → Automatic Upload)
---
## Running Tests
### From PyCharm
- Use the "Run Tests" configuration
- Or right-click on `tests/` → Run 'pytest in tests'
### From Command Line
```bash
# Run all tests
pytest -v
# Run with coverage
pytest -v --cov=. --cov-report=html
# Run specific test file
pytest tests/test_server.py -v
# Run only unit tests (skip integration)
pytest -v -m "not integration"
# Run integration tests (requires real cookie)
GEOGUESSR_NCFA_COOKIE=your_real_cookie pytest -v -m integration
```
---
## Debugging
### PyCharm Debugger
1. Set breakpoints by clicking in the gutter
2. Run → Debug 'Debug MCP Server'
3. Use the debugger panel to:
- Step through code (F8)
- Step into functions (F7)
- Evaluate expressions
- View variables
### Remote Debugging (debugpy)
For debugging in Docker or remote environments:
1. **Modify server.py** to enable debugpy:
```python
import debugpy
debugpy.listen(("0.0.0.0", 5678))
print("Waiting for debugger...")
debugpy.wait_for_client()
```
2. **Configure PyCharm Remote Debug**
a. Run → Edit Configurations → Add → Python Debug Server
b. IDE host name: `localhost`
c. Port: `5678`
d. Path mappings:
- Local: `/path/to/local/project`
- Remote: `/workspace`
3. **Start debugging**
- Start the server (it will wait for debugger)
- Run the "Python Debug Server" configuration in PyCharm
- Server will continue execution
### Logging
The server uses Python's logging module. Increase verbosity:
```python
# In server.py, change:
logging.basicConfig(level=logging.DEBUG)
```
Or via environment:
```bash
export LOG_LEVEL=DEBUG
python server.py
```
---
## Code Quality
### Format Code
```bash
# Black
black .
# Ruff (lint + fix)
ruff check --fix .
```
### Type Checking
```bash
mypy server.py
```
### Pre-commit Hooks
```bash
# Install hooks
pre-commit install
# Run manually
pre-commit run --all-files
```
---
## Project Structure
```
geoguessr-mcp/
├── .devcontainer/ # Dev container configuration
│ ├── devcontainer.json
│ ├── docker-compose.dev.yml
│ └── Dockerfile.dev
├── .idea/ # PyCharm settings
│ └── runConfigurations/
├── tests/ # Test files
│ ├── __init__.py
│ └── test_server.py
├── server.py # Main MCP server
├── requirements.txt # Production dependencies
├── requirements-dev.txt # Development dependencies
├── pyproject.toml # Project configuration
├── Dockerfile # Production Dockerfile
├── docker-compose.yml # Production compose
└── README.md
```
---
## Tips
1. **Hot Reload**: Use `watchfiles` for auto-restart during development:
```bash
pip install watchfiles
watchfiles "python server.py" .
```
2. **Test MCP Connection**: Use the test script:
```bash
python test_server.py http://localhost:8000/mcp
```
3. **Environment Variables**: Always use `.env` file locally, never commit secrets
4. **Cookie Expiration**: GeoGuessr cookies expire - if tests start failing, get a fresh cookie

36
Dockerfile Normal file
View file

@ -0,0 +1,36 @@
# GeoGuessr MCP Server Dockerfile
FROM python:3.13-slim
# Set working directory
WORKDIR /app
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
# Install uv for faster package installation
RUN pip install --no-cache-dir uv
# Copy requirements first for better caching
COPY requirements.txt .
# Install Python dependencies
RUN uv pip install --system --no-cache -r requirements.txt
# Copy application code
COPY server.py .
# Expose the port
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Run the server
CMD ["python", "server.py"]

335
README.md Normal file
View file

@ -0,0 +1,335 @@
# GeoGuessr MCP Server
A Model Context Protocol (MCP) server for analyzing your GeoGuessr account data. This server allows Claude AI to access and analyze your GeoGuessr statistics, game history, achievements, and more.
## Authentication Options
The server supports **three authentication methods**:
1. **Login with credentials** (Recommended) - Use the `login` tool with your GeoGuessr email and password
2. **Manual cookie** - Use the `set_ncfa_cookie` tool with a cookie extracted from your browser
3. **Environment variable** - Set `GEOGUESSR_NCFA_COOKIE` for server-wide default authentication
### Using Login (Easiest)
Simply ask Claude to login:
```
"Login to GeoGuessr with email: myemail@example.com and password: mypassword"
```
The server will authenticate with GeoGuessr and create a session. Your credentials are only used once to obtain an authentication token - they are **not stored** on the server.
### Security Notes
- Credentials are sent directly to GeoGuessr's official API over HTTPS
- Session tokens are stored in memory only (lost on server restart)
- For persistent authentication, use the environment variable method
- Always use HTTPS in production to protect credentials in transit
## Features
### Authentication Tools
- **login** - Authenticate with email/password
- **logout** - End the current session
- **set_ncfa_cookie** - Set authentication cookie manually
- **set_session_token** - Restore a previous session
- **get_auth_status** - Check current authentication status
### Profile & Stats
- **get_my_profile** - Get your user profile information
- **get_my_stats** - Get detailed statistics displayed on your profile
- **get_extended_stats** - Get additional stats not shown on profile
- **get_my_achievements** - Get all your achievements
- **get_my_trophies** - Get your trophy collection
- **get_trophy_case** - Get your displayed trophy case
### Games & Activity
- **get_activity_feed** - Get recent activity (games, achievements, etc.)
- **get_game_details** - Get detailed info about a specific game
- **get_unfinished_games** - Get games you haven't completed
- **get_streak_game** - Get country streak game details
- **analyze_recent_games** - Analyze your recent games with statistics
### Competitive Modes
- **get_battle_royale_game** - Get Battle Royale game stats
- **get_duel_game** - Get duel game information
- **get_game_lobby** - Get lobby info with player stats
- **get_current_season_stats** - Get current season statistics
- **get_season_game_info** - Get season info for specific game modes
- **get_tournaments** - Get tournament information
### Challenges
- **get_daily_challenge** - Get today's or previous daily challenges
- **get_challenge_details** - Get details about a specific challenge
### Social
- **get_friends** - Get your friends list
- **get_friends_summary** - Get friends with requests and recommendations
- **get_notifications** - Get your notifications
- **search_user** - Search for other players
### Maps
- **get_my_maps** - Get maps you've created
- **get_liked_maps** - Get maps you've liked
- **get_map_info** - Get info about any map
- **get_popular_maps** - Get popular/featured/official maps
- **get_personalized_maps** - Get map recommendations
- **get_map_scores** - Get high scores for a map
### Explorer & Progress
- **get_explorer_progress** - Get your explorer mode progress
- **get_objectives** - Get current objectives
- **get_unclaimed_objectives** - Get rewards to claim
- **get_unclaimed_badges** - Get badges to claim
### Analysis
- **get_performance_summary** - Get comprehensive performance overview
## Prerequisites
- Docker and Docker Compose installed on your VPS
- A GeoGuessr account (Pro subscription recommended for full API access)
- Your GeoGuessr `_ncfa` authentication cookie
## Quick Start
### 1. Clone or copy the files to your VPS
```bash
mkdir -p ~/geoguessr-mcp
cd ~/geoguessr-mcp
# Copy all the files here
```
### 2. Build and run (no configuration required!)
```bash
docker compose up -d --build
```
That's it! The server now supports login via credentials, so you don't need to configure anything upfront.
### 3. (Optional) Configure default authentication
If you want server-wide default authentication without logging in each time:
#### Option A: Get your GeoGuessr authentication cookie
1. Log in to [GeoGuessr](https://www.geoguessr.com) in your browser
2. Open Developer Tools (F12 or Ctrl+Shift+I)
3. Go to the **Application** tab (Chrome) or **Storage** tab (Firefox)
4. Under **Cookies**, find `www.geoguessr.com`
5. Look for the cookie named `_ncfa`
6. Copy its value
```bash
cp .env.example .env
nano .env # Add your cookie
```
```env
GEOGUESSR_NCFA_COOKIE=your_actual_cookie_value_here
```
#### Option B: Just use login when connected
When connected to Claude, simply say:
> "Login to GeoGuessr with my email and password"
Claude will prompt you for credentials and authenticate.
### 4. Restart if you added environment variables
```bash
docker compose up -d --build
```
### 5. Verify it's running
```bash
docker compose logs -f
```
You should see:
```
Starting GeoGuessr MCP Server on 0.0.0.0:8000 with streamable-http transport
```
## Connecting to Claude
### Claude Desktop (macOS/Windows)
Add to your Claude Desktop configuration file:
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"geoguessr": {
"type": "streamable-http",
"url": "http://YOUR_VPS_IP:8000/mcp"
}
}
}
```
### VS Code with Copilot
1. Open VS Code
2. Run the command: `MCP: Add Server`
3. Choose "HTTP (Streamable HTTP)" as the transport
4. Enter URL: `http://YOUR_VPS_IP:8000/mcp`
### Claude.ai (if MCP support is enabled)
Add the server through Claude.ai's integrations settings with:
- URL: `http://YOUR_VPS_IP:8000/mcp`
- Transport: Streamable HTTP
## Using with Claude
Once connected, you can ask Claude questions like:
**First, authenticate (if not using environment variable):**
- "Login to GeoGuessr with email: myemail@example.com password: mypassword"
- "Check my GeoGuessr authentication status"
**Then analyze your data:**
- "Show me my GeoGuessr profile and stats"
- "Analyze my last 10 games and tell me how I'm doing"
- "What achievements have I unlocked?"
- "How am I doing in the current competitive season?"
- "Show me my activity feed from the last week"
- "What maps have I liked?"
- "Search for a player named [username]"
- "Get the details of my last Battle Royale game"
**When done (optional):**
- "Logout from GeoGuessr"
## Production Deployment
### Adding SSL with Nginx (Recommended)
For production, you should add SSL. Create an `nginx.conf`:
```nginx
events {
worker_connections 1024;
}
http {
upstream mcp_server {
server geoguessr-mcp:8000;
}
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
location / {
proxy_pass http://mcp_server;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}
}
}
```
### Adding API Key Authentication
For additional security, you can add API key authentication. Modify the server.py to check for an `x-api-key` header.
### Firewall Configuration
```bash
# Allow only specific IPs or use a firewall
sudo ufw allow from YOUR_IP to any port 8000
```
## Troubleshooting
### "GEOGUESSR_NCFA_COOKIE environment variable not set"
Make sure your `.env` file exists and contains the cookie:
```bash
cat .env | grep GEOGUESSR
```
### Cookie expired
GeoGuessr cookies expire periodically. If API calls start failing, get a fresh cookie from your browser.
### Connection refused
1. Check if the container is running: `docker compose ps`
2. Check logs: `docker compose logs geoguessr-mcp`
3. Verify the port is exposed: `docker port geoguessr-mcp-server`
### API rate limiting
GeoGuessr may rate-limit excessive API calls. Space out requests if you're hitting limits.
## Cookie Security
⚠️ **Important Security Notes:**
1. The `_ncfa` cookie provides full access to your GeoGuessr account
2. Never share your `.env` file or commit it to version control
3. Consider running the server on a private network
4. Use SSL in production
5. Regularly rotate your cookie
## Development
To run locally without Docker:
```bash
# Create virtual environment
python -m venv venv
source venv/bin/activate # or `venv\Scripts\activate` on Windows
# Install dependencies
pip install -r requirements.txt
# Set environment variable
export GEOGUESSR_NCFA_COOKIE="your_cookie_here"
# Run the server
python server.py
```
## API Reference
The server uses the unofficial GeoGuessr API. Key endpoints:
- `https://www.geoguessr.com/api/v3/` - Main API (v3)
- `https://www.geoguessr.com/api/v4/` - Newer API (v4)
- `https://game-server.geoguessr.com/api/` - Game server API
Note: This is an unofficial API and may change without notice.
## License
MIT License - Feel free to modify and distribute.
## Disclaimer
This project is not affiliated with, endorsed by, or connected to GeoGuessr AB. Use at your own risk and in accordance with GeoGuessr's Terms of Service.

0
docker-compose.prod.yml Normal file
View file

48
docker-compose.yml Normal file
View file

@ -0,0 +1,48 @@
version: '3.8'
services:
geoguessr-mcp:
build:
context: .
dockerfile: Dockerfile
container_name: geoguessr-mcp-server
restart: unless-stopped
ports:
- "${MCP_PORT:-8000}:8000"
environment:
# Required: Your GeoGuessr _ncfa cookie for authentication
- GEOGUESSR_NCFA_COOKIE=${GEOGUESSR_NCFA_COOKIE}
# MCP Server configuration
- MCP_TRANSPORT=${MCP_TRANSPORT:-streamable-http}
- MCP_HOST=0.0.0.0
- MCP_PORT=8000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Optional: Nginx reverse proxy with SSL (recommended for production)
# Uncomment and configure if you want SSL termination
# nginx:
# image: nginx:alpine
# container_name: geoguessr-mcp-nginx
# restart: unless-stopped
# ports:
# - "80:80"
# - "443:443"
# volumes:
# - ./nginx.conf:/etc/nginx/nginx.conf:ro
# - ./ssl:/etc/nginx/ssl:ro
# depends_on:
# - geoguessr-mcp
networks:
default:
name: geoguessr-mcp-network

162
pyproject.toml Normal file
View file

@ -0,0 +1,162 @@
[project]
name = "geoguessr-mcp"
version = "0.1.0"
description = "MCP server for analyzing GeoGuessr account data"
readme = "README.md"
requires-python = ">=3.13"
license = {text = "MIT"}
authors = [
{name = "Yûki VACHOT", email = "yuki.vachot@datasingularity.fr"}
]
keywords = ["mcp", "geoguessr", "claude", "ai", "model-context-protocol"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"mcp[cli]>=1.4.0",
"httpx>=0.27.0",
"uvicorn>=0.30.0",
"starlette>=0.38.0",
"python-dotenv>=1.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.23.0",
"pytest-cov>=4.1.0",
"pytest-mock>=3.12.0",
"respx>=0.20.0",
"black>=24.0.0",
"ruff>=0.3.0",
"mypy>=1.8.0",
"pre-commit>=3.6.0",
"debugpy>=1.8.0",
"ipython>=8.0.0",
"rich>=13.0.0"
]
[project.scripts]
geoguessr-mcp = "server:main"
[project.urls]
Homepage = "https://github.com/yourusername/geoguessr-mcp"
Repository = "https://github.com/yourusername/geoguessr-mcp"
Issues = "https://github.com/yourusername/geoguessr-mcp/issues"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["."]
# Black configuration
[tool.black]
line-length = 100
target-version = ['py310', 'py311', 'py312', 'py313']
include = '\.pyi?$'
exclude = '''
/(
\.eggs
| \.git
| \.hatch
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
)/
'''
# Ruff configuration
[tool.ruff]
line-length = 100
target-version = "py313"
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # Pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
"ARG", # flake8-unused-arguments
"SIM", # flake8-simplify
]
ignore = [
"E501", # line too long (handled by black)
"B008", # do not perform function calls in argument defaults
"C901", # too complex
"ARG001", # unused function argument
]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["ARG001", "S101"]
# Pytest configuration
[tool.pytest.ini_options]
minversion = "8.0"
asyncio_mode = "auto"
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_functions = ["test_*"]
addopts = [
"-v",
"--tb=short",
"--strict-markers",
]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"integration: marks tests as integration tests",
]
# Coverage configuration
[tool.coverage.run]
source = ["."]
omit = [
"tests/*",
".venv/*",
"*.pyc",
]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]
# MyPy configuration
[tool.mypy]
python_version = "3.13"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
follow_imports = "silent"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false

1089
src/server.py Normal file

File diff suppressed because it is too large Load diff

0
src/tests/__init__.py Normal file
View file

201
src/tests/test_server.py Normal file
View file

@ -0,0 +1,201 @@
"""
Tests for GeoGuessr MCP Server
"""
import pytest
from unittest.mock import AsyncMock, patch, MagicMock
import httpx
# Mock the environment variable before importing server
@pytest.fixture(autouse=True)
def mock_env(monkeypatch):
"""Set up environment variables for testing."""
monkeypatch.setenv("GEOGUESSR_NCFA_COOKIE", "test_cookie_value")
class TestProfileTools:
"""Tests for profile-related tools."""
@pytest.mark.asyncio
async def test_get_my_profile_success(self):
"""Test successful profile retrieval."""
from server import get_my_profile
mock_response = {
"id": "test-user-id",
"nick": "TestPlayer",
"country": "US",
"level": 50,
}
with patch("server.get_async_session") as mock_session:
mock_client = AsyncMock()
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
mock_http_response = MagicMock()
mock_http_response.json.return_value = mock_response
mock_http_response.raise_for_status = MagicMock()
mock_client.get = AsyncMock(return_value=mock_http_response)
mock_session.return_value = mock_client
result = await get_my_profile()
assert result["nick"] == "TestPlayer"
assert result["id"] == "test-user-id"
@pytest.mark.asyncio
async def test_get_my_stats_success(self):
"""Test successful stats retrieval."""
from server import get_my_stats
mock_response = {
"gamesPlayed": 100,
"averageScore": 4500,
"highScore": 5000,
}
with patch("server.get_async_session") as mock_session:
mock_client = AsyncMock()
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
mock_http_response = MagicMock()
mock_http_response.json.return_value = mock_response
mock_http_response.raise_for_status = MagicMock()
mock_client.get = AsyncMock(return_value=mock_http_response)
mock_session.return_value = mock_client
result = await get_my_stats()
assert result["gamesPlayed"] == 100
assert result["averageScore"] == 4500
class TestGameTools:
"""Tests for game-related tools."""
@pytest.mark.asyncio
async def test_get_game_details_success(self):
"""Test successful game details retrieval."""
from server import get_game_details
mock_response = {
"token": "ABC123",
"type": "standard",
"map": {"name": "World"},
"player": {
"guesses": [
{"roundScoreInPoints": 5000, "distanceInMeters": 0},
{"roundScoreInPoints": 4500, "distanceInMeters": 100},
]
}
}
with patch("server.get_async_session") as mock_session:
mock_client = AsyncMock()
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
mock_http_response = MagicMock()
mock_http_response.json.return_value = mock_response
mock_http_response.raise_for_status = MagicMock()
mock_client.get = AsyncMock(return_value=mock_http_response)
mock_session.return_value = mock_client
result = await get_game_details("ABC123")
assert result["token"] == "ABC123"
assert result["map"]["name"] == "World"
assert len(result["player"]["guesses"]) == 2
class TestAnalysisTools:
"""Tests for analysis tools."""
@pytest.mark.asyncio
async def test_analyze_recent_games_empty(self):
"""Test analysis with no games in feed."""
from server import analyze_recent_games
mock_feed_response = {"entries": []}
with patch("server.get_async_session") as mock_session:
mock_client = AsyncMock()
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
mock_http_response = MagicMock()
mock_http_response.json.return_value = mock_feed_response
mock_http_response.raise_for_status = MagicMock()
mock_client.get = AsyncMock(return_value=mock_http_response)
mock_session.return_value = mock_client
result = await analyze_recent_games(count=5)
assert result["games_analyzed"] == 0
assert result["total_score"] == 0
assert result["games"] == []
class TestAuthentication:
"""Tests for authentication handling."""
def test_get_ncfa_cookie_missing(self, monkeypatch):
"""Test error when cookie is not set."""
monkeypatch.delenv("GEOGUESSR_NCFA_COOKIE", raising=False)
from server import get_ncfa_cookie
with pytest.raises(ValueError, match="GEOGUESSR_NCFA_COOKIE"):
get_ncfa_cookie()
def test_get_ncfa_cookie_present(self, monkeypatch):
"""Test cookie retrieval when set."""
monkeypatch.setenv("GEOGUESSR_NCFA_COOKIE", "my_test_cookie")
from server import get_ncfa_cookie
cookie = get_ncfa_cookie()
assert cookie == "my_test_cookie"
# Integration tests (marked to skip by default)
@pytest.mark.integration
class TestIntegration:
"""Integration tests that require a real GeoGuessr cookie."""
@pytest.mark.asyncio
async def test_real_profile_fetch(self):
"""Test fetching real profile data."""
import os
if not os.environ.get("GEOGUESSR_NCFA_COOKIE") or \
os.environ.get("GEOGUESSR_NCFA_COOKIE") == "test_cookie_value":
pytest.skip("Real NCFA cookie not configured")
from server import get_my_profile
result = await get_my_profile()
assert "nick" in result
assert "id" in result
if __name__ == "__main__":
"""Run tests automatically when script is executed directly."""
import sys
# Run pytest with verbose output and show print statements
exit_code = pytest.main([
__file__,
"-v", # Verbose output
"-s", # Show print statements
"--tb=short", # Shorter traceback format
"-m", "not integration", # Skip integration tests by default
])
sys.exit(exit_code)