Sportsdata Mcp

Created By
DanielTomaro138 days ago
Open-sourced sportsdata-mcp (MIT): ~500 tools / 28 providers — live cross-book betting odds (AU books + Pinnacle + Betfair), official league stats (AFL/NRL/NBA/MLB/EPL/…), racing, Kalshi/Polymarket. Zero-config: uvx sportsdata-mcp serve, or the setup command writes your Claude Desktop/Cursor config. Windows + macOS builds too. https://github.com/DanielTomaro13/sportsdata-mcp — provider requests welcome (one YAML file each).
Overview

sportsdata-mcp

CI Release License: MIT sportsdata-mcp MCP server

Add to Cursor

Free & open source (MIT). Live sports data and cross-book betting odds as MCP tools — ~500 tools across 28 providers, in Claude Desktop, Cursor, or any MCP client. Zero config: install, run setup, and the full catalogue serves.

An MCP server that exposes sports-data APIs (bookmakers, league/governing-body feeds, aggregators) as tools, configurable so you only load the tool groups you need. A capability-tag system makes tools from different providers interchangeable wherever they answer the same question — so the model can compare odds across bookies or stats across data sources with one discovery call.

The catalogue spans bookmakers, league/governing-body feeds, and stats aggregators, and it keeps growing. New providers are added by dropping a YAML spec into src/sportsdata_mcp/specs/ — the engine needs no code changes — so the exact provider and tool counts move over time. Run sportsdata-mcp list-groups for the live inventory, and three meta-tools (group discovery, capability lookup, resource listing) are always on regardless of what you enable.

Install

One-liner (any MCP client config, via uv):

uvx sportsdata-mcp serve        # or: pip install sportsdata-mcp

Prebuilt app (no Python needed): grab the latest release (macOS + Windows), unzip, and run sportsdata-mcp setup — it writes the config for Claude Desktop / Cursor for you. The macOS build is unsigned for now: right-click → Open the first time.

From source:

git clone https://github.com/DanielTomaro13/sportsdata-mcp.git
cd sportsdata-mcp
pip install -e .                # add ".[dev]" for the test + lint toolchain

Quickstart

sportsdata-mcp version          # print version info
sportsdata-mcp list-groups      # see every available tool group
sportsdata-mcp lint             # validate the packaged specs
sportsdata-mcp doctor           # probe enabled groups for reachability + auth
sportsdata-mcp serve            # start the MCP stdio server (default command)
sportsdata-mcp update-specs     # OTA-refresh provider specs (signed bundle); --clear reverts

Provider endpoints drift (e.g. Entain rotates its GraphQL persisted-query hashes). update-specs fetches a signed spec bundle and applies it into an overlay under ~/.sportsdata/spec-overlay, which the loader prefers over the packaged copy — so a drift fix doesn't need a whole new app build. The bundle is Ed25519-verified against a baked key (a product build refuses an unsigned/forged bundle; anti-rollback refuses a stale replay). Publish one with scripts/publish-spec-bundle.py; point --url / $SPORTSDATA_SPEC_FEED_URL at the asset. Restart the server after applying.

Enable tool groups with a config file or the SPORTSDATA_MCP_GROUPS env var:

SPORTSDATA_MCP_GROUPS="afl.public.core,sportsbet.racing,entain.graphql" sportsdata-mcp serve

See examples/ for Claude Desktop / Claude Code config snippets, a worked cross-bookie odds-comparison prompt, and an NBA shot-chart + box-score walkthrough that shows the nba_stats_call dispatcher pattern end to end.

Configuration

Config is resolved in this order (first hit wins):

  1. --config <path> flag
  2. $SPORTSDATA_MCP_CONFIG
  3. ./sportsdata-mcp.yaml
  4. ~/.config/sportsdata-mcp/config.yaml
  5. built-in defaults
# sportsdata-mcp.yaml
enabled_groups:
  - afl.public.core
  - sportsbet.racing
  - entain.graphql

providers:                      # all optional; sensible defaults apply
  sportsbet:
    request_timeout_seconds: 30
    rate_limit_rps: 10          # sustained requests/sec (token bucket)
    max_response_bytes: 0       # 0 = no cap (default); set a positive byte count to guard context

secrets: {}                     # for authenticated providers; prefer env vars in prod

