{
  "markdown": "# sportsdata-mcp\n\n[![CI](https://github.com/DanielTomaro13/sportsdata-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/DanielTomaro13/sportsdata-mcp/actions/workflows/ci.yml)\n[![Release](https://img.shields.io/github/v/release/DanielTomaro13/sportsdata-mcp)](https://github.com/DanielTomaro13/sportsdata-mcp/releases/latest)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)\n[![sportsdata-mcp MCP server](https://glama.ai/mcp/servers/DanielTomaro13/sportsdata-mcp/badges/score.svg)](https://glama.ai/mcp/servers/DanielTomaro13/sportsdata-mcp)\n[![Support on Ko-fi](https://img.shields.io/badge/Ko--fi-support%20this%20project-FF5E5B?logo=kofi&logoColor=white)](https://ko-fi.com/danieltomaro)\n\n[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=sportsdata&config=eyJjb21tYW5kIjogInV2eCIsICJhcmdzIjogWyJzcG9ydHNkYXRhLW1jcCIsICJzZXJ2ZSJdfQ==)\n\n**Ask your AI which bookmaker is paying more — and get a real answer.**\n\nFree & open source (MIT). ~841 tools across 64 providers in Claude Desktop,\nCursor, or any MCP client. `uvx sportsdata-mcp serve` and you're done.\n\n> **You:** Which book has the best price on Parramatta v Penrith, and how big is the spread?\n\nClaude queries five books at once and comes back with:\n\n| Book | Eels | Panthers |\n|-----------|------:|---------:|\n| Betfair | 1.18 | **6.20** |\n| BetR | 1.17 | 5.00 |\n| PointsBet | 1.17 | 4.80 |\n| Pinnacle | 1.17 | 4.78 |\n| Sportsbet | **1.19** | 4.75 |\n\n*Real captured odds. The same bet on Penrith pays **$6.20** at Betfair and\n**$4.75** at Sportsbet — a **30% spread** on identical risk. That gap is\ninvisible unless something is reading every book at once.*\n\n**What this one is for: comparing prices, not just fetching scores.** Plenty of\nsports MCP servers will get you fixtures and standings. This one is built around\n*disagreement between books* — eleven bookmakers, the Betfair exchange, and two\nprediction markets (Kalshi, Polymarket) side by side on the same market, plus\ntwenty-seven official league/stats feeds. Deep on AU/NZ books (Sportsbet, TAB,\nLadbrokes, PointsBet, BetR, Dabble) and on racing — thoroughbred, greyhound and\nharness with tote pools and exchange money — which most catalogues skip\nentirely. Capability tags make providers interchangeable, so \"compare odds\nacross books\" is one question rather than forty-three integrations.\n\n### Is this for you?\n\nWorth being straight about, because it decides whether the first thing you ask\nworks or looks broken.\n\n**Best fit — you follow or bet into Australian markets.** The cross-book edge\nabove is the reason this exists, and it is built on **159 tools across eight\nAustralian books**: Sportsbet, TAB, PointsBet, BetR, Ladbrokes/Neds, Betfair,\nDabble, Unibet. Nothing else exposes that, and racing — thoroughbred, greyhound\nand harness, with tote pools and exchange money — is covered to the same depth.\n\n**Also good — you want sport data anywhere in the world.** The other **682\ntools across 56 providers** are not region-locked: MLB, NBA, NFL, NHL, the\nPremier League, cricket, golf, tennis, F1, UFC, fantasy (ESPN, Sleeper, FPL),\nplus Pinnacle and the Kalshi and Polymarket prediction markets. 486 of those\nneed no key at all.\n\n**Not a fit — you want US sportsbook odds.** Those eight books are licensed for\nAustralia and block traffic from outside it. From the US you get Pinnacle,\nFanDuel, Kalshi and Polymarket for prices; the stats and fantasy side works in\nfull. If cross-book US pricing is what you came for, this is not the tool.\n\n### It can now place bets. Read this bit.\n\nSince **0.31.0** the catalogue includes `sportsbet_place_bet`, `tab_place_bet`,\n`entain_place_bet` and `unibet_place_bet`. These stake real money from your own\naccount, using credentials you supply. Nothing calls them on its own — they are\nordinary tools, so whatever you connect this server to decides when they run.\n\nTwo things follow, and neither is optional reading:\n\n- **An LLM with these tools in scope can place a bet.** If that is not what you\n  want, do not enable the account groups; every other group is unaffected, and\n  `list-groups` shows exactly what you have turned on.\n- **Only Sportsbet and TAB have been round-tripped against a real account.**\n  Unibet and Ladbrokes/Neds have their request shape captured from placements\n  made in a browser, which proves the request and says nothing about whether a\n  stored credential alone is accepted.\n\nThe agent workbench applies its own policy on top of this — everything starts in\n`paper` mode and stakes nothing until you opt a book in — but that is the *app's*\nguardrail, not this server's. On its own, this package does what it is asked.\n\nRather than take that on trust, ask it:\n\n```bash\nsportsdata-mcp coverage\n```\n\nIt probes every provider from **your** machine and prints what answered, what\nyour location blocks, and what needs a key — so an empty result is never\nambiguous between \"no data\" and \"wrong country\".\n\n### Built on this server\n\nTwo live terminals, both open source, both running on nothing but these tools:\n\n[**sportsdata-ai.com/sports**](https://sportsdata-ai.com/sports) — prediction\nmarkets + exchange as a de-vigged sharp line, every book measured against it.\n\n![Sports board](docs/assets/sportsboard.png)\n\n[**sportsdata-ai.com/board**](https://sportsdata-ai.com/board) — racing money\nflow: which runners are firming, fair price vs the field, win%/ROI scorecard.\n\n![Racing board](docs/assets/racingboard.png)\n\n### Try these once it's installed\n\n- *\"Compare head-to-head odds across every book for tonight's NRL games.\"*\n- *\"Which AFL games have the biggest price disagreement between bookmakers?\"*\n- *\"What does Betfair imply for the Panthers vs what Sportsbet is offering?\"*\n- *\"Show me Pinnacle's line on every MLB game today.\"*\n- *\"Pull the ladder and last five results for Hawthorn.\"*\n\n---\n\nAn [MCP](https://modelcontextprotocol.io) server that exposes sports-data APIs\n(bookmakers, league/governing-body feeds, aggregators) as tools, configurable so\nyou only load the tool groups you need. A capability-tag system makes tools from\ndifferent providers interchangeable wherever they answer the same question — so\nthe model can compare odds across bookies or stats across data sources with one\ndiscovery call.\n\nThe catalogue spans bookmakers, league/governing-body feeds, and stats\naggregators, and it keeps growing. New providers are added by dropping a YAML\nspec into `src/sportsdata_mcp/specs/` — the engine needs no code changes — so\nthe exact provider and tool counts move over time. Run `sportsdata-mcp\nlist-groups` for the live inventory, and three meta-tools (group discovery,\ncapability lookup, resource listing) are always on regardless of what you\nenable.\n\n## Install\n\n**One-liner** (any MCP client config, via [uv](https://docs.astral.sh/uv/)):\n\n```bash\nuvx sportsdata-mcp serve        # or: pip install sportsdata-mcp\n```\n\n**Prebuilt app** (no Python needed): grab the latest\n[release](https://github.com/DanielTomaro13/sportsdata-mcp/releases/latest)\n(macOS + Windows), unzip, and run `sportsdata-mcp setup` — it writes the config\nfor Claude Desktop / Cursor for you. The macOS build is unsigned for now:\nright-click → Open the first time.\n\nFrom source:\n\n```bash\ngit clone https://github.com/DanielTomaro13/sportsdata-mcp.git\ncd sportsdata-mcp\npip install -e .                # add \".[dev]\" for the test + lint toolchain\n```\n\n\n\n## Quickstart\n\n```bash\nsportsdata-mcp version          # print version info\nsportsdata-mcp list-groups      # see every available tool group\nsportsdata-mcp lint             # validate the packaged specs\nsportsdata-mcp doctor           # probe enabled groups for reachability + auth\nsportsdata-mcp serve            # start the MCP stdio server (default command)\nsportsdata-mcp update-specs     # OTA-refresh provider specs (signed bundle); --clear reverts\n```\n\nProvider endpoints drift (e.g. Entain rotates its GraphQL persisted-query hashes).\n`update-specs` fetches a **signed** spec bundle and applies it into an overlay under\n`~/.sportsdata/spec-overlay`, which the loader prefers over the packaged copy — so a drift\nfix doesn't need a whole new app build. The bundle is Ed25519-verified against a baked key\n(a product build refuses an unsigned/forged bundle; anti-rollback refuses a stale replay).\nPublish one with `scripts/publish-spec-bundle.py`; point `--url` / `$SPORTSDATA_SPEC_FEED_URL`\nat the asset. Restart the server after applying.\n\nEnable tool groups with a config file or the `SPORTSDATA_MCP_GROUPS` env var:\n\n```bash\nSPORTSDATA_MCP_GROUPS=\"afl.public.core,sportsbet.racing,entain.graphql\" sportsdata-mcp serve\n```\n\nSee [`examples/`](./examples) for Claude Desktop / Claude Code config snippets,\na worked cross-bookie [odds-comparison prompt](./examples/comparator-prompt.md),\nand an [NBA shot-chart + box-score walkthrough](./examples/nba-prompt.md) that\nshows the `nba_stats_call` dispatcher pattern end to end.\n\n## Configuration\n\nConfig is resolved in this order (first hit wins):\n\n1. `--config <path>` flag\n2. `$SPORTSDATA_MCP_CONFIG`\n3. `./sportsdata-mcp.yaml`\n4. `~/.config/sportsdata-mcp/config.yaml`\n5. built-in defaults\n\n```yaml\n# sportsdata-mcp.yaml\nenabled_groups:\n  - afl.public.core\n  - sportsbet.racing\n  - entain.graphql\n\nproviders:                      # all optional; sensible defaults apply\n  sportsbet:\n    request_timeout_seconds: 30\n    rate_limit_rps: 10          # sustained requests/sec (token bucket)\n    max_response_bytes: 0       # 0 = no cap (default); set a positive byte count to guard context\n\nsecrets: {}                     # for authenticated providers; prefer env vars in prod\n```\n\nA provider whose auth reads `env: SOME_VAR` is satisfied by the real environment\nvariable first, then by a `secrets: { SOME_VAR: \"...\" }` entry of the same name\n(a local-dev convenience — keep real secrets in the environment in production).\n\n### Environment variables\n\n| Variable | Effect |\n| --- | --- |\n| `SPORTSDATA_MCP_GROUPS` | Group selector; overrides `enabled_groups`. Accepts presets (`free`, `au-books`, `racing`, `arb`, `fantasy`, `chess`, `official-stats`, `aus`, `odds`, `motorsport`, `all`), a provider id (`espn`) or glob (`espn.*`), literal groups, and exclusions (`*,-twitter`). Run `list-groups` to see every preset with its tool count. |\n| `SPORTSDATA_MCP_CONFIG` | Path to a config file (see resolution order above). |\n| `SPORTSDATA_MCP_MAX_BYTES` | Global response-size cap in bytes for every provider that doesn't set its own `max_response_bytes`. `0` (the default) means no cap. |\n| `SPORTSDATA_MCP_CACHE_TTL` | Seconds to cache identical GET responses (default `60`, `0` disables). Absorbs the duplicate calls a model makes while reasoning, without staling live prices. Per provider: `providers.<id>.cache_ttl_seconds`. |\n| `SPORTSDATA_MCP_HTTP` / `SPORTSDATA_MCP_HOST` / `SPORTSDATA_MCP_PORT` | Serve over HTTP instead of stdio (same as `serve --http --host --port`). Binds `127.0.0.1` by default — **the endpoint is unauthenticated**, so anything wider exposes every enabled tool and any provider credential in the process to that network. |\n| `SPORTSDATA_LICENSE` | **Dormant** — the product is free; nothing requires a licence. The signed-entitlement machinery remains for anyone self-hosting gated premium feeds (see below). |\n| `SPORTSDATA_ENTITLEMENT_URL` / `SPORTSDATA_ENTITLEMENT_PUBKEY` | Only relevant with the dormant entitlement gate above. Normally unset. |\n\n### The (dormant) entitlement gate\n\nThis project used to be a paid product. It's free now — **no licence exists or is\nneeded, and every group serves by default** — but the signed-entitlement machinery\n(Ed25519-verified feed grants, offline caching, 15-min revalidation) is kept dormant\nrather than deleted: it's tested, harmless when unset, and useful to anyone\nself-hosting this server who wants to gate premium feeds for their own users. Set\n`SPORTSDATA_LICENSE` + `SPORTSDATA_ENTITLEMENT_URL` against your own issuing service\nto activate it; leave them unset (the default) and nothing changes.\n\n**Keyed feeds.** A few providers need an upstream credential you supply yourself —\ne.g. `DATAGOLF_KEY` for DataGolf, `X_BEARER_TOKEN` for Twitter/X. Everything else\nneeds no key at all.\n\nMeta-tools (`list_available_groups`, `list_tools_by_capability`, `list_resources`)\nare always registered regardless of what is enabled, so a fresh install can still\nguide the model to turn groups on.\n\n**On the response-size cap.** There is **no cap by default** — every tool returns\nwhatever the upstream API sends. If you want to guard the model's context window you\ncan opt in to a cap: precedence is `providers.<id>.max_response_bytes` >\n`SPORTSDATA_MCP_MAX_BYTES` > the default (`0`, unlimited). Be aware that very large\npayloads (e.g. Sportsbet's full `*_event_markets` firehose, ~2 MB) won't fit in\nClaude's ~200 K-token context regardless — for those, prefer a narrower tool such as\n`sportsbet_sports_card` with `includeTopMarkets: true`.\n\n## Tool groups\n\nRun `sportsdata-mcp list-groups` for live counts and descriptions.\n\n### AFL — `api.afl.com.au`\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `afl.public.core` | 22 | Competitions, seasons, rounds, fixtures, ladders, match stats |\n| `afl.public.broadcasting` | 9 | Broadcast regions, guides, providers |\n| `afl.public.content` | 8 | News/articles, videos, photos |\n| `afl.premium.cfs` | 1 | CFS premium ops — needs the anonymous `x-media-mis-token` |\n| `afl.premium.statspro` | 1 | StatsPro ops — needs the `x-media-mis-token` |\n| `afl.premium.keyserver` | 1 | HLS video URL signing |\n\n### Sportsbet — `sportsbet.com.au`\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `sportsbet.racing` | 15 | Race meetings, racecards, results, futures, SRMs |\n| `sportsbet.sports` | 14 | Sport events, markets, prices, SGMs |\n| `sportsbet.cross` | 12 | Live status, commentary, ladders, promos, video |\n| `sportsbet.results` | 2 | Resulted events by date |\n| `sportsbet.graphql` | 1 | Persisted GraphQL gateway (`apigw/sportsbook/graph`) |\n\n### Entain / Ladbrokes — `ladbrokes.com.au`\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `entain.rest` | 13 | Navigation quick-links and REST surfaces |\n| `entain.graphql` | 1 | 127 persisted GraphQL ops (`gql/router`) |\n| `entain.cdn` | 1 | Contentful CMS entries (promotions, major-event nav) |\n\n### PointsBet — `pointsbet.com.au`\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `pointsbet.sports` | 10 | Sports catalogue, competition/event feeds, full event markets, in-play, search |\n| `pointsbet.racing` | 11 | Meetings, racecards, results, futures, SRMs, tips, form |\n| `pointsbet.content` | 3 | Promotions, promo-code splash, + `pointsbet_content_call` over the static CMS/nav assets |\n\n### TAB — `tab.com.au`\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `tab.racing` | 9 | Dates, meetings, racecards (fixed + parimutuel), form, next-to-go, jackpots, futures |\n| `tab.sports` | 9 | Sports/competitions tree, full match markets + SGM, focused match markets, next-to-go, results, multi-builder |\n| `tab.discovery` | 4 | Featured/live recommendations + `tab_cms_call` over the CMS content feeds |\n\n### Unibet — `unibet.com.au`\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `unibet.racing` | 1 | `unibet_racing_call` — persisted-GraphQL: meetings, race cards, form, futures, specials |\n| `unibet.sport` | 3 | `unibet_kambi_call` over the Kambi offering API (groups, events, bet offers, in-play, bet-builder) + live stats + odds ladder |\n\n### BetR — `betr.com.au` (BlueBet platform)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `betr.racing` | 8 | Next-to-jump, today's/grouped racecards, race card, form, fluctuations, movers |\n| `betr.sport` | 7 | Event types, competition categories, event markets, match detail, popular SGMs |\n| `betr.content` | 4 | Promotions + featured racing + popular market links |\n\n### Pinnacle — `pinnacle.com` (sharp odds)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `pinnacle.sports` | 13 | Sports/leagues, full + highlighted + live + per-league matchups, carousel, matchup detail, straight + parlay markets (American-odds prices) |\n| `pinnacle.reference` | 4 | Enums, market-label dictionary, teaser definitions, API status |\n\n### Betfair Exchange — `betfair.com.au` (exchange odds)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `betfair.exchange` | 3 | `bymarket` + `byevent` back/lay price feeds (the sharpest odds) + cash-out availability |\n| `betfair.navigation` | 1 | `bynode` catalogue graph (sport → meeting → event → market) |\n| `betfair.inplay` | 5 | Live scores, event details, timeline (single + batch), scores+broadcast |\n\n### Dabble — `dabble.com.au` (iOS app backend)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `dabble.sport` | 5 | Discover any competition (active list / name lookup / sports), then its fixtures (embedded markets + decimal odds) + the full per-fixture book (400+ markets + Pick'em props) |\n\nThe Australian social-betting app's backend, read directly. Reached by **posing\nas the iOS app** — the spec bakes the app's `User-Agent` + `x-device-id` +\n`x-app-version` so the public feeds return JSON anonymously. **AU-only** and\nCloudflare-fronted (403s from non-AU IPs, like the other AU books). Works for\n**any** competition — `dabble_active_competitions` lists the ~269 currently-bettable\nones across all sports. Read-only odds — no bet placement.\nComposes with the other books via `sport.event_markets` / `sport.prices`.\n\n### SuperCoach — `supercoach.com.au` (News Corp / Champion Data fantasy)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `supercoach.fantasy` | 6 | One uniform surface across **all 7 games** (afl/nrl/epl/nba/nbl/nfl/bbl) × **2 modes** (`classic` + `draft`): competition state, the full per-player feed (price + `ppts1` projection + ownership + matchup; draft adds `predraft_rank`), fixtures (with H2H odds), club + single-player catalogues, leagues |\n\nNews Corp / Champion Data's salary-cap fantasy game. Every feed lives under\n`/{year}/api/{sport}/classic/v1/…` — pass `sport` (one of the seven) and `year`\n(the season key: current calendar year for afl/nrl, currently `2025` for the\nothers, which run across the new year). **No auth, not geo-blocked** (runs in CI).\nThe core `supercoach_players` feed is per-round and large (~1–3 MB); use `ppts1`\n(the real projection), not `ppts`. Adds the **fantasy / projections** angle via\n`stats.fantasy_projections` alongside Data Golf. See\n[documentation/SuperCoach.md](documentation/SuperCoach.md).\n\n### NBL — `nbl.com.au` (Australian National Basketball League)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `nbl.basketball` | 14 | Seasons, teams, ladder, schedule (scores), players + rosters, per-player season stats + game-log box scores, team stats, season stat leaders (sortable), and news |\n\nThe league's own site data API — a Redis-cached proxy (**\"rosetta\"**) over Genius\nSports stats at `prod.rosetta.nbl.com.au/get/…`. **No token**, but **referer-gated**\n(403s without an `nbl.com.au` Origin + Referer — both baked into the spec). Every\nresponse is enveloped `{type, count, source, data:[…]}`. Season-scoped by `year`\n(the season start year: 2025 = NBL26, current); stat-leaders takes the season UUID\nfrom `nbl_seasons`. Distinct from the SuperCoach `nbl` fantasy feed — this is the\nofficial box-score source. See [documentation/NBL.md](documentation/NBL.md).\n\n### WTA — `wtatennis.com` (Women's Tennis Association, official)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `wta.tennis` | 8 | Official WTA API: singles/doubles rankings, player catalogue + profiles + match history, tournament calendar + per-edition results + entry lists (seeds) |\n\nThe WTA's **official** data API (`api.wtatennis.com/tennis/…`) — public Spring REST,\n**no auth/key, no geo-block**, runs in CI. Rankings need `type`+`metric`\n(rankSingles+singles or rankDoubles+doubles); tournaments are keyed by\n`tournamentGroup.id` + `year` (Australian Open = group 901). Fills the tennis gap on\nthe stats side, composing with the bookmakers' live tennis markets. See\n[documentation/WTA.md](documentation/WTA.md). (ATP has no equivalent open API —\natptour.com is Cloudflare bot-protected — so it isn't modelled.)\n\n### Racing and Sports — `racingandsports.com.au`\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `racingandsports.racing` | 3 | Today's race meetings (all codes, verified) + sports match list + per-race odds (token) |\n\n### Data Golf — `datagolf.com` (needs a key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `datagolf.general` | 3 | Player list, tour schedule, current event field |\n| `datagolf.predictions` | 11 | DG rankings, pre-tournament (+ archive) + in-play model probabilities, skill + approach-skill ratings, player/live SG decompositions, live strokes-gained, live hole stats, DFS projections |\n| `datagolf.betting` | 3 | Outright + matchup + all-pairings odds across ~13 books (incl. model line) |\n| `datagolf.historical` | 9 | Archived raw round data, event-level results (finishes/earnings/points), historical bookmaker odds (outrights + matchups) and DFS results |\n\nNeeds a Data Golf API key in the `DATAGOLF_KEY` env var (a personal subscription\nkey — sourced via the `static_query` auth scheme, never stored in the repo).\n\n### FanDuel — `fanduel.com` (US)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `fanduel.racing` | 4 | `fanduel_racing_call` (full-query GraphQL: featured/today races + odds, single-race card, tracks, pools, talent picks) + messages/quick-links/promotions |\n| `fanduel.sportsbook` | 2 | `fanduel_sb_call` (REST: event pages + markets, in-play, promos, configs via the `_ak` key) + live scores |\n\n### NRL — `mc.championdata.com`\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `nrl.public.core` | 4 | Champion Data match centre: competitions, fixture, per-match player stats, app settings |\n\nPlus the `nrl://stats/definitions` resource (dictionary of every NRL stat code).\n\n### NBA — `cdn.nba.com` + `stats.nba.com`\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `nba.public.cdn` | 5 | Open CDN JSON: today's scoreboard, full schedule, live box score + play-by-play, odds |\n| `nba.stats` | 2 | `nba_daily_lineups` + `nba_stats_call`, the dispatcher over the 138-endpoint `/stats/` API |\n\n`nba_stats_call` fronts the whole stats.nba.com `/stats/` analytics surface (player/team\ndashboards, box scores v2+v3, shot charts, play-by-play, leaders, standings, draft, hustle,\ntracking, …). Browse every operation, its required params and its defaults in the\n`nba://stats/operations` resource.\n\n### ESPN — `espn.com` JSON feeds\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `espn.scores` | 5 | Site API convenience endpoints: scoreboard, teams, standings, game summary, news |\n| `espn.site` | 1 | `espn_site_call` — team detail, rosters, schedules, injuries, depth charts, transactions, history, athlete news, groups, rankings (10 ops) |\n| `espn.core` | 1 | `espn_core_call` — the canonical `$ref`-linked model: events/competitions, odds, win-probability, plays, venues, drafts, coaches, calendar, transactions (37 ops) |\n| `espn.web` | 1 | `espn_web_call` — site-wide search + `common/v3` athlete views (7 ops) |\n| `espn.cdn` | 1 | `espn_cdn_call` — the CDN live core feed: scoreboard/game/boxscore/playbyplay (4 ops) |\n\nAll ESPN tools are parametric over `sport` + `league` slugs (e.g. `football`/`nfl`,\n`basketball`/`nba`, `soccer`/`eng.1`), so the five groups cover **every** league ESPN\ncarries. Browse each dispatcher's operations in its `espn://{site,core,web,cdn}/operations`\nresource.\n\n### OpenDota — `api.opendota.com` (Dota 2 esports, no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `opendota.reference` | 4 | Heroes, hero meta by skill bracket, pro teams, leagues |\n| `opendota.matches` | 3 | Pro matches, full match detail, public ladder matches |\n| `opendota.players` | 4 | Profile + rank, match log, win/loss, hero pool |\n\nThe catalogue's first **esports** provider. Sides are Radiant/Dire rather than\nhome/away, and hero stats are paired pick/win counts per skill bracket rather than\nrates — both documented, both test-pinned. The 4.4 MB pro-player list is deliberately\nnot exposed. See [documentation/OpenDota.md](documentation/OpenDota.md).\n\n### OpenLigaDB — `api.openligadb.de` (German football, no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `openligadb.football` | 8 | Bundesliga 1/2/3 + DFB-Pokal: fixtures, results, tables, matchdays |\n\nFills the big-five hole — you had the Premier League, La Liga and Serie A but no\n**Bundesliga**. Crowd-maintained, so the long tail can lag. Note scores live in\n`matchResults`, which holds *both* half-time and full-time entries. See\n[documentation/OpenLigaDB.md](documentation/OpenLigaDB.md).\n\n### EuroLeague — `api-live.euroleague.net` (basketball, no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `euroleague.basketball` | 7 | EuroLeague + EuroCup: seasons, clubs, rounds, games, box scores |\n\nCompletes basketball alongside the NBA and NBL. One letter selects the competition\n(`E`/`U`), season codes are `E2024`, home/away are `local`/`road`, and box scores carry\n**PIR** rather than an NBA-style efficiency number. See\n[documentation/EuroLeague.md](documentation/EuroLeague.md).\n\n### NCAA — `ncaa-api.henrygd.me` (US college sports, no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `ncaa.college` | 3 | Scoreboards, conference standings, AP/coaches polls across every college sport |\n\nESPN already gives you college scores; this adds the NCAA's own **polls and conference\nstandings** in a normalised shape. A third-party mirror of NCAA.com rather than an\nofficial feed. See [documentation/NCAA.md](documentation/NCAA.md).\n\n### Fantasy Premier League — `fantasy.premierleague.com` (official, no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `fpl.players` | 2 | Every player with price, form, ownership, xG/xA; one player in full depth |\n| `fpl.reference` | 3 | Clubs with strength ratings, all 38 gameweeks with **deadlines**, scoring rules |\n| `fpl.fixtures` | 5 | Fixtures with difficulty 1-5, live per-player scoring, dream team, set-piece takers |\n| `fpl.managers` | 6 | Any manager's squad, history and picks; classic and H2H leagues; **your own team** |\n\nThe world's most-played fantasy game — 4,085,510 registered squads — and public except\nfor your own squad.\n\nOne quirk shapes the whole provider: `bootstrap-static` is a **single 1.37 MB blob** whose\nplayer rows alone are **~362,000 tokens**, with no server-side field selection. Four tools\nhit that one URL and each return one slice, so `fpl_players` lands at ~58k tokens instead\nof being unusable. Nothing is invented or renamed — only removed.\n\nWatch the units: `now_cost` is **tenths of a million** (145 = £14.5m), `form` and the\nexpected-goals family are **strings**, and `team` is FPL's own 1-20 id rather than the\nPremier League's. See [documentation/FPL.md](documentation/FPL.md).\n\n### UFC — `ufc.com` JSON:API (official, no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `ufc.events` | 3 | Events with per-segment card times and venue, full fight cards, bouts back to UFC 1 |\n| `ufc.athletes` | 2 | Fighter search and profiles with statistics attached |\n| `ufc.stats` | 2 | The **FightMetric career statistics table** and divisional rankings |\n| `ufc.records` | 2 | Single-round record book, plus the JSON:API resource index |\n\nThe obvious source, **ufcstats.com, is a dead end** — it serves a JavaScript\nproof-of-work bot challenge with `noindex` and zero data rows in the HTML, so reading it\nwould mean building bot-detection evasion. ufc.com turns out to be better anyway: it runs\nDrupal with JSON:API exposed, and `athlete_stat` carries **the same FightMetric dataset**,\ndown to the same `fightmetric_id` identifiers.\n\n48 statistics per fighter — significant strikes split by position (standing/clinch/ground)\n*and* target (head/body/leg), takedowns landed/attempted/accuracy/defence, submission and\nknockdown averages, strikes landed and absorbed per minute, career records by finish\nmethod.\n\nTwo traps worth knowing: **related records never inline** (without `include=athlete_stat`\na fighter has no statistics at all), and **filters silently return 0 rows** on the stat\ncollections rather than erroring — so those parameters are not exposed, and sorting is the\nleaderboard mechanism instead. Rate-limited to 0.5 rps because `robots.txt` asks for\n`crawl-delay: 15`. See [documentation/UFC.md](documentation/UFC.md).\n\n### Football-Data.co.uk — historical results **with closing odds** (no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `footballdatauk.history` | 1 | A league season per call: results, shots, cards, and closing prices from ~10 bookmakers, back to the 1990s |\n\nThe only source here you can **backtest** against. Every other football provider tells\nyou what happened; this one tells you what the market thought would happen, match by\nmatch. Pull a completed season, compare closing prices to results, and you have a CLV\nbaseline to measure today's live `sportsbet`/`pinnacle`/`betfair` prices against.\n\nPublished as CSV — the one provider using the engine's `response_format: csv`, so the\nmodel still receives ordinary JSON. See\n[documentation/FootballDataUK.md](documentation/FootballDataUK.md).\n\n### Motorsport beyond F1 — MotoGP, Formula E, NASCAR (no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `motogp.racing` | 6 | MotoGP/Moto2/Moto3/MotoE back to 1949: events, sessions with track conditions, classifications, championships |\n| `formulae.racing` | 5 | Formula E from 2014-15: calendar, driver and team championships with per-race points |\n| `nascar.racing` | 2 | Cup/Xfinity/Truck: full season summaries and weekend feeds with practice, qualifying and race results |\n\nWith `openf1` (live telemetry) and `jolpicaf1` (history), the `motorsport` preset now\ncovers five series. Each has a structural quirk documented in its page — MotoGP needs a\nfour-level uuid walk, Formula E wraps races but not standings, and NASCAR's results\narray is unsorted and **includes non-starters at position 0**, so `results[0]` is not\nthe winner. See [MotoGP.md](documentation/MotoGP.md),\n[FormulaE.md](documentation/FormulaE.md), [NASCAR.md](documentation/NASCAR.md).\n\n### Jolpica F1 — `api.jolpi.ca` (F1 history 1950 →, no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `jolpicaf1.reference` | 4 | Seasons, drivers, constructors, circuits |\n| `jolpicaf1.schedule` | 1 | Race calendars with per-session times |\n| `jolpicaf1.results` | 5 | Race, qualifying and sprint results, lap timings, pit stops |\n| `jolpicaf1.standings` | 2 | Drivers' and constructors' championships |\n\nThe community successor to **Ergast**, deprecated at the end of 2024. Complements\n`openf1` rather than overlapping it: OpenF1 is live telemetry from 2023 onward,\nJolpica is every race since 1950. \"Who won the 1976 Japanese GP\" and \"what lap is\nVerstappen on\" are different providers. Responses use Ergast's double `MRData`\nenvelope and return all values as strings — see\n[documentation/JolpicaF1.md](documentation/JolpicaF1.md).\n\n### Chess — `lichess.org` + `api.chess.com` (no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `lichess.chess` | 6 | Per-time-control ratings, leaderboards, user status, daily puzzle, arenas |\n| `chesscom.chess` | 7 | Profiles, ratings by format, leaderboards, titled players, monthly game archives |\n\nBoth major chess platforms. Note that **ratings are not comparable across them** —\ndifferent pools and formulas — and only Chess.com serves game history as JSON, because\nLichess streams its exports as NDJSON. See [Lichess.md](documentation/Lichess.md) and\n[ChessCom.md](documentation/ChessCom.md).\n\n### Squiggle — `squiggle.com.au` (AFL prediction models)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `squiggle.afl` | 6 | 41 independent AFL forecasting models: what each tipped, its confidence and margin, plus actual and projected ladders |\n\nNot another results feed — the **market of opinions** about AFL games, which is the\nnatural counterpart to a bookmaker's price. Pull a round's tips, pull the same games\nfrom the books, de-vig the prices, and you're comparing a 41-model consensus against\nthe market. `squiggle_ladder` also carries `swarms`, the simulated finishing-position\ndistribution behind each projection. One volunteer's server, so the provider ships an\nhonest contact User-Agent and a deliberately gentle rate limit — see\n[documentation/Squiggle.md](documentation/Squiggle.md).\n\n### NHL — `api-web.nhle.com` (official web API, no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `nhl.reference` | 3 | Seasons, club rosters by position group, player bio/draft/career |\n| `nhl.schedule` | 2 | League schedule by week, and a club's full season game log |\n| `nhl.game` | 3 | Live scoreboard, box scores with per-player ice time, scoring summaries |\n| `nhl.stats` | 3 | Standings with division/conference/wildcard sequencing, skater and goalie leaders |\n\nThe league's own API — the one nhl.com reads (the old `statsapi.web.nhl.com` is dead).\nThe `espn.*` groups already answer \"what's the NHL score\"; this is the depth layer.\nTwo conventions worth knowing: season ids are **concatenated years** (`20242025`), and\n`/now` paths 307-redirect. See [documentation/NHL.md](documentation/NHL.md).\n\n### Sleeper — `api.sleeper.app` (fantasy football, fully public)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `sleeper.reference` | 3 | Season/week state, username → user id, platform-wide trending adds/drops |\n| `sleeper.league` | 8 | Settings, rosters, managers, weekly matchups, transactions, playoff bracket, traded picks |\n| `sleeper.draft` | 3 | League drafts, draft settings, and every pick **with player names** |\n\nThe other major fantasy platform, and the easier of the two to reach: Sleeper's read\nAPI needs **no key and no cookie** — a league id is enough. Joins `espnfantasy` on the\n`fantasy.*` capabilities, so \"show me my league\" works across both. The 15 MB player\ncatalogue is deliberately not exposed; draft picks carry player names instead. See\n[documentation/Sleeper.md](documentation/Sleeper.md).\n\n### ESPN Fantasy — your own fantasy league\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `espnfantasy.reference` | 6 | Games catalogue (resolves the current season + week), season status, pro teams + bye weeks, player universe, scoring presets, player news |\n| `espnfantasy.league` | 14 | Settings, teams, rosters, standings, matchups, draft board, transactions, message board, league history, plus the undocumented `allon` mega-view |\n| `espnfantasy.scoring` | 5 | Box scores (lineups with actual **and** projected points), matchup scores, scoreboard, live scoring, positional ratings |\n| `espnfantasy.players` | 2 | Free-agent / waiver pool with ownership and projections; deep per-player stat splits |\n\nThe **fantasy** platform, not the ESPN scoreboard (that's `espn.*` above) — one URL per\nleague whose payload is chosen by a repeatable `view` param, covering all five games\n(`ffl` football, `flb` baseball, `fba` basketball, `fhl` hockey, `wfba` WNBA) via a\n`game` param on every tool. **Public leagues need no credentials**; set\n`ESPN_FANTASY_COOKIE` to `espn_s2=…; SWID={…}` and the same tools reach your private\nleagues. Fantasy `playerId`s are ESPN athlete ids, so rosters here join straight to the\nreal-world `espn.*` feeds. See [documentation/ESPNFantasy.md](documentation/ESPNFantasy.md)\nfor the view catalogue, the `x-fantasy-filter` cookbook and the id decoder tables.\n\n### OpenF1 — `api.openf1.org` (Formula 1, no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `openf1.reference` | 3 | Grand Prix weekends (meetings), sessions (the fixtures feed), driver roster |\n| `openf1.results` | 5 | Session classification, starting grid, drivers'/constructors' championship standings, overtakes |\n| `openf1.timing` | 5 | Per-lap sector + speed-trap timing, pit stops, tyre stints, live gaps/intervals, track position |\n| `openf1.telemetry` | 2 | Car telemetry (speed/throttle/brake/gear/RPM/DRS) + (x,y,z) location at ~3.7 Hz |\n| `openf1.live` | 3 | Race-control messages (flags/SC/incidents), team-radio clips, weather |\n\nFree, no-auth public REST surface (`auth: none`). Scope feeds by `session_key` /\n`meeting_key` (both accept the literal `latest`) and `driver_number`; discover keys\nwith `openf1_sessions` / `openf1_meetings` first.\n\n### Cricket Australia — `cricket.com.au` (no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `cricketaustralia.core` | 7 | Fixtures (the `/matches` feed), competitions, tours/series, teams, player profiles (batch), venue lookup, competition ladder |\n| `cricketaustralia.match` | 3 | Full scorecard (innings batting/bowling/wickets), run-graph series, live video streams |\n| `cricketaustralia.content` | 2 | Pulselive CMS: video/text/audio/playlist content list + curated playlists |\n\nTwo no-auth hosts (`apiv2.cricket.com.au/web` + the Pulselive CMS). The apiv2\nendpoints carry `jsconfig=eccn:true` by default so they return the documented\ncamelCase shape; flow is `cricketaustralia_fixtures` → `cricketaustralia_scorecard?fixtureId=` →\n`cricketaustralia_players?playerIds=`.\n\n### MLB — `statsapi.mlb.com` (official Stats API, no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `mlb.reference` | 22 | Sports/leagues/divisions/conferences, teams (+ single, affiliates, history, uniforms), rosters, alumni, coaches, personnel, players (profile, batch, search, season catalogue, changes feed), venues, seasons (current + full history) |\n| `mlb.schedule` | 5 | Games by date / range / team, plus postseason (schedule, series, tune-in) and tied games |\n| `mlb.game` | 10 | Boxscore, linescore, play-by-play, v1.1 `feed/live` firehose, win-probability, context metrics, content, per-player game line, changes, uniforms |\n| `mlb.stats` | 9 | Standings, season stats, one-player stats, league + team leaders, team-season stats, game pace, high/low records |\n| `mlb.extra` | 15 | Draft (+ prospects), awards (catalogue + recipients), attendance, transactions, free agents, jobs (umpires/datacasters/scorers), Home Run Derby, All-Star ballots |\n| `mlb.meta` | 1 | `mlb_meta` — the `/{type}` lookup for every enum (positions, statTypes, gameTypes, pitchCodes, …) |\n\nThe official MLB Stats API the `MLB-StatsAPI` library wraps, read directly (no key) —\n**comprehensive coverage of the public surface**. `sportId=1` is MLB; discover ids\nwith `mlb_teams` / `mlb_schedule` / `mlb_player_search`, then drill into a game or\nplayer. Most tools accept the API's `hydrate` string to embed related objects in one\ncall.\n\n### Premier League — `premierleague.com` (no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `premierleague.core` | 7 | Competitions, season structure, awards, the league table, current gameweek, geo |\n| `premierleague.teams` | 10 | Teams (+ batch), squads, form (single + all-teams), team stats, next fixture, club metadata |\n| `premierleague.matches` | 8 | Fixtures/results feed + match centre: detail, events, lineups, team stats (~200 Opta metrics), officials, commentary |\n| `premierleague.players` | 8 | Player directory, profiles (basic/career/season), batch lookup, season + competition stats, metadata |\n| `premierleague.stats` | 2 | Player + team stat leaderboards (sort by any Opta metric) |\n| `premierleague.content` | 8 | Editorial content/search, latest+popular news/video, broadcasting schedule |\n\nThe private JSON APIs that power premierleague.com, read directly (no key,\nno cookies) across three hosts (the **SDP** stats platform, the editorial/\nbroadcast `api.premierleague.com`, and static config on `resources.premierleague.com`).\nUnderlying data is **Opta**. Premier League = competition `8`; season id is the\nstarting year (`2025` = 2025/26). Flow: `pl_teams` → `pl_matches` → a match id →\n`pl_match`/`pl_match_stats`; `pl_standings` for the table. Unofficial/undocumented —\nrespect the ~5 rps rate limit. The SDP wire params (`_limit`, `_sort`,\n`kickoff>`/`kickoff<`) are exposed under clean tool names (`limit`, `sort`,\n`kickoff_after`/`kickoff_before`).\n\n### LaLiga — `apim.laliga.com` (public key shipped)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `laliga.core` | 6 | Competitions, season instances (subscriptions), league table, rounds/matchweeks |\n| `laliga.teams` | 3 | Season team list, single team, club squad |\n| `laliga.players` | 3 | Every-player season stats (≈749, full Opta metrics), player profile + stats |\n| `laliga.matches` | 2 | Matches feed + single-match detail |\n\nThe private JSON API behind laliga.com (Azure APIM), read directly. Underlying\ndata is **Opta**. A **public** `Ocp-Apim-Subscription-Key` is **shipped as a\nworking default**, so it runs out of the box — but the key rotates; override it\nwith `LALIGA_SUBSCRIPTION_KEY` (env or `secrets:`) when reads start 401-ing\n(re-harvest from laliga.com's `__NEXT_DATA__`). A \"subscription\" is a season\ninstance (slug `laliga-easports-2025` = 2025/26); detail endpoints are keyed by\n**slug**. Pairs with the Premier League provider for cross-league football\ncomparison via the shared `stats.ladder` / `sport.fixtures_by_date` /\n`stats.player_season` tags.\n\n### Serie A — `api-sdp.legaseriea.it` (no auth)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `seriea.core` | 3 | All competitions, the 41-season catalogue, single-season detail |\n| `seriea.season` | 6 | League table (overall/home/away), the 20 teams, every-player + team Opta stats (paginated), all 380 matches, match lineups |\n\nThe public **SDP** JSON API behind legaseriea.it, read directly (no auth).\nUnderlying data is **Opta**. The Serie A competition id is baked in, so you only\never supply a `seasonId` (discovered from `seriea_seasons`; `seasonName` like\n`2025/2026`). Player stats return identity **and** ~279 Opta metrics in one call\n(no squad endpoint), paginated 30/page with `category=General|Goalkeeping`.\nCompletes the big-three football leagues alongside Premier League + La Liga via\nthe shared `stats.ladder` / `sport.fixtures_by_date` / `stats.player_season` tags.\n\n### Kalshi — `kalshi.com` (prediction markets, no key)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `kalshi.markets` | 6 | Market catalogue + detail, order book, public trades, OHLC candlesticks (single + batch) |\n| `kalshi.events` | 9 | Events, series catalogue (by category), single series, milestones, MVE combo collections, entity registry |\n| `kalshi.exchange` | 3 | Exchange status, trading schedule, announcements |\n\nThe CFTC-regulated US event-contract exchange. **Market data is public — no\nkey required**; optionally set `KALSHI_API_KEY_ID` + `KALSHI_PRIVATE_KEY`(`_PATH`)\nand every request is RSA-signed for Kalshi's higher authenticated rate limits\n(needs `pip install \"sportsdata-mcp[kalshi-auth]\"`). Trading surfaces stay out\nof scope (read-only provider). Id chain:\n`kalshi_series_list(category)` → `kalshi_events` → `kalshi_markets` →\norderbook/trades/candles by ticker. Prices are dollar-denominated.\n\n### Polymarket — `polymarket.com` (prediction markets, no key, geo-gated)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `polymarket.gamma` | 9 | Markets/events/series/sports/tags catalogue + site search (the discovery plane) |\n| `polymarket.clob` | 6 | Order book, best price, midpoint, spread, price history, CLOB catalogue |\n| `polymarket.data` | 2 | Public trade tape + top holders |\n\nThe largest crypto prediction market. **All read endpoints are anonymous** —\nthe wallet keys Polymarket's SDKs use are for order placement only (out of\nscope). ⚠️ **Geo-gated**: Polymarket drops connections at the network edge\nfrom restricted jurisdictions (verified: AU IPs time out on every host) — run\nfrom an unrestricted region or VPN. Flow: `polymarket_events` → a market's\n`clobTokenIds` → `polymarket_book` / `polymarket_price_history`.\n\n### X (Twitter) — `api.x.com` (needs a Bearer token)\n\n| Group | Tools | Notes |\n|---|---:|---|\n| `twitter.tweets` | 7 | 7-day search, volume counts, post lookup (batch + single), quote/repost/like engagement |\n| `twitter.users` | 6 | Profile lookup (handle/id, batch), user timelines, mentions |\n| `twitter.trends` | 2 | Trends by location (WOEID) + project usage/cap monitor |\n\nThe X API v2 read surface — **no anonymous tier**, so a Bearer token is\nrequired: env `X_BEARER_TOKEN` first (an operator can ship a deployment-wide\ntoken for all its users), then the config `secrets:` block (each user their\nown). The env var holds the bare token; the spec adds `Bearer `. Mind your\ntier's monthly read cap (`twitter_usage`); the spec throttles ~0.5 req/s and\nnever auto-retries 429s. Write/user-context surfaces (posting, DMs, follows)\nare out of scope. Flow: `twitter_user_by_username(\"AFL\")` → id →\n`twitter_user_tweets`; search with X operators (`\"Storm\" lang:en -is:retweet`).\n\n## Bring your own key\n\nSeventeen providers need a key you sign up for yourself. They are **excluded from the `free`\npreset** and from the default group set, so nothing here changes unless you opt in — set\nthe environment variable and add the group.\n\n| Provider | Env var | Tools | What it adds that nothing else here has |\n|---|---|---:|---|\n| `apisports` | `API_SPORTS_KEY` | 20 | **Ten sports on one key** — and the only rugby-union coverage here |\n| `theoddsapi` | `THE_ODDS_API_KEY` | 6 | Odds from ~40 international books, with historical snapshots for CLV work |\n| `oddsapiio` | `ODDS_API_IO_KEY` | 5 | **274 bookmakers** across 34 sports, down to padel, bandy and gaelic football |\n| `sportsgameodds` | `SPORTSGAMEODDS_API_KEY` | 7 | **Player props** with stable market ids you can join across books and time |\n| `sportsdataio` | `SPORTSDATAIO_*_KEY` | 9 | **DFS salaries and projections** (DraftKings/FanDuel), which no official feed publishes |\n| `sportmonks` | `SPORTMONKS_TOKEN` | 8 | Football lineups, events and per-player stats on a **genuinely free** tier |\n| `cfbd` | `CFBD_API_KEY` | 10 | College-football **analytics**: SP+, Elo, advanced box scores, historical lines |\n| `footballdataorg` | `FOOTBALL_DATA_ORG_KEY` | 10 | European competitions with no official feed here — UCL, Eredivisie, Championship |\n| `balldontlie` | `BALLDONTLIE_API_KEY` | 10 | One consistent shape across NBA, NFL, MLB and EPL |\n| `pandascore` | `PANDASCORE_TOKEN` | 8 | Esports beyond Dota 2 — CS2, LoL, Valorant, R6 |\n| `apitennis` | `API_TENNIS_KEY` | 7 | **ATP and ITF** draws, H2H and rankings (`wta` is women's-tour only) |\n| `cricketdata` | `CRICKETDATA_API_KEY` | 8 | International and franchise cricket (`cricketaustralia` is AU-only) |\n| `highlightly` | `HIGHLIGHTLY_API_KEY` | 7 | **Highlight video** across five sports — the only video surface here that isn't single-league |\n| `mysportsfeeds` | `MYSPORTSFEEDS_API_KEY` | 5 | US majors on a **free non-commercial** tier, with clean per-game player logs |\n| `isportsapi` | `ISPORTS_API_KEY` | 5 | **Asian-handicap** odds across Asian books |\n| `entitysport` | `ENTITYSPORT_TOKEN` | 5 | Cricket **ball-by-ball commentary** (deeper than `cricketdata`) |\n| `golfcourseapi` | `GOLFCOURSE_API_KEY` | 2 | 30k+ golf courses with per-hole par, yardage and stroke index |\n\n```bash\nexport THE_ODDS_API_KEY=...\nsportsdata-mcp serve --groups \"free,theoddsapi.*\"\n```\n\n### Two things to know before you rely on these\n\n**Their response shapes are documented, not verified.** We hold no key for any of them,\nso the shapes in each spec come from the vendor's own documentation rather than from a\nlive probe. Every one of these tools carries an explicit note telling the model to\ninspect the payload it actually received rather than trusting the sketch. Elsewhere in\nthis catalogue the shapes were probed and corrected — roughly one in three turned out to\ndiffer from what the docs implied — so treat this tier as the lower-confidence one until\nyou have run it with your key. If you do, a PR flipping `shapes_verified: true` with the\ncorrections is the single most useful contribution available.\n\n**Four of them report failures with HTTP 200.** `apitennis` answers a bad key with\n`200 {\"error\":\"1\", …}`, `cricketdata` with `200 {\"status\":\"failure\",\"reason\":\"Invalid\nAPI Key\"}`, `isportsapi` with `200 {\"code\":2,\"message\":\"Invalid [api_key]…\"}`, and `apisports`\nreturns `200` with a populated `errors` object and an **empty `response`** when you\nexhaust the daily quota. That last one is the nastiest: a\nmodel asking for today's fixtures gets an empty list and reports \"no matches today\", so\na blown quota is indistinguishable from a quiet Tuesday. All four specs declare\n`error_signals`, and the engine raises a real error naming the variable to set. If you\nconsume these APIs outside this server, check the body — the status code will lie to\nyou.\n\n**Odds-API.io is `/v3/`, not `/v2/`.** The vendor's own pages advertise v2 paths; every\none of them 404s. Caught by probing before the spec was written — otherwise all five\ntools would have shipped broken.\n\n### Not included\n\nThree candidates were probed and rejected rather than shipped broken:\n\n**SportDevs** — as of 2026-08-10 `sportdevs.com`, `api.sportdevs.com` and\n`rugby.sportdevs.com` have **no DNS record at all**. The service is gone, so the\nrugby/volleyball/handball coverage it advertised is not available from it. (Rugby union\nis instead covered by `apisports`.)\n\n**Live Golf API** — `use.livegolfapi.com` resolves, but every path including the root\nreturns `404 {\"message\":\"Application not found\"}`. The host is up; the application\nbehind it is not deployed.\n\n**Fighting Tomatoes** — the documented API paths under `fightingtomatoes.com/API/…`\nreturn the site's 404 page. The domain serves a web app, not the API it advertises.\n\n**TheSportsDB**'s free tier returns silently truncated data — an EPL table comes back\nwith 5 rows of 20, with nothing marking it as partial. A provider that quietly answers\nwith a fifth of the table is worse than no provider, so it is excluded rather than\nshipped with a warning.\n\n## Telemetry\n\n**Nothing is transmitted unless you turn it on.** There is no default that sends, no\nfirst-run prompt that defaults to yes, and consent is readable only from an environment\nvariable — never from a config file, because a config file can be committed to a repo or\nbaked into someone else's Docker image.\n\nWhat IS always on is local recording, which is yours:\n\n```bash\nsportsdata-mcp stats\n```\n\nPer-tool call counts, error rates, error codes and empty-result counts, worst first. A\nmodel can read the same thing mid-session via `sportsdata_session_stats` — usually the\nfastest way to tell a missing key (100% errors, code `AUTH_REQUIRED`) from an upstream\nwith no data (no errors, high `empty`).\n\nTo share it, two deliberate acts are required, and neither alone transmits:\n\n```bash\nexport SPORTSDATA_TELEMETRY=1\nexport SPORTSDATA_TELEMETRY_ENDPOINT=https://your/collector\n```\n\nTool **arguments are never recorded**, and that guarantee is structural rather than a\nfilter: `Telemetry.record()` has no parameter that could accept them. This matters here\nmore than in most projects — an ESPN Fantasy league id identifies a league and its\nmembers, and a Sleeper username *is* a username.\n\n```bash\nsportsdata-mcp telemetry --show-payload\n```\n\nprints the exact JSON a transmission would contain, so the claims are checkable rather\nthan promised. Full detail, including what the one free-text field does:\n[docs/TELEMETRY.md](docs/TELEMETRY.md).\n\nFor adoption numbers that touch no user at all — PyPI downloads, unique cloners,\nreferrers — `python scripts/metrics.py` reads public data about the package instead.\n\n## Cross-provider comparison\n\nEvery tool is tagged with provider-agnostic **capability** slugs (e.g.\n`sport.event_markets`, `racing.race_card`). Tools sharing a slug answer the same\nquestion and are directly comparable across providers. The discovery flow:\n\n1. `list_tools_by_capability(\"sport.event_markets\")` → every enabled tool exposing it\n2. Call each provider's tool concurrently with the resolved event ids\n3. Compare the raw snapshots (schemas are **not** normalised — the model reconciles them)\n\nSee [`examples/comparator-prompt.md`](./examples/comparator-prompt.md) for a full\n\"compare Storm v Cowboys odds across bookies\" walkthrough.\n\n## Per-provider notes\n\n- **Sportsbet** — anonymous public APIs; no secrets needed. REST events are keyed\n  by integer `eventId`; a persisted-GraphQL gateway is exposed via\n  `sportsbet_graphql_call` (browse `sportsbet://graphql/operations`).\n- **Entain / Ladbrokes** — a persisted-GraphQL gateway; the model supplies an\n  operation name + variables (discover them in `entain://graphql/operations`).\n  Hashes can drift when the front-end bundle ships; refresh them with\n  `sportsdata-mcp refresh-hashes entain`.\n- **AFL** — `afl.public.*` is anonymous. `afl.premium.*` mints an anonymous\n  `x-media-mis-token` automatically; some premium endpoints still return 401 for\n  anonymous callers.\n- **NRL** — the anonymous Champion Data match-centre CDN (`mc.championdata.com`),\n  the same static JSON the official nrl.com match centre reads. No secrets, no\n  cache-buster params needed. Resolve a `competitionId` from `nrl_competitions`\n  (e.g. 12999 = 2026 NRL Premiership), a `matchId` from `nrl_fixture`, then pull\n  per-player match stats from `nrl_match`; decode stat codes via\n  `nrl://stats/definitions`.\n- **NBA** — two surfaces, no secrets. `cdn.nba.com` is wide open (it even serves\n  JSON as `text/plain`, which the client accepts). `stats.nba.com` sits behind\n  Akamai, which black-holes any request missing a full browser header bundle — the\n  spec ships that bundle in `provider.default_headers`, so it just works. Akamai also\n  rate-limits hard, so the spec's `defaults` block throttles NBA to ~1 req/2.5 s,\n  sets a 45 s timeout, and retries transient `429/5xx` with exponential backoff (all\n  overridable via `providers.nba.*`). The `/stats/` family is one dispatcher\n  (`nba_stats_call`): pick an `operation` (the path segment, e.g.\n  `leaguedashplayerstats`) and pass `query_params` — each operation already carries\n  NBA's full default param set, so you override only what matters. Most responses are\n  column-oriented (`resultSets:[{name, headers, rowSet}]}`); v3 box scores are nested.\n- **ESPN** — four public hosts, **no auth, no API key**: `site.api.espn.com` (scores,\n  teams, standings, news, summaries), `sports.core.api.espn.com` (the canonical\n  `$ref`-linked model — odds, win-probability, plays, venues, drafts, coaches),\n  `site.web.api.espn.com` (search + athlete views) and `cdn.espn.com` (the live core\n  feed, needs `?xhr=1`). Nearly every URL is `.../sports/{sport}/{league}/{resource}`,\n  so the tools take `sport` + `league` as parameters and cover every ESPN league\n  parametrically — NFL, NBA, MLB, NHL, college, soccer (`eng.1`, `esp.1`, …), golf,\n  racing, tennis, MMA and more. Discovery: `espn_scoreboard(sport, league)` → an `event`\n  id → `espn_game_summary` or the deep `espn_core_call(event_*)` ops. The spec throttles\n  to ~5 req/s and retries transient `429/5xx` (overridable via `providers.espn.*`). Note\n  the core API path uses `leagues/{league}` (plural); core list responses are lazy\n  `{count, items:[{$ref}]}` envelopes — follow the refs for detail.\n- **PointsBet** — anonymous public APIs, no secrets. `api.au.pointsbet.com` serves\n  the sportsbook (sports + racing); `pointsbet.com.au` serves static CMS/nav assets\n  via the `pointsbet_content_call` dispatcher. Sports discovery:\n  `pointsbet_sport_competitions(sportKey)` → a competition key → `pointsbet_event(eventKey)`\n  for the full market book. Racing: `pointsbet_racing_meetings(startDate, endDate)` →\n  a `raceId` → `pointsbet_racing_race`. Many feeds return a top-level JSON array.\n- **TAB (Tabcorp)** — anonymous public data, no secrets. `api.beta.tab.com.au`\n  sits behind Akamai (the spec ships a browser header bundle + ~2.5 rps throttle,\n  like NBA); `cmsapi.tab.com.au` serves CMS feeds via `tab_cms_call`. Every\n  endpoint needs a `jurisdiction` (defaults to `NSW`). The API is HATEOAS and\n  **name-based** — paths embed sport/competition/match/venue names with spaces\n  (`…/AFL Football/competitions/AFL/matches/Adelaide v Geelong`), which the HTTP\n  layer percent-encodes; pass raw names. Racing: `tab_racing_meetings(date)` →\n  `raceType`+`venueMnemonic` → `tab_racing_race`. Sports:\n  `tab_sport` → `tab_competition` → `tab_match` for the full market book.\n- **Unibet** — anonymous AU data, no secrets, two surfaces. **Racing** is\n  persisted-GraphQL (`unibet_racing_call`, the `graphql_persisted` dispatcher) at\n  `rsa.unibet.com.au` — race ids are `eventKey`s like\n  `202606040200.T.AUS.hawkesbury.1`; the endpoint enforces Apollo CSRF so a\n  `Content-Type: application/json` header is sent. **Sport** is the **Kambi**\n  offering API (`unibet_kambi_call` over `*.kambicdn.com`, market AU): group tree,\n  events, bet offers, in-play, bet-builder. Browse ops in\n  `unibet://{racing,sport}/operations`.\n- **BetR** — anonymous AU data, no secrets. BetR runs on the **BlueBet** platform,\n  so the API is `web20-api.bluebet.com.au` — a flat REST surface covering racing\n  (next-to-jump, grouped racecards, race cards, form, fluctuations) and sport\n  (event types → categories → markets, SGMs). The `betr.com.au` Next.js\n  `_next/data/{buildHash}` blobs are skipped (fragile per-deploy hash; the API\n  serves the same data).\n- **Racing and Sports** — `www.racingandsports.com.au` racing/form data, no auth.\n  `racingandsports_todays_racing` (`/todays-racing-json-v2`) is the verified feed —\n  today's meetings across thoroughbred/harness/greyhound, by country. The site is\n  behind Cloudflare, which whitelists that feed but JS-challenges the other paths\n  from datacenter IPs (they work from a residential/browser IP); the form/fields/\n  results are HTML pages, and `GetOdds` needs a per-race token, so only the JSON\n  feeds are modelled.\n- **Betfair Exchange** — anonymous, the open read-only web APIs keyed by the public\n  `_ak` query param. The crown jewel is `betfair_market_prices` (`ero …/bymarket`) —\n  exchange **back/lay** prices, the sharpest reference odds. Discover market ids by\n  walking `betfair_navigation` (`scan …/bynode`, e.g. `EVENT_TYPE:7` = Horse Racing)\n  down to MARKET nodes; live scores/details come from the `ips` in-play service.\n  `string_csv` id params take a list. (The `apieds` racing widgets are Cloudflare-gated\n  from datacenter IPs and the `appsync` GraphQL needs a session, so they're out of\n  scope — racing is covered via navigation→bymarket.)\n- **Pinnacle** — anonymous, no key. The Arcadia \"guest\" API\n  (`guest.api.arcadia.pinnacle.com`) — the open feed the web sportsbook reads.\n  Sports only (sharp-odds book, no racing); prices are American odds. Flow:\n  `pinnacle_sports` → `pinnacle_sport_matchups(sportId)` → `pinnacle_matchup_markets(matchupId)`.\n  The provider sends Pinnacle's public web-client `X-API-Key`, which unlocks the\n  full per-sport + per-league matchup lists and the parlay markets.\n- **FanDuel (US)** — anonymous US data, no secrets, two surfaces under one provider.\n  **Racing** is the first **full-query GraphQL** provider: `fanduel_racing_call`\n  POSTs the literal query text (the `graphql_query` dispatcher kind, sibling to the\n  persisted-hash `graphql_persisted`), with boilerplate variables\n  (`brand`/`product`/`device`/profile) baked as per-op `default_variables` — most\n  calls need none, override only what varies (`{results: 12}`, `{trackCode, raceNumber}`).\n  **Sportsbook** is REST (`fanduel_sb_call`) keyed by the static public `_ak` web key,\n  region NJ. The two halves need different `Origin` headers, so the sportsbook\n  dispatcher overrides `Origin` + `x-sportsbook-region` over the racing-origin\n  provider default. Browse ops in `fanduel://{racing,sportsbook}/operations`.\n  (US data — composes with other US sources via capability tags.)\n\n## CLI reference\n\n| Command | Purpose |\n|---|---|\n| `serve` | Start the MCP stdio server (default when no subcommand) |\n| `list-groups` | Print every group with tool count + description |\n| `lint` | Validate specs against the schema + capability catalogue (nonzero on failure) |\n| `coverage` | What works from **your** location: reachable providers, what your region blocks, what needs a key |\n| `doctor` | Per-provider reachabil",
  "bytes": 60000,
  "sha": "4c234aac9cc103b5f17dc8fb05ba27fef64ab7502939d531dcca39a38d250086",
  "repo_slug": "danieltomaro13/sportsdata-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_danieltomaro13_sportsdata_mcp_bc632d50/readme"
}