npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cbb-mcp-server

v0.2.1

Published

Free, local MCP server wrapping the College Basketball Data API (CBD), with a derived-analytics layer for on/off splits, RAPM, play style, and shot charts.

Readme

cbb-mcp-server

A free, local MCP (stdio) server that wraps the College Basketball Data API ("CBD"), so any MCP client (Claude Desktop, Claude Code, etc.) can query college basketball games, stats, rankings, rosters, lineups, play-by-play, and betting lines as tools. It also adds a derived-analytics layer on top — on/off splits, RAPM-style player impact, play-style classification, and shot charts — comparable to what sites like Hoop Explorer surface, computed from CBD's own data rather than scraped from anywhere.

No paid infrastructure, no hosted server: it runs locally via npx, and the only credential required is a free CBD API key.

Getting a free CBD API key

  1. Go to collegebasketballdata.com and sign in.
  2. Generate an API key from your account page. It's free.
  3. Set it as the CBB_DATA_API_KEY environment variable wherever you run this server (see below).

If the key is missing, every tool call fails with an actionable error telling you exactly this.

Installing / running it

The intended way to run this is via npx, from your MCP client's config — you generally don't need to install or clone anything yourself. See Using it with Claude Desktop / Claude Code below.

To run it directly from a terminal (for testing, or with a non-Claude MCP client):

CBB_DATA_API_KEY=your-key-here npx cbb-mcp-server

It speaks MCP over stdio, so it won't print anything on its own — an MCP client needs to connect to it.

Local development

git clone https://github.com/MarshallBorham/CBB_MCP.git
cd CBB_MCP
npm install
cp .env.example .env   # then paste your CBB_DATA_API_KEY into .env
npm run dev            # watch-mode, runs src/index.ts directly via tsx

Other commands:

  • npm run build — compile TypeScript to dist/
  • npm test — full test suite (npx vitest run <path> for a single file)
  • npm run test:coverage — test suite with coverage report
  • node dist/index.js — run the built server directly (requires CBB_DATA_API_KEY in the environment, or a .env file in the working directory)

Using it with Claude Desktop / Claude Code

Add this to your MCP client's config (Claude Desktop's claude_desktop_config.json, or Claude Code's .mcp.json / claude mcp add equivalent):

{
  "mcpServers": {
    "cbb": {
      "command": "npx",
      "args": ["-y", "cbb-mcp-server"],
      "env": {
        "CBB_DATA_API_KEY": "your-key-here"
      }
    }
  }
}

This matches the package's actual bin entry (cbb-mcp-server, from package.json) — npx -y cbb-mcp-server downloads and runs it without a separate install step.

Rate limiting & caching

CBD asks that its API not be hammered, so this server bakes in the same courtesy the community Python/Node clients do: a randomized 2–7 second delay between outbound requests to CBD, serialized through a queue so concurrent tool calls don't fire at once. On top of that, every response is cached in memory (keyed on the exact request URL) for 1 hour by default, so repeated calls for the same data don't re-hit CBD or re-wait out the delay. get_shot_chart's season-wide shot data (needed for both the D1 baseline and any player-only, no-team query) is cached separately for 24 hours, since pulling it the first time for a given season means one request per game date for the whole season. 429 responses are retried with backoff (honoring CBD's Retry-After header when present) before surfacing a clear rate-limit error.

