# 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) ```