jk-mcp-epl
# jk-mcp-epl
MCP server that gives Claude live access to Premier League data — teams, matches, standings, rosters, richer player data from Pulselive/Opta, and schedule-strength analytics.
[](https://github.com/jedi-knights/jk-mcp-epl/actions/workflows/ci.yml)
[](https://github.com/jedi-knights/jk-mcp-epl/actions/workflows/badge.yml)
[](https://jedi-knights.github.io/jk-mcp-epl/)
[](https://github.com/jedi-knights/jk-mcp-epl/actions/workflows/evals.yml)
[](https://github.com/jedi-knights/jk-mcp-epl/actions/workflows/release.yml)
[](https://www.python.org/downloads/)
[](LICENSE)
---
## Table of Contents
- [Overview](#overview)
- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Usage](#usage)
- [Configuration](#configuration)
- [Claude Code](#claude-code)
- [Claude Desktop](#claude-desktop)
- [Docker](#docker)
- [Development](#development)
- [Contributing](#contributing)
- [License](#license)
---
## Overview
AI assistants like Claude are knowledgeable, but they have a hard cutoff date — they cannot tell you today's Premier League standings, last night's scores, or which teams are fighting for the title, European places, or against relegation. This project fixes that.
It is an **MCP server** — a plugin that gives Claude direct access to live Premier League data: scores, standings, rosters, derived schedule-strength analytics, and richer per-player registrations from the Pulselive/Opta feed that powers premierleague.com. Once installed, you can ask Claude natural-language questions about the Premier League and get accurate, up-to-date answers. No subscription, no API key, and no programming required to use it.
This is the **v1 scaffold**: two data sources — ESPN's public API for the normalized league feed, and `footballapi.pulselive.com` (used unauthenticated, requires an `Origin: https://www.premierleague.com` header) for Opta-cross-referenced player data. Cup competitions (FA Cup, Carabao Cup) and per-half / xG stats are on the roadmap.
---
## Features
The v1 surface is twelve read-only, idempotent tools split across three tiers.
### ESPN-backed (8)
| Tool | Description |
|---|---|
| `get_teams` | List all 20 Premier League clubs with IDs and abbreviations |
| `get_team` | Details for a specific team |
| `get_roster` | Team's active roster — jersey, position, age, citizenship |
| `get_scoreboard` | Match scores for a single day, a date range, or the current matchweek |
| `get_team_schedule` | Every match for a team in the current season — past + upcoming |
| `get_match_details` | One match's full details — score, venue, attendance, goals, cards, subs |
| `get_standings` | Current standings — single 20-team table ordered by points |
| `get_news` | Recent Premier League news articles |
### Pulselive-backed (1)
| Tool | Description |
|---|---|
| `get_players` | Full player registry from the Opta-powered feed — Opta id, shirt number, positional detail (e.g. "Right Winger" vs generic "M"), current-club affiliation, nationality, DoB, loan flag |
### Derived analytics (3)
Pure functions over live standings + team schedules, exposing schedule-strength context the raw table does not.
| Tool | Description |
|---|---|
| `get_strength_of_schedule` | Team's average opponent points-per-game across matches already played |
| `get_results_by_opponent_tier` | Team's W-L-T split across current top / middle / bottom standings tiers |
| `get_adjusted_points_per_game` | Team's raw PPG alongside an opponent-quality-adjusted PPG |
### Roadmap
Deferred to v2+:
- FA Cup (`eng.fa`) and Carabao Cup (`eng.league_cup`) — ESPN slugs already available
- UEFA competitions (Champions League, Europa) — cross-competition tool surface
- Per-half and home/away split standings from Pulselive
- xG / shots / advanced Opta stats (Pulselive exposes them; needs an LLM-friendly formatter)
- Season lookup by year (currently `get_players` defaults to the most recent completed season)
---
## Requirements
- [Python 3.13+](https://www.python.org/downloads/)
- [uv](https://docs.astral.sh/uv/getting-started/installation/)
---
## Installation
```bash
git clone https://github.com/jedi-knights/jk-mcp-epl.git
cd jk-mcp-epl
uv sync
```
---
## Usage
Run the server in stdio mode (the default — used by Claude Code and Claude Desktop):
```bash
uv run python -m epl.server
```
Run in HTTP mode (for networked or deployed access):
```bash
MCP_TRANSPORT=streamable-http uv run python -m epl.server
```
### Example prompts
**Standings, scores, rosters:**
- Who is leading the Premier League right now?
- Show me every Premier League result from this past weekend.
- Who is on Arsenal's roster?
- When does Liverpool play next?
- Show me the full Pulselive player registry for Manchester City — who's on loan?
**Schedule strength:**
- Which Premier League team has played the toughest schedule so far?
- Show me Arsenal's record against the current top 6 teams.
- Compare Manchester City and Liverpool on adjusted points-per-game.
---
## Configuration
All configuration is via environment variables. None are required for local use.
| Variable | Default | Description |
|---|---|---|
| `MCP_TRANSPORT` | `stdio` | Transport mode: `stdio` or `streamable-http` |
| `HOST` | `0.0.0.0` | Bind address (HTTP transport only) |
| `PORT` | `8000` | TCP port (HTTP transport only) |
| `MCP_PATH` | `/mcp/epl` | URL path (HTTP transport only) |
| `API_HOST` | `https://site.api.espn.com` | ESPN API base URL |
| `LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, or `ERROR` |
| `MCP_TRACING_ENABLED` | unset | Bootstrap the OpenTelemetry SDK |
| `MCP_AUTH_ENABLED` | unset | Require RS256 bearer tokens on streamable-http |
| `MCP_AUTH_ISSUER_URL` | unset | Auth-server origin (required when auth is on) |
| `MCP_AUTH_RESOURCE_URL` | unset | This server's public URL for the `aud` claim |
---
## Claude Code
Install from your local clone globally so the server is available in every project:
```bash
claude mcp add --scope user epl -- uv run --directory /path/to/jk-mcp-epl python -m epl.server
```
Replace `/path/to/jk-mcp-epl` with the absolute path to your clone. Verify with `claude mcp list`.
Drop `--scope user` to register only for the current project, or commit a `.mcp.json` to the repo root for collaborators:
```json
{
"mcpServers": {
"epl": {
"command": "uv",
"args": ["run", "--directory", "/path/to/jk-mcp-epl", "python", "-m", "epl.server"]
}
}
}
```
---
## Claude Desktop
Add the following to your Claude Desktop configuration file.
**Location:**
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"epl": {
"command": "uv",
"args": [
"run",
"--directory", "/path/to/jk-mcp-epl",
"python", "-m", "epl.server"
]
}
}
}
```
If `uv` is not on Claude Desktop's `PATH`, use the absolute path (`which uv` will show it). Fully quit and relaunch Claude Desktop after saving — a window close is not enough.
---
## Docker
Build the image:
```bash
docker build -t jk-mcp-epl:latest .
```
Run in stdio mode (for MCP clients that spawn a subprocess):
```bash
docker run -i --rm jk-mcp-epl:latest
```
Run in HTTP mode:
```bash
docker run --rm -p 8000:8000 \
-e MCP_TRANSPORT=streamable-http \
jk-mcp-epl:latest
```
---
## Development
### Install
```bash
uv sync
```
### Invoke tasks
All common workflows are `invoke` tasks. Run `uv run inv --list` to see everything.
| Task | Alias | Description |
|---|---|---|
| `uv run inv lint` | `inv l` | Run ruff linter and format check |
| `uv run inv lint --fix` | `inv l --fix` | Auto-fix lint violations and reformat |
| `uv run inv test` | `inv t` | Run the full test suite |
| `uv run inv coverage` | `inv v` | Run tests with coverage report (threshold: 90%) |
| `uv run inv check-complexity` | `inv cc` | Check cyclomatic complexity (max 7) |
| `uv run inv build` | `inv b` | Build wheel and sdist into `dist/` |
| `uv run inv build-image` | `inv bi` | Build the Docker image |
| `uv run inv clean` | `inv c` | Remove build and coverage artifacts |
### Project structure
```
src/epl/
├── server.py # entry point, transport selection, logging setup
├── adapters/
│ ├── inbound/
│ │ ├── mcp_adapter.py # FastMCP server, health endpoints, tool registration
│ │ ├── formatters.py # domain → LLM-readable text
│ │ ├── authorization.py # inbound authz port implementations
│ │ └── tools/
│ │ ├── espn.py # 8 ESPN-backed tools
│ │ └── analytics.py # 3 schedule-strength analytics tools
│ └── outbound/
│ ├── espn_adapter.py # ESPN HTTP client
│ ├── parsers.py # ESPN JSON → domain models
│ ├── retry_adapter.py # transient-failure retry decorator
│ └── caching_adapter.py # in-process TTL cache
├── application/
│ ├── service.py # EPLService — use cases, orchestration
│ ├── _helpers.py # input validation
│ └── _analytics_helpers.py # pure math for schedule-strength tools
├── domain/
│ ├── models.py # Team, Match, Standing, etc.
│ └── exceptions.py # EPLNotFoundError, UpstreamAPIError
├── ports/
│ ├── inbound.py # Authorizer protocol
│ └── outbound.py # EPLAPIPort protocol
├── observability/ # OpenTelemetry bootstrap (opt-in)
└── security/ # JWKS token verifier
```
The dependency direction flows inward: adapters → ports → domain. Nothing in `domain/` imports from adapters or a framework.
---
## Contributing
1. Fork the repository and clone your fork
2. Create a feature branch: `git checkout -b feature/your-feature`
3. Make your changes following the existing patterns (hexagonal architecture, TDD, conventional commits)
4. Verify the full check suite passes: `uv run inv lint && uv run inv check-complexity && uv run inv coverage`
5. Open a pull request against `main`
All CI checks (lint, complexity, tests, coverage ≥ 90%) must pass before merge.
---
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 12 tools
Most tools have clearly distinct purposes, but get_roster and get_players both return player information and get_scoreboard vs get_team_schedule could be confused for team-specific date queries. Descriptions adequately differentiate them, but a few boundaries are close.
All tools follow a consistent get_<noun> pattern in snake_case. Multi-word nouns are used consistently, making the API predictable and easy to navigate.
12 tools is well-scoped for a Premier League data server, covering team, match, player, news, standings, and advanced analytics without redundancy. Each tool earns its place.
The tool set covers core domains well: teams, matches, rosters, players, news, standings, and analytics. Minor gaps exist like individual player stats or detailed match events beyond key events, but these are workable.