MCP server documentation
Version 1.0.0 · Last updated 27 October 2026
Overview
The HoopMetrics MCP server exposes NBA statistics, schedules, game projections and the HoopMetrics model’s picks to any Model Context Protocol client. All tools are read-only.
| Property | Value |
|---|---|
| Endpoint | https://mcp.hoopmetrics.app (the host root) |
| Transport | Streamable HTTP — JSON-RPC 2.0 over POST; GET with Accept: text/event-stream for the stream |
| Server name | hoopmetrics 1.0.0 |
| Authentication | OAuth 2.1 bearer token (authorization code + PKCE S256), dynamic client registration |
| Scopes | stats:read (all tools), picks:read (picks) |
Connecting
Paste https://mcp.hoopmetrics.app into your client. Step-by-step setup for Claude, Claude Code, Cursor and VS Code is on the MCP server page. Opening the endpoint in a browser redirects to that page; MCP clients are served the protocol.
Authentication
The server follows the MCP authorization spec. Clients do not need to be configured with any of the URLs below — they are discovered:
- A request without a token gets
401withWWW-Authenticate: Bearer resource_metadata="…". - The protected resource metadata (RFC 9728) names the resource
https://mcp.hoopmetrics.appand the authorization serverhttps://hoopmetrics.app/api/auth. - The client reads the authorization server metadata (RFC 8414), registers itself (RFC 7591) and runs the authorization code flow with PKCE, passing
resource=https://mcp.hoopmetrics.app. - The user signs in to HoopMetrics (or creates a free account) and approves the requested scopes on the consent screen.
- The client calls the endpoint with
Authorization: Bearer <access token>. Tokens are signed JWTs with issuerhttps://hoopmetrics.app/api/authand audiencehttps://mcp.hoopmetrics.app; refresh tokens are issued withoffline_access.
| Endpoint | URL |
|---|---|
| Protected resource metadata | https://mcp.hoopmetrics.app/.well-known/oauth-protected-resource |
| Authorization server metadata | https://hoopmetrics.app/.well-known/oauth-authorization-server/api/auth |
| Issuer | https://hoopmetrics.app/api/auth |
| Authorization | https://hoopmetrics.app/api/auth/oauth2/authorize |
| Token | https://hoopmetrics.app/api/auth/oauth2/token |
| Dynamic client registration | https://hoopmetrics.app/api/auth/oauth2/register |
| Revocation | https://hoopmetrics.app/api/auth/oauth2/revoke |
| JWKS | https://hoopmetrics.app/api/auth/jwks |
Scopes
| stats:read | Required for every call. Players, teams, games, schedules, leaderboards and game projections. |
| picks:read | The model’s picks: get_picks and the picks / prop_lines sections of get_game. |
Tools
Parameters below are generated from the schemas the server registers, so they always match what your client sees in tools/list. Results come back as one text content block holding JSON.
Start with search to turn a name into a slug, then call get_player or get_team. Games are addressed by game_id from list_games or get_team.
Find NBA players and teams by (partial) name. Returns slugs to use with get_player / get_team.
PARAMETERS
queryrequired string | Name or part of a name, e.g. 'jokic' or 'celtics' min length 2 |
season string | Season like 2026-27 (default: current) format YYYY-YY |
RETURNS
season, players[] (slug, full_name, team, position, gp, pts, reb, ast) and teams[] (slug, abbreviation, name). Up to 10 players, ordered by scoring.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search",
"arguments": {
"query": "jokic"
}
}
}Schedule and results for one date (YYYY-MM-DD, default today UTC). Returns game_id for get_game.
PARAMETERS
date string | Date, YYYY-MM-DD format YYYY-MM-DD |
RETURNS
date, count and games[]: game_id, game_date, game_time_utc, status (scheduled / live / final), period, scores, and both teams' id, abbreviation, name, slug and colour.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_games",
"arguments": {
"date": "2026-10-27"
}
}
}One game by game_id: teams, status, score, model projection, and (with picks:read) the model's picks and player prop lines.
The picks and prop_lines sections need picks:read; without it they read “requires picks:read scope”.
PARAMETERS
game_idrequired string | NBA game id, e.g. 0022600123 |
sections ("projection" | "picks" | "prop_lines")[] | Which optional sections to include (default all). prop_lines is large (every line of every player); leave it out when only the model's picks are needed. |
RETURNS
game; projection (model version, home_win_prob, projected points for each side, projected_total, projected_spread, injury adjustments in points); picks.game (team markets), picks.players (player props) and picks.prop_lines (every line offered for every player).
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_game",
"arguments": {
"game_id": "0022600128",
"sections": [
"projection",
"picks"
]
}
}
}Bio, season averages and recent game log for a player (slug from search).
PARAMETERS
slugrequired string | Player slug, e.g. nikola-jokic |
season string | Season like 2026-27 (default: current) format YYYY-YY |
games integer | How many recent games (default 12) 1–40 |
RETURNS
player (bio, team, position, draft), season, season_stats (per-game averages, shooting splits, rebounds split, fouls drawn, plus-minus) and recent_games[] (box score line per game, newest first).
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_player",
"arguments": {
"slug": "nikola-jokic",
"games": 10
}
}
}Record, team stats, roster and latest games for a team (slug from search).
PARAMETERS
slugrequired string | Team slug, e.g. boston-celtics |
season string | Season like 2026-27 (default: current) format YYYY-YY |
RETURNS
season, team (record, per-game team stats and league ranks), roster[] with each player's per-game averages, and recent_games[] — the team's 10 latest games by date, scheduled ones included.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_team",
"arguments": {
"slug": "boston-celtics"
}
}
}Top players by a per-game stat for a season.
PARAMETERS
stat "pts" | "reb" | "ast" | "stl" | "blk" | "fg3m" | "min" | Per-game stat to rank by default "pts" |
season string | Season like 2026-27 (default: current) format YYYY-YY |
limit integer | How many players (default 25) 1–100 |
RETURNS
season, stat and leaders[] in rank order — player slug and name, position, team, games played and the per-game value.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_leaderboard",
"arguments": {
"stat": "ast",
"limit": 10
}
}
}Current value picks from the HoopMetrics model: game markets (moneyline, totals, handicap) and top player props, ordered by edge, each with the bookmaker selection behind its price (sb_event_id / sb_market_id / sb_selection_uuid). Requires picks:read.
PARAMETERS
player_limit integer | Player prop picks to return (default 30) 1–100 |
only_value boolean | Return only picks with verdict = value (drops pass / longshot / low-sample). Longshot = positive EV but price > 4.0 or model < 25% |
RETURNS
note, game_picks[] and player_picks[], ordered by edge: game_id, market, selection, line, price, model_prob, implied_prob, edge, verdict, sample_games, the bookmaker selection ids (sb_*) and price_captured_at. Player picks add the player, projection, hit rates (last 5, last 15, season, head-to-head) and the opponent's rank against that stat.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_picks",
"arguments": {
"only_value": true,
"player_limit": 20
}
}
}Data & picks
Every answer is a snapshot of the HoopMetrics database, not a live feed. Lines and picks refresh about every 30 minutes on game days, scores every couple of hours, box scores every morning, and the injury report shortly before each line refresh. Seasons are written 2026-27; the current season is the default everywhere.
Picks are research, not advice. They compare the model’s probability with a bookmaker’s price; the fields that describe a pick are:
| Field | Meaning |
|---|---|
| edge | model_prob × price − 1. 0.05 means the model expects a 5% return per unit at that price. |
| model_prob | The model's probability that the selection wins. |
| implied_prob | 1 / price — what the price implies, margin included. |
| price | Decimal odds at price_captured_at. |
| price_captured_at | When that price was read (UTC). Re-check the live price before acting on a quote older than a few hours. |
| sb_event_id · sb_market_id · sb_selection_uuid | Identify the bookmaker event, market and selection the price came from. null when the selection is no longer offered. |
| sample_games | Current-season games behind the estimate. |
| game_time_utc | Tip-off in UTC. game_date is the US calendar date of the game. |
Verdicts
| verdict | Meaning |
|---|---|
| value | Positive edge with enough current-season sample to back it. |
| pass | No edge at the current price. |
| longshot | Positive expected value, but price above 4.0 or model probability under 25%. |
| low-sample | Positive edge that the current-season sample does not yet confirm. |
| injury-risk | Player props only: the injury report gives the player a 10–50% chance of missing the game, so the pick is never marked value. Above 50% there is no pick at all. |
Errors
| Response | When |
|---|---|
| 401 invalid_request | No Authorization header. Start the OAuth flow from WWW-Authenticate. |
| 401 invalid_token | Expired or malformed token, or one issued for another resource (issuer or audience mismatch). Refresh or re-authorize. |
| 403 insufficient_scope | The token lacks stats:read. |
| Tool result isError | Unknown slug or game_id, or a picks tool called without picks:read. The text says which. |
Server instructions
The server sends these instructions to the client on initialize:
HoopMetrics is NBA research: box scores, season splits, schedules and a v1 betting model priced against bookmaker lines.
Every figure is a snapshot from the HoopMetrics database, not a live feed; carry the game_date / season through to what you report.
Identity first: call search to turn a name into a slug, then get_player / get_team with that slug. Games are addressed by game_id (from list_games or get_team).
Picks (get_picks, and the picks section of get_game) are model output with an "edge" = model probability × decimal price − 1; they need the picks:read scope and are research, not advice.
Every pick carries the bookmaker selection behind its price — sb_event_id (event id), sb_market_id and sb_selection_uuid — plus price_captured_at, the time that quote was read. They are null when the selection has left the offer; re-check the live price before acting on a quote older than a few hours.Changelog
- 1.0.0 — seven read-only tools, OAuth 2.1 with dynamic client registration, endpoint at the root of
mcp.hoopmetrics.app.