Practically, this means:

  • The first call for a given season/team is the slowest one.
  • The analytics tools that pull a lot of underlying data — get_player_rapm (play-by-play for every game in scope) and get_shot_chart in player-only mode (a whole season's shots when no team is given) — can take from several seconds to several minutes the first time, depending on how many games are in scope, since each underlying request still respects the courtesy delay. Re-running the same query afterward is fast (served from cache).

Tools

Every tool mirrors CBD's real parameter names. Required parameters are called out below; anything not listed as required is an optional filter.

Games

  • get_games — scores/results (2003+). Filter by date range, season, team, conference, season type, tournament, status. { "team": "Duke", "season": 2024 }
  • get_games_media — broadcast/TV info for games. Same filters as get_games minus status. { "team": "Duke", "season": 2024 }
  • get_games_players — individual player box scores. { "team": "Duke", "season": 2024 }
  • get_games_teams — team-level box scores. { "team": "Duke", "season": 2024 }

Stats

  • get_player_season_stats — player-level season aggregates. Requires season. { "season": 2024, "team": "Duke" }
  • get_player_shooting_stats — player shot-zone/efficiency detail for a season. Requires season plus at least one of team/conference. { "season": 2024, "team": "Duke" }
  • get_team_season_stats — team-level season aggregates. No required params. { "team": "Duke", "season": 2024 }
  • get_team_shooting_stats — team shot-zone/efficiency detail. Requires season plus at least one of team/conference. { "season": 2024, "team": "Duke" }

Plays, lineups, substitutions

  • get_play_types — enumeration of CBD's play-type tags. No params.
  • get_plays_by_date — every play across all games on a date. Requires date. { "date": "2024-01-15T00:00:00Z" }
  • get_plays_by_team — every play for a team's season. Requires season and team. { "season": 2024, "team": "Duke" }
  • get_plays_by_game — every play in one game, including which players were on the floor (onFloor) and shot location/outcome (shotInfo) — the core input for RAPM and shot charts. Requires gameId. { "gameId": 401520175 }
  • get_lineups_by_team — every 5-man lineup a team used in a season, with minutes and per-lineup stats. Requires team and season. { "team": "Duke", "season": 2024 }
  • get_lineups_by_game — every 5-man lineup used in one game. Requires gameId. { "gameId": 401520175 }
  • get_substitutions_by_team — every sub event for a team's season. Requires season and team. { "season": 2024, "team": "Duke" }
  • get_substitutions_by_game — every sub event in one game (useful as a cross-check against get_plays_by_game's onFloor data). Requires gameId. { "gameId": 401520175 }
  • get_substitutions_by_player — every sub event for one player's season. Requires playerId and season. { "playerId": 15225, "season": 2024 }

Rankings & ratings

  • get_rankings — AP/coaches poll rankings. No required params. { "season": 2024, "pollType": "ap" }
  • get_srs_ratings — Simple Rating System ratings. No required params. { "season": 2024 }
  • get_adjusted_efficiency — KenPom-style adjusted offensive/defensive efficiency. No required params. { "season": 2024, "team": "Duke" }
  • get_elo — Elo ratings. No required params. { "season": 2024 }

Teams & rosters

  • get_teams — full list of D1 teams. No required params.
  • get_team_roster — team rosters. Requires season. { "season": 2024, "team": "Duke" }
  • get_conferences — full list of D1 conferences. No params.
  • get_conference_history — conference membership history. No required params.

Betting (2013+)

  • get_lines — betting lines. No required params. { "season": 2024, "team": "Duke" }
  • get_lines_providers — list of betting line providers CBD tracks. No params.

Draft

  • get_draft_picks — NBA draft picks with college background. No required params. { "year": 2024 }
  • get_draft_positions — draft position categories CBD uses. No params.
  • get_draft_teams — NBA teams CBD's draft data covers. No params.

Venues

  • get_venues — full list of college basketball venues CBD tracks. No params.

Analytics (derived — not returned directly by CBD)

  • get_player_on_off — a player's on/off splits: team offensive/defensive/net rating and four factors with the player on the court vs. off it, minutes-weighted across every lineup CBD returns. Requires team, season, player (name or CBD player/athlete ID). { "team": "Duke", "season": 2024, "player": "Jared McCain" }
  • get_player_rapm — Regularized Adjusted Plus-Minus for every player who appeared for a team or conference in a season, via ridge regression over play-by-play stints. Requires season and exactly one of team/conference. This is the heaviest tool here — see Rate limiting & caching. { "team": "Duke", "season": 2024 }
  • get_team_play_style / get_player_play_style — classifies a team's or player's shot profile into style tags (e.g. Perimeter Sniper, Rim Attacker) based on shot-zone frequency and efficiency. Team version requires team and season; player version also requires player. { "team": "Duke", "season": 2024 }
  • get_shot_chart — hex-binned shot frequency/efficiency by court location for a team or player, compared against the D1 average for that season. Requires season and exactly one of team/player. Returns structured bin data, not an image. { "team": "Duke", "season": 2024 }

Which analytics numbers are exact, and which are approximate

The four raw-tool layers above are direct passthroughs of what CBD returns — no approximation. The analytics layer is built on top of that same data, but each tool makes different tradeoffs worth understanding before you trust its numbers:

  • get_player_on_off reconstructs offense/defense/net rating and four factors directly from play-by-play (stint segmentation by on-court lineup, points from running score totals, possessions and four-factors from shot/rebound/turnover events), not from CBD's /lineups/team endpoint. That endpoint's own possession counts turned out not to reliably reconcile with real season totals — verified directly, e.g. Duke 2023-24's summed opponent possessions across its lineup rows undercounted the real season total by ~12% while summed opponent points matched almost exactly — a defect isolated to that endpoint's own aggregation, not to CBD's underlying play-by-play data. A golden-dataset comparison against Hoop Explorer's on/off numbers (8 real cases, 5 Duke + 3 Houston players, 2023-24 season) confirms the fix: every case now matches within ~6 points of net rating on both sides (the worst case being Houston's best player's thin, 309-minute "off" sample — the kind of case any on/off methodology disagrees on most), down from gaps of 5-15 points for Duke specifically before the fix. See test/analytics/onOff.hoopExplorerComparison.test.ts and test/fixtures/hoop-explorer-on-off.json for the full data.

    Two smaller caveats remain, both deliberate and documented in onOff.ts: a 10-second minimum cumulative-duration floor per 5-man combination (far smaller than a typical minutes floor, since this tool aggregates by summing raw points/possessions once rather than averaging each lineup's own pre-computed rating, so it doesn't need a large floor to avoid small-sample noise the way that approach would); and CBD's onFloor field occasionally reporting something other than exactly 10 players for a stint (verified: ~11% of Duke 2023-24's season), which this tool excludes from on/off entirely rather than guess-correct — the main reason totalMinutes can run somewhat below a team's true season total even though ratings themselves aren't affected by it.

  • get_player_rapm is explicitly approximate. It depends on several modeling choices that any RAPM implementation has to make and that legitimately differ between implementations: how possessions are estimated per stint, a combined offense/defense design-matrix column per player (rather than separate offense and defense columns), and a ridge-regression regularization strength chosen via cross-validation on this data rather than tuned against any external benchmark. Treat it as "this player's impact, by this implementation's methodology" rather than a ground truth. What is verified: the stint segmentation this all sits on top of (contiguous stretches of the same 10 players on the floor, derived from each play's onFloor list) was cross-checked against real substitution events for a real game and matched 30/30 boundaries within a 35-second tolerance — see test/analytics/rapm.stintCrossCheck.test.ts.

  • get_team_play_style / get_player_play_style are a first-pass heuristic, not a definitive taxonomy, and classify by shot-zone mix (rim / mid-range / three-point / free-throw frequency and efficiency), not by offensive action (CBD's play-by-play doesn't tag possessions as Pick & Roll, Post-Up, Isolation, etc., so this tool can't classify by those categories the way some other style tools do). Style tags and thresholds are a reasonable starting point, intended to be iterated on, not a final word.

  • get_shot_chart — bin-level frequency and efficiency are computed directly from real shot attempts (exact, modulo the coordinate-normalization and hex-bin-size choices made when building the grid); the "vs. D1 average" comparison is only as good as the cached league-average baseline, which reflects whatever the season's data looked like the last time it was fetched (cached for 24 hours).

License

MIT — see LICENSE.