# Directory Structure
```
├── Dockerfile
├── nba_server.py
├── README.md
└── requirements.txt
```
# Files
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
[](https://mseep.ai/app/obinopaul-nba-mcp-server)
# NBA MCP Server
A Python server implementing Model Context Protocol (MCP) for NBA statistics and live game data.
## Overview
This server provides a set of tools for accessing NBA data through the NBA API. It serves as a bridge between applications and the NBA's data services, offering both live game information and historical statistics.
## Features
- Live game data (scoreboard, box scores, play-by-play)
- Player information and career statistics
- Team game logs and statistics
- League standings
- Game results and schedules
## Tools
### Live Game Data
- **nba_live_scoreboard**
- Fetch today's NBA scoreboard (live or latest)
- Returns game IDs, start times, scores, and broadcast details
- **nba_live_boxscore**
- Fetch real-time box score for a given NBA game ID
- Provides detailed player and team statistics
- **nba_live_play_by_play**
- Retrieve live play-by-play actions for a specific game
- Includes scoring plays, fouls, timeouts, and substitutions
### Player Information
- **nba_common_player_info**
- Retrieve basic information about a player
- Includes biographical data, height, weight, team, position
- **nba_player_career_stats**
- Obtain a player's career statistics
- Available in different formats (per game, totals, per 36 minutes)
- **nba_list_active_players**
- Return a list of all currently active NBA players
- **nba_player_game_logs**
- Obtain a player's game statistics within a specified date range
### Team Data
- **nba_team_game_logs_by_name**
- Fetch a team's game logs using the team name
- Avoids needing to know the team's numeric ID
- **nba_fetch_game_results**
- Fetch game results for a given team ID and date range
- **nba_team_standings**
- Fetch NBA team standings for a given season and season type
- **nba_team_stats_by_name**
- Fetch team statistics using the team name
- Supports different aggregation methods (totals, per game, etc.)
- **nba_all_teams_stats**
- Fetch statistics for all NBA teams across multiple seasons
### Schedule Information
- **nba_list_todays_games**
- Returns scoreboard data for any specific date
## Usage
The server is implemented using the MCP framework and can be run as a standalone service.
```python
# Start the server
python nba_server.py
# or
mcp run nba_server.py
```
### Configuration
- The server runs with a 30-second timeout for more reliable operation
- Signal handlers are implemented for graceful shutdown (Ctrl+C)
### Usage with Claude Desktop
#### Option 1: Using Docker (Recommended)
1. Clone this repository
```
git clone https://github.com/obinopaul/nba-mcp-server.git
cd nba-mcp-server
```
2. Install dependencies
```
pip install -r requirements.txt
```
3. Build the Docker image
```
docker build -t nba_mcp_server .
```
4. Run the Docker container
```
docker run -d -p 5000:5000 --name nba_mcp_server nba_mcp_server
```
5. Add this to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"nba_mcp_server": {
"command": "docker",
"args": [
"exec",
"-i",
"nba_mcp_server",
"python",
"nba_server.py"
]
}
}
}
```
#### Option 2: Direct Python Execution
1. Clone this repository
```
git clone https://github.com/obinopaul/nba-mcp-server.git
cd nba-mcp-server
```
2. Create a new environment
```
conda create --name your_env_name python=3.13
conda activate your_env_name
```
3. Install dependencies
```
pip install -r requirements.txt
```
4. Run NBA mcp server on the terminal
```
mcp run nba_server.py
```
5. Add this to your `claude_desktop_config.json`, adjusting the Python path as needed:
```json
{
"mcpServers": {
"nba_mcp_server": {
"command": "/path/to/your/python",
"args": [
"/path/to/nba_server.py"
]
}
}
}
```
After adding your chosen configuration, restart Claude Desktop to load the NBA server. You'll then be able to use all the NBA data tools in your conversations with Claude.
## Technical Details
The server is built on:
- NBA API (nba_api) Python package
- MCP for API interface
- Pydantic for input validation
- Pandas for data manipulation
## License
This MCP server is available under the MIT License.
```
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
```
nba_api
langchain
langgraph
mcp[cli]
pandas
pydantic
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
# Use an official Python runtime as a parent image
FROM python:3.10-slim
# Set the working directory in the container
WORKDIR /nba-mcp-sever
# Copy the current directory contents into the container
COPY . /nba-mcp-sever
# Install any necessary dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Expose the port that your app will run on
EXPOSE 5000
# Run the server when the container launches
CMD ["python", "nba_server.py"]
# docker build -t nba_server .
# docker run -p 4000:5000 nba_server
```
--------------------------------------------------------------------------------
/nba_server.py:
--------------------------------------------------------------------------------
```python
from mcp.server.fastmcp import FastMCP
import time
import signal
import sys
from nba_api.live.nba.endpoints import scoreboard, boxscore, playbyplay
from nba_api.stats.static import players, teams
from pydantic import BaseModel, Field, field_validator, ValidationError
from typing import Optional, List, Dict, Any
from datetime import datetime, timedelta
import pandas as pd
import os
from nba_api.live.nba.endpoints import scoreboard, boxscore, playbyplay
from nba_api.stats.endpoints import commonplayerinfo, playercareerstats, scoreboardv2, teamgamelogs, leaguegamefinder, leaguestandingsv3, teamyearbyyearstats
from nba_api.stats.static import players, teams
from nba_api.stats.library.parameters import SeasonType, SeasonYear
# print(f"Python executable: {sys.executable}", file=sys.stderr)
# print(f"Python path: {sys.path}", file=sys.stderr)
print(f"Current working directory: {os.getcwd()}", file=sys.stderr)
# Handle SIGINT (Ctrl+C) gracefully
def signal_handler(sig, frame):
print("Shutting down server gracefully...")
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
# Create an MCP server with increased timeout
mcp = FastMCP(
name="nba_mcp_server",
# host="127.0.0.1",
# port=5000,
# Add this to make the server more resilient
timeout=30 # Increase timeout to 30 seconds
)
# -------------------------------------------------------------------
# 1) ScoreBoard Tool (Live Endpoint)
# -------------------------------------------------------------------
class LiveScoreBoardInput(BaseModel):
dummy_param: Optional[str] = Field(default="", description="Not used.")
@mcp.tool()
def nba_live_scoreboard(dummy_param: Optional[str] = "") -> Dict[str, Any]:
"""Fetch today's NBA scoreboard (live or latest).
This tool retrieves data from the `nba_api.live.nba.endpoints.scoreboard` endpoint. It provides
information about games happening *today* (or the most recent games if no games are live).
This includes game IDs, start times, scores, period information, and broadcast details.
**Args:**
dummy_param (str, optional): This parameter is not used. It exists for compatibility
with the MCP framework and should be left as an empty string. Defaults to "".
**Returns:**
Dict[str, Any]: A dictionary containing scoreboard data. The structure follows the
`nba_api`'s `ScoreBoard` object. Key elements include:
* "games": A list of dictionaries, one for each game. Each game dictionary contains:
* "gameId": (str) The 10-digit game ID. **Crucially, this is needed for other live tools.**
* "gameStatus": (int) A numeric representation of the game status (1 = scheduled, 2 = in progress, 3 = final).
* "gameStatusText": (str) A textual representation of the game status (e.g., "Final", "Q4 05:30", "8:00 pm ET").
* "homeTeam": (dict) Information about the home team.
* "awayTeam": (dict) Information about the away team.
* ...and many other fields.
* "gameDate": (str) - The date of the game
* "scoreboard": (dict) - Contains overall scoreboard of that date.
If an error occurs, the dictionary will contain a single "error" key with a
description of the problem.
"""
try:
sb = scoreboard.ScoreBoard()
return sb.get_dict()
except Exception as e:
return {"error": str(e)}
# -------------------------------------------------------------------
# 2) BoxScore Tool (Live Endpoint)
# -------------------------------------------------------------------
class LiveBoxScoreInput(BaseModel):
game_id: str = Field(..., description="A 10-digit NBA game ID (e.g., '0022200017').")
@mcp.tool()
def nba_live_boxscore(game_id: str) -> Dict[str, Any]:
"""Fetch the real-time box score for a given NBA game ID.
This tool retrieves live box score data from the `nba_api.live.nba.endpoints.boxscore`
endpoint. It provides detailed statistics for a *specific* game, including:
* Player statistics (points, rebounds, assists, etc.)
* Team statistics (points by quarter, totals)
* Active players
* Game officials
**Args:**
game_id (str): The 10-digit NBA game ID. This is typically obtained from
`nba_live_scoreboard`. Example: "0022300123"
**Returns:**
Dict[str, Any]: A dictionary containing the box score data. The structure follows
the `nba_api`'s `BoxScore` object. Key elements include:
* "gameId": The game ID.
* "gameStatus": Numeric game status.
* "boxScoreTraditional": (dict) - contains player and team stats.
* "teams": A list of two dictionaries (one for each team), containing:
* "teamId": The team ID.
* "teamName": The team name.
* "teamCity": The team city.
* "players": A list of dictionaries, one for each player, with detailed stats.
* ...and many other fields.
If an error occurs, the dictionary will contain a single "error" key.
"""
if not isinstance(game_id, str):
game_id = str(game_id)
try:
bs = boxscore.BoxScore(game_id=game_id)
return bs.get_dict()
except Exception as e:
return {"error": str(e)}
# -------------------------------------------------------------------
# 3) PlayByPlay Tool (Live Endpoint)
# -------------------------------------------------------------------
class LivePlayByPlayInput(BaseModel):
game_id: str = Field(..., description="A 10-digit NBA game ID.")
@mcp.tool()
def nba_live_play_by_play(game_id: str) -> Dict[str, Any]:
"""Retrieve the live play-by-play actions for a specific NBA game ID.
This tool retrieves data from the `nba_api.live.nba.endpoints.playbyplay` endpoint.
It provides a chronological list of events that occur during a game, including:
* Scoring plays
* Fouls
* Timeouts
* Substitutions
* Descriptions of each play
**Args:**
game_id (str): The 10-digit NBA game ID. Obtain this from `nba_live_scoreboard`.
Example: "0022300123"
**Returns:**
Dict[str, Any]: A dictionary containing the play-by-play data. The structure follows
the `nba_api`'s `PlayByPlay` object. Key elements include:
* "gameId": The game ID.
* "actions": A list of dictionaries, one for each play. Each play dictionary contains:
* "actionNumber": A sequential number for the play.
* "clock": The game clock time when the play occurred.
* "period": The quarter/overtime period.
* "teamId": The ID of the team involved in the play (if applicable).
* "personId": The ID of the player involved in the play (if applicable).
* "description": A textual description of the play.
* ...and many other fields.
If an error occurs, the dictionary will contain a single "error" key.
"""
if not isinstance(game_id, str):
game_id = str(game_id)
try:
pbp = playbyplay.PlayByPlay(game_id=game_id)
return pbp.get_dict()
except Exception as e:
return {"error": str(e)}
# -------------------------------------------------------------------
# 4) CommonPlayerInfo Tool (Stats Endpoint)
# -------------------------------------------------------------------
class CommonPlayerInfoInput(BaseModel):
player_id: str = Field(..., description="NBA player ID (e.g., '2544').")
@mcp.tool()
def nba_common_player_info(player_id: str) -> Dict[str, Any]:
"""Retrieve basic information about a player.
This tool retrieves data from the `nba_api.stats.endpoints.commonplayerinfo` endpoint.
It provides biographical and basic information about a specific NBA player, including:
* Player ID
* Full Name
* Birthdate
* Height
* Weight
* Current Team
* Jersey Number
* Position
* Draft information
* College
**Args:**
player_id (str): The NBA player ID. This is typically a number, like "2544" (LeBron James).
You can use `nba_search_players` (not yet documented here, but in your original code) to
find a player ID by name.
**Returns:**
Dict[str, Any]: A dictionary containing player information. The structure follows the
`nba_api`'s `CommonPlayerInfo` object. Key elements include:
* "CommonPlayerInfo": A list containing a single dictionary with player details.
* "personId": The player ID.
* "displayFirstLast": The player's full name.
* "birthdate": The player's birthdate.
* "height": Player height.
* "weight": Player weight.
* "teamId": The ID of the player's current team.
* "teamName": The name of the player's current team.
* ... and many other fields
* "ResultSets": (list) - Contains the results in sets.
If an error occurs, the dictionary will contain a single "error" key.
"""
if not isinstance(player_id, str):
player_id = str(player_id)
try:
info = commonplayerinfo.CommonPlayerInfo(player_id=player_id)
return info.get_dict()
except Exception as e:
return {"error": str(e)}
# -------------------------------------------------------------------
# 5) PlayerCareerStats Tool (Stats Endpoint)
# -------------------------------------------------------------------
class PlayerCareerStatsInput(BaseModel):
player_id: str = Field(..., description="NBA player ID.")
per_mode: Optional[str] = Field(default="PerGame", description="One of 'Totals', 'PerGame', 'Per36'.")
@mcp.tool()
def nba_player_career_stats(player_id: str, per_mode: str = "PerGame") -> Dict[str, Any]:
"""Obtain an NBA player's career statistics.
This tool retrieves career statistics (regular season, playoffs, and potentially All-Star games)
from the `nba_api.stats.endpoints.playercareerstats` endpoint. It provides aggregated
statistics for a player across their entire career or specific seasons.
**Args:**
player_id (str): The NBA player ID (e.g., "2544").
per_mode (str, optional): Determines the statistical aggregation. Valid options are:
* "PerGame": Stats averaged per game played.
* "Totals": Total career statistics.
* "Per36": Stats per 36 minutes played.
Defaults to "PerGame".
**Returns:**
Dict[str, Any]: A dictionary containing the player's career statistics. The structure
follows the `nba_api`'s `PlayerCareerStats` object. Key elements include:
* "SeasonTotalsRegularSeason": A list of dictionaries, one for each season the
player played in the regular season. Each dictionary contains aggregated stats
for that season (e.g., games played, points, rebounds, assists, etc.).
* "CareerTotalsRegularSeason": A list containing a single dictionary with the
player's total career regular season stats.
* "SeasonTotalsPostSeason", "CareerTotalsPostSeason": Similar data for playoff games.
* "SeasonTotalsAllStarSeason", "CareerTotalsAllStarSeason": Similar data for All-Star games.
* "resultSets" (list): Contains different sets of career stats.
If an error occurs, the dictionary will contain a single "error" key.
"""
# Convert player_id to a string if it's not already.
if not isinstance(player_id, str):
player_id = str(player_id)
try:
career = playercareerstats.PlayerCareerStats(player_id=player_id, per_mode36=per_mode)
return career.get_dict()
except Exception as e:
return {"error": str(e)}
# -------------------------------------------------------------------
# 8) List All Active Players
# -------------------------------------------------------------------
class ListActivePlayersInput(BaseModel):
# no arguments needed
dummy: str = "unused"
@mcp.tool()
def nba_list_active_players(dummy: str = "") -> List[Dict[str, Any]]:
"""Return a list of all currently active NBA players.
This tool uses the `nba_api.stats.static.players` module to retrieve a list of all players
marked as active in the NBA API's database.
**Args:**
dummy (str, optional): This parameter is not used. It is included for compatibility with the MCP framework.
**Returns:**
List[Dict[str, Any]]: A list of dictionaries, where each dictionary represents an active player.
Each player dictionary contains:
* "id": (int) The player's ID.
* "full_name": (str) The player's full name.
* "first_name": (str) The player's first name.
* "last_name": (str) The player's last name.
* "is_active": (bool) Always True for this function.
If there's an issue, a list containing a dictionary with an "error" key is returned.
"""
try:
all_active = players.get_active_players()
return all_active
except Exception as e:
return [{"error": str(e)}]
# -------------------------------------------------------------------
# 9) List Today’s Games (Stats vs. Live)
# -------------------------------------------------------------------
class TodayGamesInput(BaseModel):
game_date: str = Field(..., description="A date in 'YYYY-MM-DD' format.")
league_id: str = Field(default="00", description="League ID (default=00 for NBA).")
@mcp.tool()
def nba_list_todays_games(game_date: str, league_id: str = "00") -> Dict[str, Any]:
"""Returns scoreboard data from stats.nba.com for a given date.
This tool retrieves game information for a specific date from the
`nba_api.stats.endpoints.scoreboardv2` endpoint. It's similar to `nba_live_scoreboard`,
but it allows you to query for games on *any* date (past, present, or future), not just
today's games.
**Args:**
game_date (str): The date for which to retrieve game information, in "YYYY-MM-DD" format.
Example: "2023-12-25"
league_id (str, optional): The league ID. "00" represents the NBA. Defaults to "00".
**Returns:**
Dict[str, Any]: A dictionary containing game data for the specified date. The structure
follows the `nba_api`'s `ScoreboardV2` object (but is normalized). Key elements:
* "GameHeader": A list of dictionaries, one for each game, containing:
* "GAME_DATE_EST": The game date in YYYY-MM-DD format.
* "GAME_ID": The 10-digit game ID. **Important for other tools.**
* "HOME_TEAM_ID": The ID of the home team.
* "VISITOR_TEAM_ID": The ID of the away team.
* "GAME_STATUS_TEXT": Textual game status (e.g., "Final", "8:00 PM ET").
* ...and other fields.
* "LineScore": A list of dictionaries with detailed scoring information for each team
in each game.
* "SeriesStandings": A list of series standings
* "LastMeeting": A list of last meeting
* ... other fields
If an error occurs, the dictionary will contain a single "error" key.
"""
try:
sb = scoreboardv2.ScoreboardV2(game_date=game_date, league_id=league_id)
return sb.get_normalized_dict()
except Exception as e:
return {"error": str(e)}
# # -------------------------------------------------------------------
# # 10) TeamGameLogsTool: Fetch a Team's Game Logs
# # -------------------------------------------------------------------
# class TeamGameLogsInput(BaseModel):
# team_id: str = Field(..., description="The NBA Team ID.")
# season: str = Field(default="2022-23", description="Season in 'YYYY-YY' format.")
# season_type: str = Field(default="Regular Season", description="'Regular Season', 'Playoffs', etc.")
# @mcp.tool()
# def nba_team_game_logs(team_id: str, season: str, season_type: str) -> List[Dict[str, Any]]:
# """Fetch a list of all games for a given Team ID in a specified season."""
# try:
# logs = teamgamelogs.TeamGameLogs(team_id_nullable=team_id, season_nullable=season, season_type_nullable=season_type)
# df = logs.get_data_frames()[0]
# selected_columns = ["TEAM_ID", "GAME_ID", "GAME_DATE", "MATCHUP", "WL"]
# partial_df = df[selected_columns]
# return partial_df.to_dict("records")
# except Exception as e:
# return [{"error": str(e)}]
# -------------------------------------------------------------------
# 11) team_game_logs_by_name_tool: Fetch a Team's Game Logs by Name
# -------------------------------------------------------------------
class TeamGameLogsByNameInput(BaseModel):
team_name: str = Field(..., description="Partial or full NBA team name.")
season: str = Field(default="2022-23", description="Season in 'YYYY-YY' format.")
season_type: str = Field(default="Regular Season", description="'Regular Season', 'Playoffs', etc.")
@mcp.tool()
def nba_team_game_logs_by_name(team_name: str, season: str, season_type: str) -> List[Dict[str, Any]]:
"""Fetch a team's game logs by providing the team name.
This tool retrieves a team's game log (list of games) for a given season and season type,
using the team's *name* as input. This avoids needing to know the team's numeric ID. It uses
the `nba_api.stats.static.teams` module to find the team and then the
`nba_api.stats.endpoints.teamgamelogs` endpoint to get the game log.
**Args:**
team_name (str): The full or partial name of the NBA team (e.g., "Lakers", "Los Angeles Lakers").
season (str): The season in "YYYY-YY" format (e.g., "2023-24").
season_type (str): The type of season. Valid options are:
* "Regular Season"
* "Playoffs"
* "Pre Season"
* "All Star"
**Returns:**
List[Dict[str, Any]]: A list of dictionaries, where each dictionary represents a game in the
team's game log. The selected columns are:
* "TEAM_ID": The team's numeric ID.
* "GAME_ID": The 10-digit game ID.
* "GAME_DATE": The date of the game.
* "MATCHUP": A string showing the matchup (e.g., "LAL vs. GSW").
* "WL": The game result ("W" for win, "L" for loss, or None if the game hasn't been played).
If no team is found or an error occurs, the list will contain a single dictionary
with an "error" key.
"""
try:
found = teams.find_teams_by_full_name(team_name)
if not found:
return [{"error": f"No NBA team found matching name '{team_name}'."}]
best_match = found[0]
team_id = best_match["id"]
logs = teamgamelogs.TeamGameLogs(team_id_nullable=str(team_id), season_nullable=season, season_type_nullable=season_type)
df = logs.get_data_frames()[0]
columns_we_want = ["TEAM_ID", "GAME_ID", "GAME_DATE", "MATCHUP", "WL"]
partial_df = df[columns_we_want]
return partial_df.to_dict("records")
except Exception as e:
return [{"error": str(e)}]
# -------------------------------------------------------------------
# 12) nba_fetch_game_results: Fetch Game Results for a Team
# -------------------------------------------------------------------
class GameResultsInput(BaseModel):
team_id: str = Field(..., description="A valid NBA team ID.")
dates: List[str] = Field(..., description="A list of dates in 'YYYY-MM-DD' format.", min_items=1)
@mcp.tool()
def nba_fetch_game_results(team_id: str, dates: List[str]) -> List[Dict[str, Any]]:
"""Fetch game results for a given NBA team ID and date range.
This tool retrieves game results and statistics for a specified team within a given range of dates.
It leverages the `nba_api.stats.endpoints.leaguegamefinder` to efficiently find games and then filters
the results to include only the dates requested.
**Args:**
team_id (str): The NBA team ID (e.g., "1610612744" for the Golden State Warriors).
dates (List[str]): A list of dates in "YYYY-MM-DD" format, representing the date range for which
to fetch game results. The order of dates does not matter; the function will
automatically determine the start and end dates. Must contain at least one date.
**Returns:**
List[Dict[str, Any]]: A list of dictionaries, where each dictionary represents a game played by
the specified team within the provided date range. Includes all columns returned by
the `nba_api`'s `LeagueGameFinder`.
If an error occurs or no games are found, a list with a single dictionary containing an "error"
key is returned.
"""
# Convert player_id to a string if it's not already.
if not isinstance(team_id, str):
team_id = str(team_id)
try:
date_objects = [datetime.strptime(date, '%Y-%m-%d') for date in dates]
gamefinder = leaguegamefinder.LeagueGameFinder(
team_id_nullable=team_id,
season_type_nullable=SeasonType.regular,
date_from_nullable=min(date_objects).strftime('%m/%d/%Y'),
date_to_nullable=max(date_objects).strftime('%m/%d/%Y')
)
games = gamefinder.get_data_frames()[0]
games['GAME_DATE'] = pd.to_datetime(games['GAME_DATE'])
start_date = min(date_objects)
end_date = max(date_objects)
all_dates = []
current_date = start_date
while current_date <= end_date:
all_dates.append(current_date)
current_date += timedelta(days=1)
games = games[games['GAME_DATE'].dt.date.isin([d.date() for d in all_dates])]
return games.to_dict('records')
except Exception as e:
return {"error": str(e)}
# -------------------------------------------------------------------------
# nba_team_standings: Retrieve NBA Team Standings
# -------------------------------------------------------------------------
class LeagueStandingsInput(BaseModel):
season: str = Field(default=SeasonYear.default, description="The NBA season (e.g., '2023-24').")
season_type: str = Field(default="Regular Season", description="The season type (e.g., 'Regular Season').")
@mcp.tool()
def nba_team_standings(season: str = SeasonYear.default, season_type: str = "Regular Season") -> List[Dict[str, Any]]:
"""Fetch the NBA team standings for a given season and season type.
Retrieves team standings data from `nba_api.stats.endpoints.leaguestandingsv3`. This includes
wins, losses, win percentage, conference and division rankings, and other relevant information.
**Args:**
season (str, optional): The NBA season in "YYYY-YY" format (e.g., "2023-24"). Defaults to the
current season as defined by `nba_api.stats.library.parameters.SeasonYear.default`.
season_type (str, optional): The type of season. Valid options include:
* "Regular Season"
* "Playoffs"
* "Pre Season"
* "All Star"
Defaults to "Regular Season".
**Returns:**
List[Dict[str, Any]]: A list of dictionaries, each representing a team's standing and
associated statistics. The structure is based on the `nba_api`'s `LeagueStandingsV3`
data frame output. Includes fields like:
* "TeamID": The team's ID.
* "TeamCity": The team's city.
* "TeamName": The team's name.
* "Conference": The team's conference (e.g., "East", "West").
* "ConferenceRecord": The team's record within its conference.
* "PlayoffRank": The team's rank for playoff seeding within its conference.
* "WINS": Number of wins.
* "LOSSES": Number of losses.
* "Win_PCT": Win percentage.
* ...and many other statistical fields.
If an error occurs, returns a list containing a single dictionary with an "error" key.
"""
try:
standings = leaguestandingsv3.LeagueStandingsV3(season=season, season_type=season_type)
return standings.get_data_frames()[0].to_dict('records')
except Exception as e:
return [{"error": str(e)}]
# -------------------------------------------------------------------------
# nba_team_stats_by_name: Retrieve NBA Team Stats by Team Name
# -------------------------------------------------------------------------
class TeamStatsInput(BaseModel):
team_name: str = Field(..., description="The NBA team name (e.g., 'Cleveland Cavaliers').")
season_type: str = Field(default="Regular Season", description="The season type (e.g., 'Regular Season').")
per_mode: str = Field(default="PerGame", description="Options are Totals, PerGame, Per48, Per40, etc.")
@field_validator("team_name")
def validate_team_name(cls, value):
found_teams = teams.find_teams_by_full_name(value)
if not found_teams:
raise ValueError(f"No NBA team found with the name '{value}'.")
return value
@mcp.tool()
def nba_team_stats_by_name(team_name: str, season_type: str = "Regular Season", per_mode: str = "PerGame") -> List[Dict[str, Any]]:
"""Fetches NBA team statistics from stats.nba.com using the team name.
This tool retrieves detailed team statistics for a specified team, season type, and aggregation
method. It first uses `nba_api.stats.static.teams` to find the team ID based on the provided
name, then uses `nba_api.stats.endpoints.teamyearbyyearstats` to get the statistics.
**Args:**
team_name (str): The full or partial name of the NBA team (e.g., "Celtics", "Boston Celtics").
This argument is validated to ensure a team with provided name exists.
season_type (str, optional): The type of season. Valid options are:
* "Regular Season"
* "Playoffs"
* "Pre Season"
* "All Star"
Defaults to "Regular Season".
per_mode (str, optional): Determines how the statistics are aggregated. Valid options include:
* "Totals": Total season statistics.
* "PerGame": Stats averaged per game.
* "Per48": Stats per 48 minutes.
* "Per40": Stats per 40 minutes.
* "Per36" : Stats per 36 minutes
* ...and other per-minute options.
Defaults to "PerGame".
**Returns:**
List[Dict[str, Any]]: A list of dictionaries. If the data for provided `team_name` is not found
or is empty, this will contain a single error dictionary. Otherwise, each dictionary represents
a season for the team and includes a wide range of statistics, based on the
`nba_api`'s `TeamYearByYearStats` data frame output. Some key fields include:
* "TEAM_ID": The team's ID.
* "TEAM_CITY": The team's city.
* "TEAM_NAME": The team's name.
* "YEAR": The year of the season.
* "WINS":, "LOSSES":, "Win_PCT": Basic win-loss information.
* Numerous statistical fields (e.g., "PTS", "REB", "AST", "STL", "BLK", etc.)
"""
try:
found_teams = teams.find_teams_by_full_name(team_name)
if not found_teams:
return [{"error": f"No NBA team found with the name '{team_name}'."}]
team_id = found_teams[0]['id']
team_stats = teamyearbyyearstats.TeamYearByYearStats(team_id=team_id, per_mode_simple=per_mode, season_type_all_star=season_type)
team_stats_data = team_stats.get_data_frames()[0]
if team_stats_data.empty:
return [{"error": f"No stats found for {team_name}, season_type {season_type}."}]
return team_stats_data.to_dict('records')
except Exception as e:
return [{"error": str(e)}]
# -------------------------------------------------------------------
# 15) nba_all_teams_stats: Retrieve NBA Team Stats for All Teams
# -------------------------------------------------------------------
class AllTeamsStatsInput(BaseModel):
years: List[str] = Field(default=["2023"], description="A list of NBA season years (e.g., ['2022', '2023']).")
season_type: str = Field(default="Regular Season", description="The season type (e.g., 'Regular Season').")
@field_validator("years")
def validate_years(cls, value):
for year in value:
if not year.isdigit() or len(year) != 4:
raise ValueError("Each year must be a 4-digit string (e.g., '2023')")
return value
@mcp.tool()
def nba_all_teams_stats(years: List[str] = ["2023"], season_type: str = "Regular Season") -> List[Dict[str, Any]]:
"""Fetch the NBA team statistics for all teams for a given list of season years and a season type.
This tool retrieves comprehensive team statistics for *all* NBA teams across one or more seasons.
It uses the `nba_api.stats.endpoints.leaguestandingsv3` endpoint to gather the data. This is
useful for comparing teams or tracking league-wide trends over time.
**Args:**
years (List[str], optional): A list of NBA season years in "YYYY" format (e.g., ["2022", "2023"]).
Defaults to ["2023"]. Each year must be a 4-digit string.
season_type (str, optional): The type of season. Valid options are:
* "Regular Season"
* "Playoffs"
* "Pre Season"
* "All Star"
Defaults to "Regular Season".
**Returns:**
List[Dict[str, Any]]: A list of dictionaries, where each dictionary represents a team's
statistics *for a specific season*. The data includes a wide range of statistics,
similar to `nba_team_standings`, but aggregated for all teams. Key fields:
* "TeamID": The team's ID.
* "TeamCity": The team's city.
* "TeamName": The team's name.
* "Conference": The team's conference.
* "ConferenceRecord": Record within the conference.
* "WINS", "LOSSES", "Win_PCT": Win-loss statistics.
* "Season": The year of the season (taken from the input `years`).
* ...and many other statistical fields.
If no data available, returns an error message.
"""
all_seasons_stats = []
try:
for year in years:
team_stats = leaguestandingsv3.LeagueStandingsV3(
season=year,
season_type=season_type,
league_id='00',
)
team_stats_data = team_stats.get_data_frames()[0]
if team_stats_data.empty:
all_seasons_stats.append({"error": f"No stats found for season {year}, season_type {season_type}."})
continue
for col in ['PlayoffRank', 'ConferenceRank', 'DivisionRank', 'WINS', 'LOSSES', 'ConferenceGamesBack', 'DivisionGamesBack']:
if col in team_stats_data.columns:
try:
team_stats_data[col] = pd.to_numeric(team_stats_data[col], errors='coerce')
except (ValueError, TypeError):
pass
team_stats_data['Season'] = year
all_seasons_stats.extend(team_stats_data.to_dict('records'))
return all_seasons_stats
except Exception as e:
return [{"error": str(e)}]
# -------------------------------------------------------------------
# 16) nba_player_game_logs: Retrieve NBA Player Game Logs and stats
# -------------------------------------------------------------------
@mcp.tool()
def nba_player_game_logs(player_id: str, date_range: List[str], season_type: str = "Regular Season") -> List[Dict[str, Any]]:
"""Obtain an NBA player's game statistics for dates within a specified date range.
This tool retrieves individual game statistics for a given player within a specific date range. It uses
the `nba_api.stats.endpoints.leaguegamefinder` to find games played by the player and filters the
results to include only games within the specified dates.
**Args:**
player_id (str): The NBA player ID (e.g., "2544" for LeBron James).
date_range (List[str]): A list containing two strings representing the start and end dates
of the desired range, in "YYYY-MM-DD" format. Example: ["2024-01-01", "2024-01-31"]
season_type (str, optional): The type of season. Valid options are:
* "Regular Season"
* "Playoffs"
* "Pre Season"
* "All Star"
Defaults to "Regular Season".
**Returns:**
List[Dict[str, Any]]: A list of dictionaries, where each dictionary represents a game played
by the specified player within the provided date range. Includes all columns returned
by the underlying `nba_api` call, including detailed game statistics. Key fields:
* "PLAYER_ID": The player's ID
* "PLAYER_NAME": The player's name.
* "TEAM_ID": The ID of the player's team.
* "TEAM_ABBREVIATION": Team abbreviation.
* "GAME_ID": The 10-digit Game ID.
* "GAME_DATE": The date of the game.
* "MATCHUP": Text showing the matchup.
* "WL": Win ('W') or Loss ('L')
* "MIN": Minutes played.
* ...and many other statistical fields (PTS, REB, AST, etc.).
If no games are found or an error occurs, returns a list containing a single dictionary
with an "error" key.
"""
# Convert player_id to a string if it's not already.
if not isinstance(player_id, str):
player_id = str(player_id)
try:
start_date_str, end_date_str = date_range
start_date = datetime.strptime(start_date_str, '%Y-%m-%d')
end_date = datetime.strptime(end_date_str, '%Y-%m-%d')
gamefinder = leaguegamefinder.LeagueGameFinder(
player_id_nullable=player_id,
season_type_nullable=season_type,
date_from_nullable=start_date.strftime('%m/%d/%Y'),
date_to_nullable=end_date.strftime('%m/%d/%Y')
)
games = gamefinder.get_data_frames()[0]
games['GAME_DATE'] = pd.to_datetime(games['GAME_DATE'])
all_dates = []
current_date = start_date
while current_date <= end_date:
all_dates.append(current_date)
current_date += timedelta(days=1)
games = games[games['GAME_DATE'].dt.date.isin([d.date() for d in all_dates])]
return games.to_dict('records')
except Exception as e:
return [{"error": str(e)}]
if __name__ == "__main__":
try:
print("Starting MCP server 'nba_mcp_server' on 127.0.0.1:5000")
# Use this approach to keep the server running
mcp.run()
except Exception as e:
print(f"Error: {e}")
# Sleep before exiting to give time for error logs
time.sleep(5)
```