Skip to main content
Glama
ebhattad

nfl-mcp

by ebhattad
README.md
# nfl-mcp

MCP server for NFL data (2013 through the current season), powered by [nflreadpy](https://github.com/nflverse/nflreadpy) and DuckDB.
Query play-by-play, rosters, injuries, stats, and more using natural language in Claude Code, VS Code, or Claude Desktop.

Ask Claude questions like:
- *"Who had the best EPA per play in 2024?"*
- *"Show me Patrick Mahomes' completion % over expected by season"*
- *"Compare 4th quarter red zone efficiency for KC vs PHI in 2023"*
- *"Which defenses had the highest sack rate in 3rd & long situations?"*
- *"Who was on IR for the Eagles in Week 10, 2023?"*
- *"Show me snap count trends for the Chiefs receiving corps in 2024"*

## Quickstart

```bash
pip install nfl-mcp        # or: uvx nfl-mcp
nfl-mcp init               # configure, load data, and start the server
```

`init` walks you through setup and offers to start the server immediately when done. No database server to install. No credentials to manage. Data is stored locally in DuckDB and can be refreshed through today with `nfl-mcp update`.

## Deploy to Azure

Run the server in the cloud as an [Azure Container App](https://learn.microsoft.com/azure/container-apps/) with one click:

[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Febhattad%2Fnfl-mcp%2Fmain%2Finfra%2Fazuredeploy.json)

The button opens the Azure portal's **Custom deployment** blade prefilled from [`infra/azuredeploy.json`](infra/azuredeploy.json). Pick a resource group, then **Create**. It provisions a Container Apps Environment, a Log Analytics workspace, and the Container App (public HTTPS ingress on port 8000). When the deployment finishes, the `mcpUrl` output is your endpoint — point any MCP client at `https://<app>.<region>.azurecontainerapps.io/mcp`.

> **The data is baked into the image.** The full DuckDB database is built into the container image at build time, so the app serves read-only with **no runtime ingest** — it starts instantly, never re-downloads data, needs no external storage, and runs comfortably on `0.5` vCPU / `1Gi`. To refresh the data, rebuild the image (re-run the publish workflow); the [`Publish container image`](.github/workflows/docker.yml) workflow also rebuilds weekly. The replica is pinned to a single instance (`minReplicas = maxReplicas = 1`).

**One-time setup before the button works:**
1. The [`Publish container image`](.github/workflows/docker.yml) workflow must have pushed an image to `ghcr.io/ebhattad/nfl-mcp` (it runs weekly, on each GitHub release, or manually via *Actions → Run workflow*). The build ingests all default datasets, so it takes longer than a normal image build.
2. Make that GHCR package **public**: repo → *Packages* → `nfl-mcp` → *Package settings* → *Change visibility → Public*. The ARM template pulls the image without credentials, so it must be public.

## Prerequisites

- Python 3.10+
- [uv](https://docs.astral.sh/uv/) (recommended) or pip

## Setup

### 1. Initialize

```bash
nfl-mcp init
```

The wizard will:
1. Configure the local DuckDB database path
2. Download the default NFL datasets (play-by-play, rosters, stats, injuries, and more)
3. Auto-configure your IDE (Claude Desktop and/or VS Code)
4. Offer to start the server immediately

Options:

```
--skip-ingest       Configure without loading data
```

### 2. Start the server

`init` offers to start the server for you. If you need to start it manually later:

```bash
nfl-mcp serve
nfl-mcp serve --port 9000
nfl-mcp serve --host 0.0.0.0
```

The server uses the [MCP Streamable HTTP](https://modelcontextprotocol.io/docs/concepts/transports) transport. Point any MCP client at `http://<host>:<port>/mcp`. The server also sends permissive CORS headers, so browser-based clients (e.g. the [MCP Inspector](https://github.com/modelcontextprotocol/inspector)) can connect — their preflight `OPTIONS` requests are answered instead of rejected.

> **Note:** The server must be running for your IDE to connect. Run `nfl-mcp serve` in a terminal and keep it open.

### 3. Verify

```bash
nfl-mcp doctor
```

Checks database connectivity, loaded data, and IDE configuration.

### 4. Manual client configuration (optional)

If you skipped IDE setup during init, or need to reconfigure:

```bash
nfl-mcp setup-client                    # auto-detect clients
nfl-mcp setup-client --client vscode    # VS Code only
nfl-mcp setup-client --client claude-desktop
```

Or configure manually. Add to `.vscode/mcp.json` (VS Code):

```json
{
  "servers": {
    "nfl": {
      "url": "http://localhost:8000/mcp"
    }
  }
}
```

Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (Claude Desktop):

```json
{
  "mcpServers": {
    "nfl": {
      "url": "http://localhost:8000/mcp"
    }
  }
}
```

## CLI Reference

```
nfl-mcp init               Interactive setup wizard
nfl-mcp serve              Start the MCP server (Streamable HTTP, default port 8000)
nfl-mcp ingest             Load NFL data into the database
nfl-mcp update             Refresh the current season as new games are played
nfl-mcp setup-client       Configure IDE MCP clients
nfl-mcp doctor             Health check
```

### Serve options

```bash
nfl-mcp serve
nfl-mcp serve --port 9000
nfl-mcp serve --host 0.0.0.0
nfl-mcp serve --auto-update              # refresh the current season while serving
```

### Ingestion options

```bash
nfl-mcp ingest                          # default datasets, all available seasons
nfl-mcp ingest --dataset all            # every dataset
nfl-mcp ingest --dataset schedules      # one specific dataset
nfl-mcp ingest --dataset pbp --dataset injuries   # multiple datasets
nfl-mcp ingest --start 2020 --end 2024  # limit to a season range
nfl-mcp ingest --fresh                  # re-ingest even if already loaded
nfl-mcp ingest --list                   # show all available dataset names
```

Ingest is **idempotent** — re-running skips datasets and seasons already in the database.

## In-season updates

nflverse rewrites the current season's files as games finish. `nfl-mcp update` refreshes only that season, leaving the rest of the database alone — it checks a published timestamp first and downloads only when that timestamp has moved.

```bash
nfl-mcp update                        # one pass over the current season
nfl-mcp update --watch --interval 2h  # keep polling
nfl-mcp serve --auto-update           # or refresh while serving, no downtime
```

Don't run `update` against a database a server already holds open — DuckDB allows a single writer. `serve --auto-update` sidesteps that by building a refreshed copy and swapping it in, and works in Docker via `NFL_MCP_AUTO_UPDATE=1` (off by default; mount a named volume at `/data` to keep the result). The file grows with each refresh, since DuckDB doesn't reclaim deleted pages; `nfl-mcp ingest --fresh` compacts it.

## Datasets

All data is sourced from [nflverse](https://github.com/nflverse/nflverse-data) via nflreadpy and stored locally in DuckDB. **Every dataset below is ingested by default** — `nfl-mcp ingest` loads the full nflverse family so any data a client might need is already there.

> **Season coverage: 2013 onward.** Season-based tables are ingested from **2013** — the window where every nflverse source is complete and consistent — through the current season. Datasets that begin later (e.g. Next Gen Stats 2016, FTN charting 2022) start at their first available season. Non-seasonal reference tables (draft picks, combine, contracts, players) carry their full historical record.

| Table | Loaded range |
|---|---|
| `plays` | 2013–present |
| `schedules` | 2013–present |
| `rosters` | 2013–present |
| `player_stats` | 2013–present |
| `team_stats_raw` | 2013–present |
| `injuries` | 2013–present |
| `snap_counts` | 2013–present |
| `depth_charts` | 2013–present |
| `rosters_weekly` | 2013–present |
| `ff_opportunity` | 2013–present |
| `officials` | 2015–present |
| `nextgen_stats_*` | 2016–present |
| `participation` | 2016–2024 |
| `pfr_advstats_*` | 2018–present |
| `ftn_charting` | 2022–present |
| `teams` | current |
| `players` | all-time |
| `contracts` | historical |
| `trades` | historical |
| `draft_picks` | 1980–present |
| `combine` | all-time |
| `ff_playerids` | current |
| `ff_rankings_draft` | current |
| `ff_rankings_week` | current |

```bash
nfl-mcp ingest                 # load the full nflverse family (default)
nfl-mcp ingest --list          # see all dataset names
nfl-mcp ingest --dataset pbp   # load just one dataset
```

## MCP Tools

| Tool | Description |
|------|-------------|
| `nfl_schema` | Database schema reference — compact summary by default, pass `category` for detail |
| `nfl_status` | Database health: total plays, loaded seasons, available tables |
| `nfl_query` | Raw SQL SELECT for custom queries (500 row cap, 10s timeout) |
| `nfl_search_plays` | Find plays by player, team, season, season type, situation, touchdowns, etc. |
| `nfl_team_stats` | Pre-aggregated team offense, defense, and situational stats |
| `nfl_player_stats` | Player stats by season and season type — passing, rushing, or receiving |
| `nfl_compare` | Side-by-side comparison of two teams or two players |
| `nfl_schedule` | Game schedule and results — scores, spread, weather, coaches |
| `nfl_roster` | Team roster by season and position |
| `nfl_injuries` | Player injury report status by team, week, and designation |
| `nfl_snap_counts` | Offensive, defensive, and special teams snap counts per player |
| `nfl_fantasy_opportunity` | Target share, air yards share, carry share, and expected fantasy points per player per week (2013–present) |
| `nfl_fantasy_rankings` | Expert consensus rankings (ECR) — draft/dynasty/best-ball (`scope=draft`) or current-week start/sit (`scope=week`) |
| `nfl_ftn_charting` | Aggregated FTN charting tendencies (2022–present) over scrimmage plays — play-action, RPO, screen, no-huddle, motion, trick-play rates, plus box/pass-rush/blitz counts |
| `nfl_td_luck` | Actual vs expected touchdowns per player-season — surfaces TD-regression candidates (most "unlucky" first) |
| `nfl_role_trend` | Rolling 3-week snap / target / carry / air-yards share with current-week delta — usage trending up or down |
| `nfl_separation_opportunity` | Next Gen Stats separation/YAC joined to fantasy opportunity (2016+) — flags receivers getting open but under-producing |
| `nfl_drop_rate` | Catchable-target drop rate per receiver-season from FTN charting (2022+), plus contested targets and created receptions |
| `nfl_contract_value` | Fantasy points per $M of average per year (APY) — best value-for-money players |
| `nfl_injury_return` | Post-return snap-share recovery (% of pre-injury baseline) at +1..+8 weeks, by normalized injury type and position |
| `nfl_catalog` | List all loaded tables with row counts and last refresh time |

## Key columns in `plays`

- `epa` — expected points added (the best single-play quality metric)
- `wpa` — win probability added
- `posteam` / `defteam` — offensive/defensive team abbreviations
- `passer_player_name` / `rusher_player_name` / `receiver_player_name`
- `play_type` — `'pass'` | `'run'` | `'field_goal'` | `'punt'` | `'kickoff'` | ...
- `desc` — raw play description (use `ILIKE` for text search)

## Local Development

```bash
git clone https://github.com/ebhattad/nfl-mcp
cd nfl-mcp
pip install -e ".[dev]"

nfl-mcp ingest --dataset all --start 2024 --end 2024
nfl-mcp serve   # server available at http://localhost:8000/mcp
pytest
pytest -m unit     # unit tests
pytest -m integration  # integration tests (requires loaded DB)
```

## Troubleshooting

- `nfl-mcp doctor` is the fastest way to verify config, database, and client setup.
- If tools return database errors, run `nfl-mcp ingest` to ensure data is loaded.
- You can override the DB location with `NFL_MCP_DB_PATH=/path/to/nflread.duckdb`.
- Re-running `nfl-mcp ingest` is safe — it skips anything already loaded.

## License

MIT