Skip to main content
Glama
lukejbyrne

world-cup-stats-mcp

by lukejbyrne
README.md
# World Cup Stats MCP

A read-only MCP server that gives compatible AI clients structured access to a
World Cup SQLite database. It supports local stdio development and
authenticated Streamable HTTP deployment from the same codebase.

```text
AI host
  └── MCP client
        ├── stdio ── local server process
        └── HTTPS ── remote server on Fly.io
                          └── read-only SQLite
```

## Production scope

For this project, production-ready means a private, single-tenant, read-only
service with:

- typed tools and bounded result sizes;
- read-only database access and safe tool errors;
- automated data, protocol and authenticated HTTP tests;
- a health endpoint and structured server logs;
- a pinned, non-root Docker image;
- TLS deployment on Fly.io with a pre-shared bearer token.

This is not an OAuth service for arbitrary third-party users. A public,
multi-user MCP product should replace the pre-shared token with a real OAuth
authorization server and add its own usage limits and monitoring.

## Available tools

- `list_tournaments` lists available men’s or women’s tournaments.
- `get_team_history` returns a country’s tournament records and totals.
- `get_head_to_head` finds World Cup meetings between two countries.
- `get_player_record` returns appearances, starts, goals and knockout statistics.

All tools are read-only. The server does not expose arbitrary SQL.

## Dataset

The project includes the SQLite edition of
[The Fjelstul World Cup Database](https://github.com/jfjelstul/worldcup):

- 22 men’s tournaments from 1930–2022;
- 8 women’s tournaments from 1991–2019;
- matches, teams, squads, appearances, goals, cards and standings.

Attribution, licence and the exact checksum are in
[`data/README.md`](data/README.md). The database and schema are distributed
under CC BY-SA 4.0.

## Local setup

Requirements:

- Python 3.12 or newer;
- [uv](https://docs.astral.sh/uv/);
- Node.js if using MCP Inspector.

Install the locked dependencies:

```bash
cd "/Users/lukebyrne/Documents/builds/world-cup-stats-mcp"
uv sync --no-editable
```

Run all tests:

```bash
uv run --no-editable pytest
```

Run an ordinary Python query before introducing MCP:

```bash
uv run --no-editable python scripts/demo_query.py
```

## Test the local stdio server

Launch MCP Inspector:

```bash
npx -y @modelcontextprotocol/inspector \
  uv \
  --directory "/Users/lukebyrne/Documents/builds/world-cup-stats-mcp" \
  run \
  --no-editable \
  world-cup-stats-mcp
```

In Inspector, open **Tools**, list the tools and call
`get_player_record` with `Kylian Mbappe`.

The ready-to-use stdio configuration is in [`mcp.json`](mcp.json).

## Test Streamable HTTP locally

Create a temporary token and start the HTTP transport:

```bash
export MCP_API_TOKEN="$(openssl rand -hex 32)"
export MCP_TRANSPORT="streamable-http"
export MCP_PUBLIC_BASE_URL="http://127.0.0.1:8000"
export MCP_ALLOWED_HOSTS="127.0.0.1:8000,localhost:8000"
uv run --no-editable world-cup-stats-mcp
```

The endpoints are:

```text
Health: http://127.0.0.1:8000/health
MCP:    http://127.0.0.1:8000/mcp
```

In Inspector, select **Streamable HTTP**, enter the MCP URL and paste the token
into **Bearer Token**. An omitted or incorrect token receives HTTP 401.

The repeatable command-line smoke test is:

```bash
MCP_SERVER_URL="http://127.0.0.1:8000/mcp" \
MCP_API_TOKEN="$MCP_API_TOKEN" \
uv run --no-editable python scripts/remote_smoke_test.py
```

## Build the container

The Docker image contains the immutable database snapshot and runs as a
non-root user:

```bash
docker build -t world-cup-stats-mcp .
docker run --rm \
  -p 8000:8000 \
  -e MCP_API_TOKEN="$MCP_API_TOKEN" \
  -e MCP_PUBLIC_BASE_URL="http://127.0.0.1:8000" \
  -e MCP_ALLOWED_HOSTS="127.0.0.1:8000,localhost:8000" \
  world-cup-stats-mcp
```

Fly can build the same Dockerfile remotely, so a local Docker daemon is not
required for deployment.

## Deploy to Fly.io

Install and authenticate Fly’s CLI:

```bash
brew install flyctl
fly auth login
```

The repository contains a checked-in `fly.toml`. Set the bearer token as a Fly
secret, then deploy:

```bash
fly secrets set MCP_API_TOKEN="$MCP_API_TOKEN"
fly deploy
```

Check the deployment:

```bash
fly status
fly logs
curl --fail "https://world-cup-mcp-luke.fly.dev/health"
```

Run the authenticated MCP smoke test:

```bash
MCP_SERVER_URL="https://world-cup-mcp-luke.fly.dev/mcp" \
MCP_API_TOKEN="$MCP_API_TOKEN" \
uv run --no-editable python scripts/remote_smoke_test.py
```

## Recording prompts

Start with:

> Compare Lionel Messi and Kylian Mbappé’s World Cup records through 2022.
> Include appearances, starts, total goals, knockout goals, final appearances
> and final goals. Use only the connected World Cup tools.

Then demonstrate a multi-tool question:

> Compare Argentina and France at the men’s World Cup since 1990. Include each
> team’s tournament record and their head-to-head matches, then give me an
> evidence-based verdict. Use only the connected World Cup tools.

The second prompt should call `get_team_history` twice and
`get_head_to_head` once.

## Data conventions

- Match wins and losses follow the source database’s convention. A knockout
  match decided on penalties counts as a win for the shootout winner.
- Shootout wins and losses are also returned separately.
- Own goals are excluded from personal goal totals.
- Germany and West Germany remain separate source teams.
- SQLite is opened with `mode=ro` and `PRAGMA query_only`.
- Set `WORLDCUP_DB_PATH` only when moving the database file.

TDQS

B3.3/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: listing tournaments, team history, head-to-head matchups, and player records. No overlap or ambiguity.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern (list_tournaments, get_team_history, get_head_to_head, get_player_record), using 'get' for data retrieval and 'list' for enumeration.

Tool Count4/5

With 4 tools, the server is slightly under-scoped for a World Cup stats domain, but each tool covers a meaningful query. The count is reasonable and not problematic.

Completeness3/5

The tool surface covers team history, head-to-head, and player records, but lacks individual match details or tournament standings, which are notable gaps for a stats MCP.

Maintenance

ActivitySlowing
ResponsivenessNo issues