Skip to main content
Glama
jedi-knights

jk-mcp-mls

by jedi-knights
README.md
# jk-mcp-mls

MCP server that gives Claude live access to Major League Soccer data — teams, matches, standings, rosters, and schedule-strength analytics — via the ESPN public API.

[![CI](https://github.com/jedi-knights/jk-mcp-mls/actions/workflows/ci.yml/badge.svg)](https://github.com/jedi-knights/jk-mcp-mls/actions/workflows/ci.yml)
[![Badge](https://github.com/jedi-knights/jk-mcp-mls/actions/workflows/badge.yml/badge.svg)](https://github.com/jedi-knights/jk-mcp-mls/actions/workflows/badge.yml)
[![Coverage](https://img.shields.io/badge/Coverage-93.5%25-brightgreen)](https://jedi-knights.github.io/jk-mcp-mls/)
[![Evals](https://github.com/jedi-knights/jk-mcp-mls/actions/workflows/evals.yml/badge.svg)](https://github.com/jedi-knights/jk-mcp-mls/actions/workflows/evals.yml)
[![Release](https://github.com/jedi-knights/jk-mcp-mls/actions/workflows/release.yml/badge.svg)](https://github.com/jedi-knights/jk-mcp-mls/actions/workflows/release.yml)
[![Python](https://img.shields.io/badge/python-3.13-blue)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](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 MLS standings, last night's scores, or which teams are currently in a playoff position. This project fixes that.

It is an **MCP server** — a plugin that gives Claude direct access to live MLS data: scores, standings, rosters, and derived schedule-strength analytics. Once installed, you can ask Claude natural-language questions about Major League Soccer and get accurate, up-to-date answers. No subscription, no API key, and no programming required to use it.

This is the **v1 scaffold** — it wraps the ESPN public API only. Richer sources (mlssoccer.com's Opta-powered feed, official CMS award articles, Leagues Cup, U.S. Open Cup, Concacaf Champions Cup) are on the roadmap.

---

## Features

The v1 surface is eleven read-only, idempotent tools split across two tiers.

### ESPN-backed (8)

| Tool | Description |
|---|---|
| `get_teams` | List all 30 MLS 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 **grouped by Eastern and Western Conferences** |
| `get_news` | Recent MLS news articles |

### 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

Not in v1; probed and shown to be viable at the ESPN API:

- Leagues Cup (`concacaf.leagues.cup`), U.S. Open Cup (`usa.open`), Concacaf Champions Cup, Campeones Cup
- Player leaderboards and team season aggregates once a stable MLS Opta feed is identified
- Award articles via `mlssoccer.com` CMS
- Playoff bracket for the MLS Cup Playoffs

---

## 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-mls.git
cd jk-mcp-mls
uv sync
```

---

## Usage

Run the server in stdio mode (the default — used by Claude Code and Claude Desktop):

```bash
uv run python -m mls.server
```

Run in HTTP mode (for networked or deployed access):

```bash
MCP_TRANSPORT=streamable-http uv run python -m mls.server
```

### Example prompts

**Standings, scores, rosters:**
- Who is leading the MLS Eastern Conference right now?
- Show me every MLS result from this past weekend.
- Who is on Atlanta United's roster?
- When does LAFC play next?

**Schedule strength:**
- Which MLS team has played the toughest schedule so far?
- Show me Atlanta United's record against the current top 5 teams.
- Compare Inter Miami and Seattle Sounders 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/mls` | 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 mls -- uv run --directory /path/to/jk-mcp-mls python -m mls.server
```

Replace `/path/to/jk-mcp-mls` 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": {
    "mls": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/jk-mcp-mls", "python", "-m", "mls.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": {
    "mls": {
      "command": "uv",
      "args": [
        "run",
        "--directory", "/path/to/jk-mcp-mls",
        "python", "-m", "mls.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-mls:latest .
```

Run in stdio mode (for MCP clients that spawn a subprocess):

```bash
docker run -i --rm jk-mcp-mls:latest
```

Run in HTTP mode:

```bash
docker run --rm -p 8000:8000 \
  -e MCP_TRANSPORT=streamable-http \
  jk-mcp-mls: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/mls/
├── 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                # MLSService — use cases, orchestration
│   ├── _helpers.py               # input validation
│   └── _analytics_helpers.py     # pure math for schedule-strength tools
├── domain/
│   ├── models.py                 # Team, Match, Standing (with conference), etc.
│   └── exceptions.py             # MLSNotFoundError, UpstreamAPIError
├── ports/
│   ├── inbound.py                # Authorizer protocol
│   └── outbound.py               # MLSAPIPort 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

A4.6/5.0

Scored across 11 tools

Disambiguation4/5

Most tools are clearly distinct (teams, roster, scoreboard, standings, news), but the three team-performance analytics tools (strength_of_schedule, results_by_opponent_tier, adjusted_points_per_game) share a similar shape and could be confused if an agent doesn't read the descriptions carefully.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern: 'get_' followed by a specific resource (teams, team, scoreboard, roster, etc.). There is no mixing of conventions or vague verbs.

Tool Count5/5

11 tools is well within the ideal 3-15 range for a domain-specific server. Each tool covers a distinct need, and the count feels appropriately scoped without being excessive or too thin.

Completeness5/5

The server provides comprehensive read-only coverage for MLS data: team info, rosters, matches (by date and by team), standings, news, and advanced performance metrics. There are no obvious dead ends or missing core operations for a read-only informational server.

Maintenance

ActivityNo data
ResponsivenessNo issues