A provider whose auth reads env: SOME_VAR is satisfied by the real environment variable first, then by a secrets: { SOME_VAR: "..." } entry of the same name (a local-dev convenience — keep real secrets in the environment in production).

Environment variables

VariableEffect
SPORTSDATA_MCP_GROUPSComma-separated group list; overrides enabled_groups.
SPORTSDATA_MCP_CONFIGPath to a config file (see resolution order above).
SPORTSDATA_MCP_MAX_BYTESGlobal response-size cap in bytes for every provider that doesn't set its own max_response_bytes. 0 (the default) means no cap.
SPORTSDATA_LICENSEDormant — the product is free; nothing requires a licence. The signed-entitlement machinery remains for anyone self-hosting gated premium feeds (see below).
SPORTSDATA_ENTITLEMENT_URL / SPORTSDATA_ENTITLEMENT_PUBKEYOnly relevant with the dormant entitlement gate above. Normally unset.

The (dormant) entitlement gate

This project used to be a paid product. It's free now — no licence exists or is needed, and every group serves by default — but the signed-entitlement machinery (Ed25519-verified feed grants, offline caching, 15-min revalidation) is kept dormant rather than deleted: it's tested, harmless when unset, and useful to anyone self-hosting this server who wants to gate premium feeds for their own users. Set SPORTSDATA_LICENSE + SPORTSDATA_ENTITLEMENT_URL against your own issuing service to activate it; leave them unset (the default) and nothing changes.

Keyed feeds. A few providers need an upstream credential you supply yourself — e.g. DATAGOLF_KEY for DataGolf, X_BEARER_TOKEN for Twitter/X. Everything else needs no key at all.

Meta-tools (list_available_groups, list_tools_by_capability, list_resources) are always registered regardless of what is enabled, so a fresh install can still guide the model to turn groups on.

On the response-size cap. There is no cap by default — every tool returns whatever the upstream API sends. If you want to guard the model's context window you can opt in to a cap: precedence is providers.<id>.max_response_bytes > SPORTSDATA_MCP_MAX_BYTES > the default (0, unlimited). Be aware that very large payloads (e.g. Sportsbet's full *_event_markets firehose, ~2 MB) won't fit in Claude's ~200 K-token context regardless — for those, prefer a narrower tool such as sportsbet_sports_card with includeTopMarkets: true.

Tool groups

Run sportsdata-mcp list-groups for live counts and descriptions.

AFL — api.afl.com.au

GroupToolsNotes
afl.public.core22Competitions, seasons, rounds, fixtures, ladders, match stats
afl.public.broadcasting9Broadcast regions, guides, providers
afl.public.content8News/articles, videos, photos
afl.premium.cfs1CFS premium ops — needs the anonymous x-media-mis-token
afl.premium.statspro1StatsPro ops — needs the x-media-mis-token
afl.premium.keyserver1HLS video URL signing

Sportsbet — sportsbet.com.au

GroupToolsNotes
sportsbet.racing15Race meetings, racecards, results, futures, SRMs
sportsbet.sports14Sport events, markets, prices, SGMs
sportsbet.cross12Live status, commentary, ladders, promos, video
sportsbet.results2Resulted events by date
sportsbet.graphql1Persisted GraphQL gateway (apigw/sportsbook/graph)

Entain / Ladbrokes — ladbrokes.com.au

GroupToolsNotes
entain.rest13Navigation quick-links and REST surfaces
entain.graphql1127 persisted GraphQL ops (gql/router)
entain.cdn1Contentful CMS entries (promotions, major-event nav)

PointsBet — pointsbet.com.au

GroupToolsNotes
pointsbet.sports10Sports catalogue, competition/event feeds, full event markets, in-play, search
pointsbet.racing11Meetings, racecards, results, futures, SRMs, tips, form
pointsbet.content3Promotions, promo-code splash, + pointsbet_content_call over the static CMS/nav assets

TAB — tab.com.au

GroupToolsNotes
tab.racing9Dates, meetings, racecards (fixed + parimutuel), form, next-to-go, jackpots, futures
tab.sports9Sports/competitions tree, full match markets + SGM, focused match markets, next-to-go, results, multi-builder
tab.discovery4Featured/live recommendations + tab_cms_call over the CMS content feeds

Unibet — unibet.com.au

