Robin Server
Robin Server
Section titled “Robin Server”Backend server for Robin Web, built with Rust and Salvo. It provides normalized JSON over HTTP, a read-only MCP server over Streamable HTTP, raw upstream passthrough endpoints, and static file serving.
claude mcp add --transport http robin-rwr https://robin.rwr-infra.uk/mcpclaude mcp add --transport http robin-rwr-cn https://robin.kreedzt.cn/mcpTechnical Stack
Section titled “Technical Stack”- Language: Rust
- HTTP framework: Salvo
- MCP transport:
rmcp+axum(Streamable HTTP) - Upstream parsing:
roxmltree(server XML),scraper(player HTML) - Tests:
cargo test --all-features
Environment Variables
Section titled “Environment Variables”| Variable | Default | Description |
|---|---|---|
HOST | 127.0.0.1 | Main HTTP server bind address |
PORT | 5800 | Main HTTP server port |
MAPS_CONFIG | maps.json | Maps configuration file path |
CACHE_DURATION_SECS | 3 | Upstream response cache expiry in seconds |
ANDROID_REPO_URL | (empty) | GitHub repository URL for Android app releases |
WEB_REPO_URL | (empty) | GitHub repository URL for Web app releases |
ANNOUNCEMENT_PATH | (empty) | HTML file served by /api/announcement; disabled when unset or empty |
MCP_HOST | 127.0.0.1 | MCP listener bind address; independent of HOST. IPv6 may be written ::1 or [::1] |
MCP_PORT | 5801 | MCP listener port; 0 disables MCP; must differ from PORT |
MCP_ALLOWED_HOSTS | (empty) | Comma-separated Host allowlist for MCP. Unset on a loopback bind uses the loopback defaults; unset on a non-loopback bind disables Host validation with a warning |
API Endpoints
Section titled “API Endpoints”GET /ping
Section titled “GET /ping”Health check endpoint that returns pong.
GET /api/version
Section titled “GET /api/version”Returns the latest release information for the configured Android and Web repositories.
{ "android": { "version": "v2.0.0", "url": "https://github.com/owner/android-repo/releases/tag/v2.0.0" }, "web": { "version": "v1.5.0", "url": "https://github.com/owner/web-repo/releases/tag/v1.5.0" }}GET /api/maps
Section titled “GET /api/maps”Returns the configured maps as a JSON array:
[ { "name": "map9", "path": "media/packages/vanilla.desert/maps/map9", "image": "md5_1.png" }]GET /api/announcement
Section titled “GET /api/announcement”Returns the operator-controlled announcement loaded from ANNOUNCEMENT_PATH at startup:
{ "enabled": true, "html": "<div class=\"notice\">Server maintenance at 20:00 UTC</div>"}GET /api/servers.json
Section titled “GET /api/servers.json”Returns normalized server data. Query parameters use snake_case; response fields use camelCase.
| Parameter | Description |
|---|---|
search | Case-insensitive search across server name, address, comment and IP |
online_only | true or false |
map | Filter by map name |
mode | Filter by game mode |
country | Filter by country |
offset | Pagination offset |
limit | Page size, default 20, maximum 100 |
Abbreviated response:
{ "total": 128, "offset": 0, "limit": 20, "servers": [ { "id": "203.0.113.10:7777", "name": "Example server" } ]}GET /api/players.json
Section titled “GET /api/players.json”Returns normalized player leaderboard data from the upstream view_players.php page.
| Parameter | Description |
|---|---|
search | Search the upstream leaderboard |
db | invasion, pacific, or prereset_invasion; default invasion |
sort | One of rank_progression, username, kills, deaths, kd, score, time_played, teamkills, longest_kill_streak, targets_destroyed, vehicles_destroyed, soldiers_healed, distance_moved, shots_fired, throwables_thrown, sid |
offset | Pagination offset |
limit | Page size, default 20, maximum 100 |
Abbreviated response:
{ "db": "invasion", "offset": 0, "limit": 2, "hasNext": true, "hasPrevious": false, "players": [ { "id": "invasion:RUNNING-PUSH", "username": "RUNNING-PUSH", "kills": 2449822 } ]}curl "http://localhost:5800/api/players.json?db=invasion&sort=kills&limit=10"Errors
Section titled “Errors”Both JSON endpoints return a JSON error body:
400 Bad Request— invalid query parameter; the upstream is not contacted.502 Bad Gateway— the upstream request failed or returned unparsable data.
Raw upstream passthroughs
Section titled “Raw upstream passthroughs”GET /api/server_list and GET /api/player_list proxy the upstream XML/HTML endpoints unchanged, including the original query string. Prefer the normalized JSON endpoints unless you need the raw upstream format.
MCP Server
Section titled “MCP Server”The process also serves a read-only Model Context Protocol server over Streamable HTTP.
- Self-hosted endpoint:
http://<MCP_HOST>:<MCP_PORT>/mcp; defaulthttp://127.0.0.1:5801/mcp. - Transport: stateless Streamable HTTP with JSON responses. Every
POST /mcpis independent; no session ID is required. - Tool results use
structuredContent. Invalid arguments return JSON-RPC-32602; upstream or not-found failures setisError: truewithstructuredContent.error. - Current release:
0.6.1. Theinitializeresponse reportsserverInfo.nameasrobin rwr.
| Tool | Parameters | Result |
|---|---|---|
list_servers | Same as /api/servers.json | {total, offset, limit, servers} |
get_server | address (string), port (integer) | One server object |
search_players | Same as /api/players.json | {db, offset, limit, hasNext, hasPrevious, players} |
list_maps | None | {maps: [...]} |
Connecting
Section titled “Connecting”Claude Code:
claude mcp add --transport http robin-rwr http://127.0.0.1:5801/mcpAny Streamable HTTP MCP client can use the same URL. There is no authentication layer in the server itself.
Security
Section titled “Security”- MCP has no authentication, OAuth, or rate limiting. Keep the listener on loopback, or put it behind an authenticating reverse proxy / network policy.
- MCP validates the incoming
Hostheader to block DNS rebinding. SetMCP_ALLOWED_HOSTSto the public hostname when exposing it through a proxy. MCP_PORT=0disables the MCP listener; the main HTTP server keeps running.MCP_PORTmust differ fromPORT.
Kubernetes
Section titled “Kubernetes”Keep only the existing ingress port public; do not expose 5801 at the edge.
- Set
MCP_HOST=0.0.0.0inside the Pod so the Service can reach it. - Add a ClusterIP Service port for
5801and route the/mcpIngress path to it. - Set
MCP_ALLOWED_HOSTSto the public Ingress hostname. - Use a
NetworkPolicyto allow only the ingress controller to reach5801.
env: - name: MCP_HOST value: "0.0.0.0" - name: MCP_PORT value: "5801" - name: MCP_ALLOWED_HOSTS value: "robin.example.com"Maps Configuration
Section titled “Maps Configuration”Maps are configured through a JSON file specified by the MAPS_CONFIG environment variable.
{ "maps": [ { "name": "map9", "path": "media/packages/vanilla.desert/maps/map9", "image": "md5_1.png" } ]}name: Human-readable map namepath: Full game path for the map (unique identifier)image: Image filename or CDN URL for map preview
Development
Section titled “Development”# Install Rust first, then:git clone https://github.com/rwr-infra/robin-server.gitcd robin-server
# Run development servercargo run
# Custom configurationHOST=0.0.0.0 PORT=8080 cargo runBuilding
Section titled “Building”# Local buildcargo build --release
# Docker builddocker build -t robin-server .Deployment
Section titled “Deployment”Docker Deployment
Section titled “Docker Deployment”# Basic deploymentdocker pull zhaozisong0/rwrs-server:latestdocker run -d -p 80:80 --name robin-server \ -e HOST=0.0.0.0 \ -e PORT=80 \ zhaozisong0/rwrs-server:latestTo expose MCP directly, publish 5801 as well and set an explicit allowlist:
docker run -d -p 80:80 -p 5801:5801 --name robin-server \ -e HOST=0.0.0.0 \ -e PORT=80 \ -e MCP_HOST=0.0.0.0 \ -e MCP_PORT=5801 \ -e MCP_ALLOWED_HOSTS=robin.example.com \ zhaozisong0/rwrs-server:latestFor production, prefer a single public ingress port and route /mcp internally as described in the Kubernetes section.
With Frontend
Section titled “With Frontend”# Copy frontend build to static directorydocker cp ./dist/. robin-server:/static/Docker Compose
Section titled “Docker Compose”version: '3'services: robin-server: image: zhaozisong0/rwrs-server:latest ports: - "80:80" - "5801:5801" environment: - HOST=0.0.0.0 - PORT=80 - MCP_HOST=0.0.0.0 - MCP_PORT=5801 - MCP_ALLOWED_HOSTS=robin.example.com - ANDROID_REPO_URL=https://github.com/rwr-infra/robin-android - WEB_REPO_URL=https://github.com/rwr-infra/robin-web volumes: - ./dist:/static - ./maps.json:/maps.jsonTesting
Section titled “Testing”# Run all testscargo test --all-features
# Run with outputcargo test --all-features -- --nocapture
# Run a specific test modulecargo test --all-features cache_tests
# Generate coverage reportcargo install cargo-llvm-covcargo llvm-cov --all-features- Repository: github.com/rwr-infra/robin-server
Related Tools
Section titled “Related Tools”- Robin Web - Frontend web interface
- Robin Android - Android client