GroupToolsNotes
unibet.racing1unibet_racing_call — persisted-GraphQL: meetings, race cards, form, futures, specials
unibet.sport3unibet_kambi_call over the Kambi offering API (groups, events, bet offers, in-play, bet-builder) + live stats + odds ladder

BetR — betr.com.au (BlueBet platform)

GroupToolsNotes
betr.racing8Next-to-jump, today's/grouped racecards, race card, form, fluctuations, movers
betr.sport7Event types, competition categories, event markets, match detail, popular SGMs
betr.content4Promotions + featured racing + popular market links

Pinnacle — pinnacle.com (sharp odds)

GroupToolsNotes
pinnacle.sports13Sports/leagues, full + highlighted + live + per-league matchups, carousel, matchup detail, straight + parlay markets (American-odds prices)
pinnacle.reference4Enums, market-label dictionary, teaser definitions, API status

Betfair Exchange — betfair.com.au (exchange odds)

GroupToolsNotes
betfair.exchange3bymarket + byevent back/lay price feeds (the sharpest odds) + cash-out availability
betfair.navigation1bynode catalogue graph (sport → meeting → event → market)
betfair.inplay5Live scores, event details, timeline (single + batch), scores+broadcast

Dabble — dabble.com.au (iOS app backend)

GroupToolsNotes
dabble.sport5Discover any competition (active list / name lookup / sports), then its fixtures (embedded markets + decimal odds) + the full per-fixture book (400+ markets + Pick'em props)

The Australian social-betting app's backend, read directly. Reached by posing as the iOS app — the spec bakes the app's User-Agent + x-device-id + x-app-version so the public feeds return JSON anonymously. AU-only and Cloudflare-fronted (403s from non-AU IPs, like the other AU books). Works for any competition — dabble_active_competitions lists the ~269 currently-bettable ones across all sports. Read-only odds — no bet placement. Composes with the other books via sport.event_markets / sport.prices.

SuperCoach — supercoach.com.au (News Corp / Champion Data fantasy)

GroupToolsNotes
supercoach.fantasy6One 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

News Corp / Champion Data's salary-cap fantasy game. Every feed lives under /{year}/api/{sport}/classic/v1/… — pass sport (one of the seven) and year (the season key: current calendar year for afl/nrl, currently 2025 for the others, which run across the new year). No auth, not geo-blocked (runs in CI). The core supercoach_players feed is per-round and large (~1–3 MB); use ppts1 (the real projection), not ppts. Adds the fantasy / projections angle via stats.fantasy_projections alongside Data Golf. See documentation/SuperCoach.md.

NBL — nbl.com.au (Australian National Basketball League)

GroupToolsNotes
nbl.basketball14Seasons, teams, ladder, schedule (scores), players + rosters, per-player season stats + game-log box scores, team stats, season stat leaders (sortable), and news

The league's own site data API — a Redis-cached proxy ("rosetta") over Genius Sports stats at prod.rosetta.nbl.com.au/get/…. No token, but referer-gated (403s without an nbl.com.au Origin + Referer — both baked into the spec). Every response is enveloped {type, count, source, data:[…]}. Season-scoped by year (the season start year: 2025 = NBL26, current); stat-leaders takes the season UUID from nbl_seasons. Distinct from the SuperCoach nbl fantasy feed — this is the official box-score source. See documentation/NBL.md.

WTA — wtatennis.com (Women's Tennis Association, official)

GroupToolsNotes
wta.tennis8Official WTA API: singles/doubles rankings, player catalogue + profiles + match history, tournament calendar + per-edition results + entry lists (seeds)

The WTA's official data API (api.wtatennis.com/tennis/…) — public Spring REST, no auth/key, no geo-block, runs in CI. Rankings need type+metric (rankSingles+singles or rankDoubles+doubles); tournaments are keyed by tournamentGroup.id + year (Australian Open = group 901). Fills the tennis gap on the stats side, composing with the bookmakers' live tennis markets. See documentation/WTA.md. (ATP has no equivalent open API — atptour.com is Cloudflare bot-protected — so it isn't modelled.)

Racing and Sports — racingandsports.com.au

GroupToolsNotes
racingandsports.racing3Today's race meetings (all codes, verified) + sports match list + per-race odds (token)

Data Golf — datagolf.com (needs a key)

GroupToolsNotes
datagolf.general3Player list, tour schedule, current event field
datagolf.predictions11DG rankings, pre-tournament (+ archive) + in-play model probabilities, skill + approach-skill ratings, player/live SG decompositions, live strokes-gained, live hole stats, DFS projections
datagolf.betting3Outright + matchup + all-pairings odds across ~13 books (incl. model line)
datagolf.historical9Archived raw round data, event-level results (finishes/earnings/points), historical bookmaker odds (outrights + matchups) and DFS results

Needs a Data Golf API key in the DATAGOLF_KEY env var (a personal subscription key — sourced via the static_query auth scheme, never stored in the repo).

FanDuel — fanduel.com (US)

GroupToolsNotes
fanduel.racing4fanduel_racing_call (full-query GraphQL: featured/today races + odds, single-race card, tracks, pools, talent picks) + messages/quick-links/promotions
fanduel.sportsbook2fanduel_sb_call (REST: event pages + markets, in-play, promos, configs via the _ak key) + live scores

NRL — mc.championdata.com

GroupToolsNotes
nrl.public.core4Champion Data match centre: competitions, fixture, per-match player stats, app settings

Plus the nrl://stats/definitions resource (dictionary of every NRL stat code).

NBA — cdn.nba.com + stats.nba.com

GroupToolsNotes
nba.public.cdn5Open CDN JSON: today's scoreboard, full schedule, live box score + play-by-play, odds
nba.stats2nba_daily_lineups + nba_stats_call, the dispatcher over the 138-endpoint /stats/ API

nba_stats_call fronts the whole stats.nba.com /stats/ analytics surface (player/team dashboards, box scores v2+v3, shot charts, play-by-play, leaders, standings, draft, hustle, tracking, …). Browse every operation, its required params and its defaults in the nba://stats/operations resource.

ESPN — espn.com JSON feeds

GroupToolsNotes
espn.scores5Site API convenience endpoints: scoreboard, teams, standings, game summary, news
espn.site1espn_site_call — team detail, rosters, schedules, injuries, depth charts, transactions, history, athlete news, groups, rankings (10 ops)
espn.core1espn_core_call — the canonical $ref-linked model: events/competitions, odds, win-probability, plays, venues, drafts, coaches, calendar, transactions (37 ops)
espn.web1espn_web_call — site-wide search + common/v3 athlete views (7 ops)
espn.cdn1espn_cdn_call — the CDN live core feed: scoreboard/game/boxscore/playbyplay (4 ops)

All ESPN tools are parametric over sport + league slugs (e.g. football/nfl, basketball/nba, soccer/eng.1), so the five groups cover every league ESPN carries. Browse each dispatcher's operations in its espn://{site,core,web,cdn}/operations resource.

OpenF1 — api.openf1.org (Formula 1, no key)

GroupToolsNotes
openf1.reference3Grand Prix weekends (meetings), sessions (the fixtures feed), driver roster
openf1.results5Session classification, starting grid, drivers'/constructors' championship standings, overtakes
openf1.timing5Per-lap sector + speed-trap timing, pit stops, tyre stints, live gaps/intervals, track position
openf1.telemetry2Car telemetry (speed/throttle/brake/gear/RPM/DRS) + (x,y,z) location at ~3.7 Hz
openf1.live3Race-control messages (flags/SC/incidents), team-radio clips, weather

Free, no-auth public REST surface (auth: none). Scope feeds by session_key / meeting_key (both accept the literal latest) and driver_number; discover keys with openf1_sessions / openf1_meetings first.

Cricket Australia — cricket.com.au (no key)

GroupToolsNotes
cricketaustralia.core7Fixtures (the /matches feed), competitions, tours/series, teams, player profiles (batch), venue lookup, competition ladder
cricketaustralia.match3Full scorecard (innings batting/bowling/wickets), run-graph series, live video streams
cricketaustralia.content2Pulselive CMS: video/text/audio/playlist content list + curated playlists

Two no-auth hosts (apiv2.cricket.com.au/web + the Pulselive CMS). The apiv2 endpoints carry jsconfig=eccn:true by default so they return the documented camelCase shape; flow is cricketaustralia_fixturescricketaustralia_scorecard?fixtureId=cricketaustralia_players?playerIds=.

MLB — statsapi.mlb.com (official Stats API, no key)

GroupToolsNotes
mlb.reference22Sports/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)
mlb.schedule5Games by date / range / team, plus postseason (schedule, series, tune-in) and tied games
mlb.game10Boxscore, linescore, play-by-play, v1.1 feed/live firehose, win-probability, context metrics, content, per-player game line, changes, uniforms
mlb.stats9Standings, season stats, one-player stats, league + team leaders, team-season stats, game pace, high/low records
mlb.extra15Draft (+ prospects), awards (catalogue + recipients), attendance, transactions, free agents, jobs (umpires/datacasters/scorers), Home Run Derby, All-Star ballots
mlb.meta1mlb_meta — the /{type} lookup for every enum (positions, statTypes, gameTypes, pitchCodes, …)

The official MLB Stats API the MLB-StatsAPI library wraps, read directly (no key) — comprehensive coverage of the public surface. sportId=1 is MLB; discover ids with mlb_teams / mlb_schedule / mlb_player_search, then drill into a game or player. Most tools accept the API's hydrate string to embed related objects in one call.

Premier League — premierleague.com (no key)

GroupToolsNotes
premierleague.core7Competitions, season structure, awards, the league table, current gameweek, geo
premierleague.teams10Teams (+ batch), squads, form (single + all-teams), team stats, next fixture, club metadata
premierleague.matches8Fixtures/results feed + match centre: detail, events, lineups, team stats (~200 Opta metrics), officials, commentary
premierleague.players8Player directory, profiles (basic/career/season), batch lookup, season + competition stats, metadata
premierleague.stats2Player + team stat leaderboards (sort by any Opta metric)
premierleague.content8Editorial content/search, latest+popular news/video, broadcasting schedule

The private JSON APIs that power premierleague.com, read directly (no key, no cookies) across three hosts (the SDP stats platform, the editorial/ broadcast api.premierleague.com, and static config on resources.premierleague.com). Underlying data is Opta. Premier League = competition 8; season id is the starting year (2025 = 2025/26). Flow: pl_teamspl_matches → a match id → pl_match/pl_match_stats; pl_standings for the table. Unofficial/undocumented — respect the ~5 rps rate limit. The SDP wire params (_limit, _sort, kickoff>/kickoff<) are exposed under clean tool names (limit, sort, kickoff_after/kickoff_before).

LaLiga — apim.laliga.com (public key shipped)

GroupToolsNotes
laliga.core6Competitions, season instances (subscriptions), league table, rounds/matchweeks
laliga.teams3Season team list, single team, club squad
laliga.players3Every-player season stats (≈749, full Opta metrics), player profile + stats
laliga.matches2Matches feed + single-match detail

The private JSON API behind laliga.com (Azure APIM), read directly. Underlying data is Opta. A public Ocp-Apim-Subscription-Key is shipped as a working default, so it runs out of the box — but the key rotates; override it with LALIGA_SUBSCRIPTION_KEY (env or secrets:) when reads start 401-ing (re-harvest from laliga.com's __NEXT_DATA__). A "subscription" is a season instance (slug laliga-easports-2025 = 2025/26); detail endpoints are keyed by slug. Pairs with the Premier League provider for cross-league football comparison via the shared stats.ladder / sport.fixtures_by_date / stats.player_season tags.

Serie A — api-sdp.legaseriea.it (no auth)

GroupToolsNotes
seriea.core3All competitions, the 41-season catalogue, single-season detail
seriea.season6League table (overall/home/away), the 20 teams, every-player + team Opta stats (paginated), all 380 matches, match lineups

The public SDP JSON API behind legaseriea.it, read directly (no auth). Underlying data is Opta. The Serie A competition id is baked in, so you only ever supply a seasonId (discovered from seriea_seasons; seasonName like 2025/2026). Player stats return identity and ~279 Opta metrics in one call (no squad endpoint), paginated 30/page with category=General|Goalkeeping. Completes the big-three football leagues alongside Premier League + La Liga via the shared stats.ladder / sport.fixtures_by_date / stats.player_season tags.

Kalshi — kalshi.com (prediction markets, no key)

GroupToolsNotes
kalshi.markets6Market catalogue + detail, order book, public trades, OHLC candlesticks (single + batch)
kalshi.events9Events, series catalogue (by category), single series, milestones, MVE combo collections, entity registry
kalshi.exchange3Exchange status, trading schedule, announcements

The CFTC-regulated US event-contract exchange. Market data is public — no key required; optionally set KALSHI_API_KEY_ID + KALSHI_PRIVATE_KEY(_PATH) and every request is RSA-signed for Kalshi's higher authenticated rate limits (needs pip install "sportsdata-mcp[kalshi-auth]"). Trading surfaces stay out of scope (read-only provider). Id chain: kalshi_series_list(category)kalshi_eventskalshi_markets → orderbook/trades/candles by ticker. Prices are dollar-denominated.

Polymarket — polymarket.com (prediction markets, no key, geo-gated)

GroupToolsNotes
polymarket.gamma9Markets/events/series/sports/tags catalogue + site search (the discovery plane)
polymarket.clob6Order book, best price, midpoint, spread, price history, CLOB catalogue
polymarket.data2Public trade tape + top holders

The largest crypto prediction market. All read endpoints are anonymous — the wallet keys Polymarket's SDKs use are for order placement only (out of scope). ⚠️ Geo-gated: Polymarket drops connections at the network edge from restricted jurisdictions (verified: AU IPs time out on every host) — run from an unrestricted region or VPN. Flow: polymarket_events → a market's clobTokenIdspolymarket_book / polymarket_price_history.

X (Twitter) — api.x.com (needs a Bearer token)

GroupToolsNotes
twitter.tweets77-day search, volume counts, post lookup (batch + single), quote/repost/like engagement
twitter.users6Profile lookup (handle/id, batch), user timelines, mentions
twitter.trends2Trends by location (WOEID) + project usage/cap monitor

The X API v2 read surface — no anonymous tier, so a Bearer token is required: env X_BEARER_TOKEN first (an operator can ship a deployment-wide token for all its users), then the config secrets: block (each user their own). The env var holds the bare token; the spec adds Bearer . Mind your tier's monthly read cap (twitter_usage); the spec throttles ~0.5 req/s and never auto-retries 429s. Write/user-context surfaces (posting, DMs, follows) are out of scope. Flow: twitter_user_by_username("AFL") → id → twitter_user_tweets; search with X operators ("Storm" lang:en -is:retweet).

Cross-provider comparison

Every tool is tagged with provider-agnostic capability slugs (e.g. sport.event_markets, racing.race_card). Tools sharing a slug answer the same question and are directly comparable across providers. The discovery flow:

  1. list_tools_by_capability("sport.event_markets") → every enabled tool exposing it
  2. Call each provider's tool concurrently with the resolved event ids
  3. Compare the raw snapshots (schemas are not normalised — the model reconciles them)

See examples/comparator-prompt.md for a full "compare Storm v Cowboys odds across bookies" walkthrough.

Per-provider notes

  • Sportsbet — anonymous public APIs; no secrets needed. REST events are keyed by integer eventId; a persisted-GraphQL gateway is exposed via sportsbet_graphql_call (browse sportsbet://graphql/operations).
  • Entain / Ladbrokes — a persisted-GraphQL gateway; the model supplies an operation name + variables (discover them in entain://graphql/operations). Hashes can drift when the front-end bundle ships; refresh them with sportsdata-mcp refresh-hashes entain.
  • AFLafl.public.* is anonymous. afl.premium.* mints an anonymous x-media-mis-token automatically; some premium endpoints still return 401 for anonymous callers.
  • NRL — the anonymous Champion Data match-centre CDN (mc.championdata.com), the same static JSON the official nrl.com match centre reads. No secrets, no cache-buster params needed. Resolve a competitionId from nrl_competitions (e.g. 12999 = 2026 NRL Premiership), a matchId from nrl_fixture, then pull per-player match stats from nrl_match; decode stat codes via nrl://stats/definitions.
  • NBA — two surfaces, no secrets. cdn.nba.com is wide open (it even serves JSON as text/plain, which the client accepts). stats.nba.com sits behind Akamai, which black-holes any request missing a full browser header bundle — the spec ships that bundle in provider.default_headers, so it just works. Akamai also rate-limits hard, so the spec's defaults block throttles NBA to ~1 req/2.5 s, sets a 45 s timeout, and retries transient 429/5xx with exponential backoff (all overridable via providers.nba.*). The /stats/ family is one dispatcher (nba_stats_call): pick an operation (the path segment, e.g. leaguedashplayerstats) and pass query_params — each operation already carries NBA's full default param set, so you override only what matters. Most responses are column-oriented (resultSets:[{name, headers, rowSet}]}); v3 box scores are nested.
  • ESPN — four public hosts, no auth, no API key: site.api.espn.com (scores, teams, standings, news, summaries), sports.core.api.espn.com (the canonical $ref-linked model — odds, win-probability, plays, venues, drafts, coaches), site.web.api.espn.com (search + athlete views) and cdn.espn.com (the live core feed, needs ?xhr=1). Nearly every URL is .../sports/{sport}/{league}/{resource}, so the tools take sport + league as parameters and cover every ESPN league parametrically — NFL, NBA, MLB, NHL, college, soccer (eng.1, esp.1, …), golf, racing, tennis, MMA and more. Discovery: espn_scoreboard(sport, league) → an event id → espn_game_summary or the deep espn_core_call(event_*) ops. The spec throttles to ~5 req/s and retries transient 429/5xx (overridable via providers.espn.*). Note the core API path uses leagues/{league} (plural); core list responses are lazy {count, items:[{$ref}]} envelopes — follow the refs for detail.
  • PointsBet — anonymous public APIs, no secrets. api.au.pointsbet.com serves the sportsbook (sports + racing); pointsbet.com.au serves static CMS/nav assets via the pointsbet_content_call dispatcher. Sports discovery: pointsbet_sport_competitions(sportKey) → a competition key → pointsbet_event(eventKey) for the full market book. Racing: pointsbet_racing_meetings(startDate, endDate) → a raceIdpointsbet_racing_race. Many feeds return a top-level JSON array.
  • TAB (Tabcorp) — anonymous public data, no secrets. api.beta.tab.com.au sits behind Akamai (the spec ships a browser header bundle + ~2.5 rps throttle, like NBA); cmsapi.tab.com.au serves CMS feeds via tab_cms_call. Every endpoint needs a jurisdiction (defaults to NSW). The API is HATEOAS and name-based — paths embed sport/competition/match/venue names with spaces (…/AFL Football/competitions/AFL/matches/Adelaide v Geelong), which the HTTP layer percent-encodes; pass raw names. Racing: tab_racing_meetings(date)raceType+venueMnemonictab_racing_race. Sports: tab_sporttab_competitiontab_match for the full market book.
  • Unibet — anonymous AU data, no secrets, two surfaces. Racing is persisted-GraphQL (unibet_racing_call, the graphql_persisted dispatcher) at rsa.unibet.com.au — race ids are eventKeys like 202606040200.T.AUS.hawkesbury.1; the endpoint enforces Apollo CSRF so a Content-Type: application/json header is sent. Sport is the Kambi offering API (unibet_kambi_call over *.kambicdn.com, market AU): group tree, events, bet offers, in-play, bet-builder. Browse ops in unibet://{racing,sport}/operations.
  • BetR — anonymous AU data, no secrets. BetR runs on the BlueBet platform, so the API is web20-api.bluebet.com.au — a flat REST surface covering racing (next-to-jump, grouped racecards, race cards, form, fluctuations) and sport (event types → categories → markets, SGMs). The betr.com.au Next.js _next/data/{buildHash} blobs are skipped (fragile per-deploy hash; the API serves the same data).
  • Racing and Sportswww.racingandsports.com.au racing/form data, no auth. racingandsports_todays_racing (/todays-racing-json-v2) is the verified feed — today's meetings across thoroughbred/harness/greyhound, by country. The site is behind Cloudflare, which whitelists that feed but JS-challenges the other paths from datacenter IPs (they work from a residential/browser IP); the form/fields/ results are HTML pages, and GetOdds needs a per-race token, so only the JSON feeds are modelled.
  • Betfair Exchange — anonymous, the open read-only web APIs keyed by the public _ak query param. The crown jewel is betfair_market_prices (ero …/bymarket) — exchange back/lay prices, the sharpest reference odds. Discover market ids by walking betfair_navigation (scan …/bynode, e.g. EVENT_TYPE:7 = Horse Racing) down to MARKET nodes; live scores/details come from the ips in-play service. string_csv id params take a list. (The apieds racing widgets are Cloudflare-gated from datacenter IPs and the appsync GraphQL needs a session, so they're out of scope — racing is covered via navigation→bymarket.)
  • Pinnacle — anonymous, no key. The Arcadia "guest" API (guest.api.arcadia.pinnacle.com) — the open feed the web sportsbook reads. Sports only (sharp-odds book, no racing); prices are American odds. Flow: pinnacle_sportspinnacle_sport_matchups(sportId)pinnacle_matchup_markets(matchupId). The provider sends Pinnacle's public web-client X-API-Key, which unlocks the full per-sport + per-league matchup lists and the parlay markets.
  • FanDuel (US) — anonymous US data, no secrets, two surfaces under one provider. Racing is the first full-query GraphQL provider: fanduel_racing_call POSTs the literal query text (the graphql_query dispatcher kind, sibling to the persisted-hash graphql_persisted), with boilerplate variables (brand/product/device/profile) baked as per-op default_variables — most calls need none, override only what varies ({results: 12}, {trackCode, raceNumber}). Sportsbook is REST (fanduel_sb_call) keyed by the static public _ak web key, region NJ. The two halves need different Origin headers, so the sportsbook dispatcher overrides Origin + x-sportsbook-region over the racing-origin provider default. Browse ops in fanduel://{racing,sportsbook}/operations. (US data — composes with other US sources via capability tags.)

CLI reference

CommandPurpose
serveStart the MCP stdio server (default when no subcommand)
list-groupsPrint every group with tool count + description
lintValidate specs against the schema + capability catalogue (nonzero on failure)
doctorPer-provider reachability + auth + REST-contract probe (nonzero on failure)
refresh-hashes <provider>Refresh persisted-query hashes from the live front-end bundle (--dry-run to preview)
versionPrint version info

-v / --verbose enables DEBUG logging (and un-silences httpx/httpcore).

Contributing

See documentation/ADDING_A_PROVIDER.md for the full guide, with separate playbooks for adding a bookmaker vs a sports website / data API. In short, adding a provider is a spec-only change in the common case:

  1. Write src/sportsdata_mcp/specs/<provider>.yaml (copy an existing spec).
  2. Tag each tool with capability slugs from specs/_capabilities.yaml; add a new slug there if none fits (two providers sharing a slug makes them comparable).
  3. sportsdata-mcp lint — must pass.
  4. sportsdata-mcp doctor (with the new groups enabled) — probes it live.
  5. pytest -m "not live" — offline suite; drop the marker filter to run live tests.
  6. Add a row to tests/contract/test_api_contracts.py so the new provider's documented response shape is verified live on every PR (see below).
pip install -e ".[dev]"
pytest -m "not live"      # offline suite (the CI gate)
pytest -m contract        # live response-contract checks (see below)
ruff check .

CI

Every push/PR runs three jobs (.github/workflows/ci.yml):

  • test — ruff, sportsdata-mcp lint, and the offline suite (pytest -m "not live") across Python 3.11–3.13. The deterministic gate.
  • contractpytest -m contract: live response-contract checks that hit each upstream API and assert it still returns the documented shape (top-level keys, and the documented keys on list items). It is resilient by design — it skips on anything outside our control (network errors, 5xx, 401/403/429, geo-blocks, a missing DATAGOLF_KEY, or an empty feed) and only fails on a genuine shape regression or a broken spec (wrong path/params → 4xx). Bookmaker APIs that geo-block GitHub's runners simply skip there.
  • package — builds the wheel and proves the CLI loads the packaged specs from a clean install.

License

Proprietary and confidential. Copyright (c) 2026 Daniel Tomaro. All rights reserved. No use, copying, modification, or distribution is permitted without the owner's prior written consent — see LICENSE.

mcp-name: io.github.DanielTomaro13/sportsdata-mcp

Server Config

{
  "mcpServers": {
    "sportsdata": {
      "command": "uvx",
      "args": [
        "sportsdata-mcp",
        "serve"
      ]
    }
  }
}
Project Info
Created At
8 days ago
Updated At
8 days ago
Author Name
DanielTomaro13
Star
-
Language
-
License
-
Category
Tags

Recommend Servers

View All
Teardrop

2 days ago