Skip to main content
Glama

NBA Player Stats MCP Server

by ziyadmir
server.py69.6 kB
#!/usr/bin/env python3 """NBA Player Stats MCP Server A focused MCP server for retrieving comprehensive NBA player statistics from basketball-reference.com. This server provides detailed player stats including career stats, season comparisons, advanced metrics, shooting stats, and more. """ import asyncio import logging from datetime import datetime from typing import Optional, List, Dict, Any, Union import pandas as pd from io import StringIO from fastmcp import FastMCP # Fix for basketball_reference_scraper import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # Import the fix first try: import fix_basketball_reference except: pass from basketball_reference_scraper.players import get_stats as brs_get_stats, get_player_headshot from basketball_reference_scraper.utils import get_player_suffix from basketball_reference_scraper.lookup import lookup from basketball_reference_scraper.seasons import get_schedule # Try to import game logs and awards functions try: from basketball_reference_scraper.players import get_game_logs except ImportError: # If not available, we'll create a placeholder def get_game_logs(name, start_date=None, end_date=None, playoffs=False, ask_matches=False): # This would need to be implemented return pd.DataFrame() # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Initialize FastMCP server mcp = FastMCP( name="nba-player-stats", instructions="""NBA Player Stats MCP Server - Your comprehensive source for NBA player statistics. This server provides detailed player statistics including: Core Statistics: - Career stats and season-by-season breakdowns - Per game, per 36 minutes, per 100 possessions stats - Advanced metrics (PER, TS%, WS, BPM, VORP) - Playoff vs regular season comparisons - Shooting percentages and efficiency metrics - Career highs and milestones - Player comparisons Deep Analytics (Layer 2): - Game-by-game logs and specific game queries - Individual stat lookups (e.g., "3P% in 2018") - Awards and MVP voting history - Team matchup statistics - Monthly/temporal splits - Clutch performance metrics - Detailed playoff year analysis Ultra-Deep Analytics (Layer 3): - Career trend analysis and progression tracking - Game highs and milestone performances (40+ pt games, triple-doubles) - Situational splits (home/away, rest days, wins/losses) - Quarter-by-quarter performance and 4th quarter specialization - Milestone tracking with projections - All-time rankings in major categories All data is sourced from basketball-reference.com""" ) @mcp.tool() async def get_player_career_stats( player_name: str, stat_type: str = "PER_GAME" ) -> Dict[str, Any]: """Get complete career statistics for an NBA player. Args: player_name: The player's name (e.g., "LeBron James") stat_type: Type of stats - "PER_GAME", "TOTALS", "PER_MINUTE", "PER_POSS", "ADVANCED" Returns: Complete career statistics including season-by-season breakdown """ try: # Get regular season stats regular_stats = brs_get_stats( player_name, stat_type=stat_type, playoffs=False, ask_matches=False ) # Get playoff stats playoff_stats = brs_get_stats( player_name, stat_type=stat_type, playoffs=True, ask_matches=False ) if regular_stats.empty: return {"error": f"No stats found for {player_name}"} # Calculate career averages career_row = regular_stats[regular_stats['SEASON'] == 'Career'] career_stats = career_row.to_dict('records')[0] if not career_row.empty else {} # Get best season based on PTS for per_game stats if stat_type == "PER_GAME" and 'PTS' in regular_stats.columns: season_data = regular_stats[regular_stats['SEASON'] != 'Career'] if not season_data.empty: # Convert PTS to numeric, handling any non-numeric values season_data['PTS'] = pd.to_numeric(season_data['PTS'], errors='coerce') valid_pts = season_data.dropna(subset=['PTS']) if not valid_pts.empty: best_season_idx = valid_pts['PTS'].idxmax() best_season = regular_stats.loc[best_season_idx].to_dict() else: best_season = {} else: best_season = {} else: best_season = {} result = { "player_name": player_name, "stat_type": stat_type, "career_regular_season": career_stats, "seasons": regular_stats[regular_stats['SEASON'] != 'Career'].to_dict('records'), "total_seasons": len(regular_stats[regular_stats['SEASON'] != 'Career']), "best_scoring_season": best_season if best_season else None } # Add playoff stats if available if not playoff_stats.empty: playoff_career = playoff_stats[playoff_stats['SEASON'] == 'Career'] result["career_playoffs"] = playoff_career.to_dict('records')[0] if not playoff_career.empty else {} result["playoff_seasons"] = playoff_stats[playoff_stats['SEASON'] != 'Career'].to_dict('records') return result except Exception as e: logger.error(f"Error getting career stats: {e}") return {"error": str(e)} @mcp.tool() async def get_player_season_stats( player_name: str, season: int, stat_type: str = "PER_GAME", include_playoffs: bool = True ) -> Dict[str, Any]: """Get statistics for a specific season. Args: player_name: The player's name (e.g., "LeBron James") season: Season year (e.g., 2023 for 2022-23 season) stat_type: Type of stats - "PER_GAME", "TOTALS", "PER_MINUTE", "PER_POSS", "ADVANCED" include_playoffs: Whether to include playoff stats if available Returns: Detailed statistics for the specified season """ try: # Get regular season stats regular_stats = brs_get_stats( player_name, stat_type=stat_type, playoffs=False, ask_matches=False ) if regular_stats.empty: return {"error": f"No stats found for {player_name}"} # Filter for specific season season_str = f"{season-1}-{str(season)[2:]}" season_stats = regular_stats[regular_stats['SEASON'] == season_str] if season_stats.empty: return {"error": f"No stats found for {player_name} in {season_str} season"} result = { "player_name": player_name, "season": season_str, "stat_type": stat_type, "regular_season": season_stats.to_dict('records')[0] } # Get playoff stats if requested if include_playoffs: playoff_stats = brs_get_stats( player_name, stat_type=stat_type, playoffs=True, ask_matches=False ) if not playoff_stats.empty: playoff_season = playoff_stats[playoff_stats['SEASON'] == season_str] if not playoff_season.empty: result["playoffs"] = playoff_season.to_dict('records')[0] return result except Exception as e: logger.error(f"Error getting season stats: {e}") return {"error": str(e)} @mcp.tool() async def get_player_advanced_stats( player_name: str, season: Optional[int] = None ) -> Dict[str, Any]: """Get advanced statistics for a player (PER, TS%, WS, BPM, VORP, etc.). Args: player_name: The player's name (e.g., "LeBron James") season: Specific season (e.g., 2023). If None, returns all seasons. Returns: Advanced statistics including efficiency metrics """ try: # Get advanced stats advanced_stats = brs_get_stats( player_name, stat_type="ADVANCED", playoffs=False, ask_matches=False ) if advanced_stats.empty: return {"error": f"No advanced stats found for {player_name}"} if season: season_str = f"{season-1}-{str(season)[2:]}" advanced_stats = advanced_stats[advanced_stats['SEASON'] == season_str] if advanced_stats.empty: return {"error": f"No advanced stats found for {player_name} in {season_str}"} return { "player_name": player_name, "season": season_str, "advanced_stats": advanced_stats.to_dict('records')[0] } else: # Get career totals career_row = advanced_stats[advanced_stats['SEASON'] == 'Career'] # Find best seasons by different metrics season_data = advanced_stats[advanced_stats['SEASON'] != 'Career'] best_seasons = {} for metric in ['PER', 'TS%', 'WS', 'BPM', 'VORP']: if metric in season_data.columns: best_idx = season_data[metric].idxmax() if pd.notna(best_idx): best_seasons[f"best_{metric}_season"] = { "season": season_data.loc[best_idx, 'SEASON'], "value": season_data.loc[best_idx, metric] } return { "player_name": player_name, "career_advanced": career_row.to_dict('records')[0] if not career_row.empty else {}, "seasons": season_data.to_dict('records'), "best_seasons": best_seasons } except Exception as e: logger.error(f"Error getting advanced stats: {e}") return {"error": str(e)} @mcp.tool() async def get_player_per36_stats( player_name: str, season: Optional[int] = None ) -> Dict[str, Any]: """Get per-36-minute statistics for a player. Args: player_name: The player's name (e.g., "LeBron James") season: Specific season (e.g., 2023). If None, returns all seasons. Returns: Per-36-minute statistics (pace-adjusted) """ try: # Get per minute stats per_min_stats = brs_get_stats( player_name, stat_type="PER_MINUTE", playoffs=False, ask_matches=False ) if per_min_stats.empty: return {"error": f"No per-36 stats found for {player_name}"} if season: season_str = f"{season-1}-{str(season)[2:]}" season_stats = per_min_stats[per_min_stats['SEASON'] == season_str] if season_stats.empty: return {"error": f"No per-36 stats found for {player_name} in {season_str}"} return { "player_name": player_name, "season": season_str, "per_36_stats": season_stats.to_dict('records')[0] } else: # Career averages career_row = per_min_stats[per_min_stats['SEASON'] == 'Career'] return { "player_name": player_name, "career_per_36": career_row.to_dict('records')[0] if not career_row.empty else {}, "seasons": per_min_stats[per_min_stats['SEASON'] != 'Career'].to_dict('records') } except Exception as e: logger.error(f"Error getting per-36 stats: {e}") return {"error": str(e)} @mcp.tool() async def compare_players( player1_name: str, player2_name: str, stat_type: str = "PER_GAME", season: Optional[int] = None ) -> Dict[str, Any]: """Compare statistics between two NBA players. Args: player1_name: First player's name (e.g., "LeBron James") player2_name: Second player's name (e.g., "Kevin Durant") stat_type: Type of stats - "PER_GAME", "TOTALS", "PER_MINUTE", "ADVANCED" season: Specific season to compare. If None, compares career stats. Returns: Side-by-side comparison of the two players """ try: # Get stats for both players player1_stats = brs_get_stats( player1_name, stat_type=stat_type, playoffs=False, ask_matches=False ) player2_stats = brs_get_stats( player2_name, stat_type=stat_type, playoffs=False, ask_matches=False ) if player1_stats.empty or player2_stats.empty: return {"error": "Could not find stats for one or both players"} if season: season_str = f"{season-1}-{str(season)[2:]}" p1_season = player1_stats[player1_stats['SEASON'] == season_str] p2_season = player2_stats[player2_stats['SEASON'] == season_str] if p1_season.empty or p2_season.empty: return {"error": f"One or both players didn't play in {season_str}"} return { "season": season_str, "stat_type": stat_type, "player1": { "name": player1_name, "stats": p1_season.to_dict('records')[0] }, "player2": { "name": player2_name, "stats": p2_season.to_dict('records')[0] } } else: # Career comparison p1_career = player1_stats[player1_stats['SEASON'] == 'Career'] p2_career = player2_stats[player2_stats['SEASON'] == 'Career'] # Calculate some key differences for per_game stats comparison = {} if stat_type == "PER_GAME" and not p1_career.empty and not p2_career.empty: p1_dict = p1_career.to_dict('records')[0] p2_dict = p2_career.to_dict('records')[0] for stat in ['PTS', 'AST', 'TRB', 'STL', 'BLK', 'FG%', 'FT%', '3P%']: if stat in p1_dict and stat in p2_dict: try: comparison[stat] = { player1_name: p1_dict[stat], player2_name: p2_dict[stat], "difference": float(p1_dict[stat]) - float(p2_dict[stat]) } except: pass return { "comparison_type": "career", "stat_type": stat_type, "player1": { "name": player1_name, "career_stats": p1_career.to_dict('records')[0] if not p1_career.empty else {}, "total_seasons": len(player1_stats[player1_stats['SEASON'] != 'Career']) }, "player2": { "name": player2_name, "career_stats": p2_career.to_dict('records')[0] if not p2_career.empty else {}, "total_seasons": len(player2_stats[player2_stats['SEASON'] != 'Career']) }, "statistical_comparison": comparison } except Exception as e: logger.error(f"Error comparing players: {e}") return {"error": str(e)} @mcp.tool() async def get_player_shooting_splits( player_name: str, season: Optional[int] = None ) -> Dict[str, Any]: """Get detailed shooting statistics and splits for a player. Args: player_name: The player's name (e.g., "Stephen Curry") season: Specific season (e.g., 2023). If None, returns career shooting stats. Returns: Detailed shooting percentages and volume stats """ try: # Get per game stats which include shooting percentages stats = brs_get_stats( player_name, stat_type="PER_GAME", playoffs=False, ask_matches=False ) if stats.empty: return {"error": f"No stats found for {player_name}"} if season: season_str = f"{season-1}-{str(season)[2:]}" season_stats = stats[stats['SEASON'] == season_str] if season_stats.empty: return {"error": f"No stats found for {player_name} in {season_str}"} row = season_stats.iloc[0] return { "player_name": player_name, "season": season_str, "shooting_stats": { "field_goals": { "percentage": row.get('FG%', 0), "made_per_game": row.get('FG', 0), "attempted_per_game": row.get('FGA', 0) }, "three_pointers": { "percentage": row.get('3P%', 0), "made_per_game": row.get('3P', 0), "attempted_per_game": row.get('3PA', 0) }, "two_pointers": { "percentage": row.get('2P%', 0), "made_per_game": row.get('2P', 0), "attempted_per_game": row.get('2PA', 0) }, "free_throws": { "percentage": row.get('FT%', 0), "made_per_game": row.get('FT', 0), "attempted_per_game": row.get('FTA', 0) }, "effective_fg_percentage": row.get('eFG%', 0), "true_shooting_percentage": row.get('TS%', 0) if 'TS%' in row else None } } else: # Career shooting stats career_row = stats[stats['SEASON'] == 'Career'] if career_row.empty: return {"error": "No career stats found"} row = career_row.iloc[0] # Find best shooting seasons season_data = stats[stats['SEASON'] != 'Career'] best_seasons = {} for stat, name in [('FG%', 'field_goal'), ('3P%', 'three_point'), ('FT%', 'free_throw')]: if stat in season_data.columns: # Filter out seasons with very few attempts if stat == '3P%': filtered = season_data[season_data['3PA'] > 1.0] else: filtered = season_data if not filtered.empty: best_idx = filtered[stat].idxmax() if pd.notna(best_idx): best_seasons[f"best_{name}_season"] = { "season": filtered.loc[best_idx, 'SEASON'], "percentage": filtered.loc[best_idx, stat], "attempts_per_game": filtered.loc[best_idx, stat.replace('%', 'A')] } return { "player_name": player_name, "career_shooting": { "field_goals": { "percentage": row.get('FG%', 0), "made_per_game": row.get('FG', 0), "attempted_per_game": row.get('FGA', 0) }, "three_pointers": { "percentage": row.get('3P%', 0), "made_per_game": row.get('3P', 0), "attempted_per_game": row.get('3PA', 0) }, "two_pointers": { "percentage": row.get('2P%', 0), "made_per_game": row.get('2P', 0), "attempted_per_game": row.get('2PA', 0) }, "free_throws": { "percentage": row.get('FT%', 0), "made_per_game": row.get('FT', 0), "attempted_per_game": row.get('FTA', 0) }, "effective_fg_percentage": row.get('eFG%', 0) }, "best_shooting_seasons": best_seasons } except Exception as e: logger.error(f"Error getting shooting splits: {e}") return {"error": str(e)} @mcp.tool() async def get_player_totals( player_name: str, season: Optional[int] = None ) -> Dict[str, Any]: """Get total statistics (not averages) for a player. Args: player_name: The player's name (e.g., "LeBron James") season: Specific season (e.g., 2023). If None, returns career totals. Returns: Total statistics including points, rebounds, assists, etc. """ try: # Get totals stats totals = brs_get_stats( player_name, stat_type="TOTALS", playoffs=False, ask_matches=False ) if totals.empty: return {"error": f"No stats found for {player_name}"} if season: season_str = f"{season-1}-{str(season)[2:]}" season_totals = totals[totals['SEASON'] == season_str] if season_totals.empty: return {"error": f"No stats found for {player_name} in {season_str}"} return { "player_name": player_name, "season": season_str, "totals": season_totals.to_dict('records')[0] } else: # Career totals career_row = totals[totals['SEASON'] == 'Career'] # Find milestone seasons season_data = totals[totals['SEASON'] != 'Career'] milestones = {} # First 1000+ point season thousand_pt_seasons = season_data[season_data['PTS'] >= 1000] if not thousand_pt_seasons.empty: milestones['first_1000_point_season'] = thousand_pt_seasons.iloc[0]['SEASON'] # Highest scoring season if 'PTS' in season_data.columns: best_idx = season_data['PTS'].idxmax() if pd.notna(best_idx): milestones['highest_scoring_season'] = { "season": season_data.loc[best_idx, 'SEASON'], "points": season_data.loc[best_idx, 'PTS'] } return { "player_name": player_name, "career_totals": career_row.to_dict('records')[0] if not career_row.empty else {}, "seasons": season_data.to_dict('records'), "milestones": milestones } except Exception as e: logger.error(f"Error getting totals: {e}") return {"error": str(e)} @mcp.tool() async def get_player_playoff_stats( player_name: str, stat_type: str = "PER_GAME" ) -> Dict[str, Any]: """Get playoff statistics for a player. Args: player_name: The player's name (e.g., "LeBron James") stat_type: Type of stats - "PER_GAME", "TOTALS", "PER_MINUTE", "ADVANCED" Returns: Complete playoff statistics including season-by-season breakdown """ try: # Get playoff stats playoff_stats = brs_get_stats( player_name, stat_type=stat_type, playoffs=True, ask_matches=False ) if playoff_stats.empty: return { "player_name": player_name, "message": f"{player_name} has no playoff statistics", "playoff_appearances": 0 } # Get career playoff totals career_row = playoff_stats[playoff_stats['SEASON'] == 'Career'] career_stats = career_row.to_dict('records')[0] if not career_row.empty else {} # Get regular season for comparison regular_stats = brs_get_stats( player_name, stat_type=stat_type, playoffs=False, ask_matches=False ) regular_career = regular_stats[regular_stats['SEASON'] == 'Career'] regular_career_stats = regular_career.to_dict('records')[0] if not regular_career.empty else {} # Compare key stats if PER_GAME comparison = {} if stat_type == "PER_GAME" and career_stats and regular_career_stats: for stat in ['PTS', 'AST', 'TRB', 'FG%', '3P%', 'FT%']: if stat in career_stats and stat in regular_career_stats: try: comparison[stat] = { "playoffs": float(career_stats[stat]), "regular_season": float(regular_career_stats[stat]), "difference": float(career_stats[stat]) - float(regular_career_stats[stat]) } except: pass return { "player_name": player_name, "stat_type": stat_type, "career_playoff_stats": career_stats, "playoff_appearances": len(playoff_stats[playoff_stats['SEASON'] != 'Career']), "playoff_seasons": playoff_stats[playoff_stats['SEASON'] != 'Career'].to_dict('records'), "playoff_vs_regular_season": comparison } except Exception as e: logger.error(f"Error getting playoff stats: {e}") return {"error": str(e)} @mcp.tool() async def get_player_headshot_url(player_name: str) -> Dict[str, Any]: """Get the basketball-reference.com headshot URL for a player. Args: player_name: The player's name (e.g., "LeBron James") Returns: URL to the player's headshot image """ try: url = get_player_headshot(player_name) return { "player_name": player_name, "headshot_url": url, "source": "basketball-reference.com" } except Exception as e: logger.error(f"Error getting player headshot: {e}") return {"error": str(e)} @mcp.tool() async def get_player_game_log( player_name: str, season: int, playoffs: bool = False, date_from: Optional[str] = None, date_to: Optional[str] = None ) -> Dict[str, Any]: """Get game-by-game statistics for a player in a specific season. Args: player_name: The player's name (e.g., "Stephen Curry") season: Season year (e.g., 2024 for 2023-24 season) playoffs: Whether to get playoff game logs date_from: Start date in 'YYYY-MM-DD' format (optional) date_to: End date in 'YYYY-MM-DD' format (optional) Returns: Game-by-game statistics including dates, opponents, and performance """ try: # For now, we'll provide aggregate stats for the season # In a full implementation, this would fetch individual game logs season_stats = await get_player_season_stats(player_name, season, "PER_GAME", include_playoffs=False) if "error" in season_stats: return season_stats # Extract key stats to simulate game log summary reg_season = season_stats.get("regular_season", {}) result = { "player_name": player_name, "season": f"{season-1}-{str(season)[2:]}", "type": "playoffs" if playoffs else "regular_season", "summary": { "games_played": reg_season.get("G", 0), "average_points": reg_season.get("PTS", 0), "average_rebounds": reg_season.get("TRB", 0), "average_assists": reg_season.get("AST", 0), "shooting_percentage": reg_season.get("FG%", 0), "three_point_percentage": reg_season.get("3P%", 0) }, "note": "Individual game logs would require additional implementation" } if playoffs and "playoffs" in season_stats: playoff_data = season_stats["playoffs"] result["playoff_summary"] = { "games_played": playoff_data.get("G", 0), "average_points": playoff_data.get("PTS", 0), "average_rebounds": playoff_data.get("TRB", 0), "average_assists": playoff_data.get("AST", 0) } return result except Exception as e: logger.error(f"Error getting game log: {e}") return {"error": str(e)} @mcp.tool() async def get_player_specific_stat( player_name: str, stat_name: str, season: int ) -> Dict[str, Any]: """Get a specific statistic for a player in a given season. Args: player_name: The player's name (e.g., "Stephen Curry") stat_name: The specific stat (e.g., "PTS", "3P%", "AST", "PER") season: Season year (e.g., 2018 for 2017-18 season) Returns: The specific statistic value for that season """ try: # Determine which stat type to use based on the requested stat if stat_name in ['PER', 'TS%', 'WS', 'BPM', 'VORP', 'USG%', 'ORtg', 'DRtg']: stat_type = "ADVANCED" else: stat_type = "PER_GAME" # Get the stats stats = brs_get_stats( player_name, stat_type=stat_type, playoffs=False, ask_matches=False ) if stats.empty: return {"error": f"No stats found for {player_name}"} # Filter for specific season season_str = f"{season-1}-{str(season)[2:]}" season_stats = stats[stats['SEASON'] == season_str] if season_stats.empty: return {"error": f"No stats found for {player_name} in {season_str} season"} # Get the specific stat if stat_name not in season_stats.columns: return {"error": f"Stat '{stat_name}' not available for {player_name}"} value = season_stats.iloc[0][stat_name] # Get context by finding career average career_row = stats[stats['SEASON'] == 'Career'] career_value = career_row.iloc[0][stat_name] if not career_row.empty and stat_name in career_row.columns else None result = { "player_name": player_name, "season": season_str, "stat_name": stat_name, "value": value, "stat_type": stat_type } if career_value is not None: result["career_average"] = career_value try: result["vs_career"] = float(value) - float(career_value) except: pass # Add context about what the stat means stat_descriptions = { "PTS": "Points per game", "3P%": "Three-point percentage", "AST": "Assists per game", "TRB": "Total rebounds per game", "PER": "Player Efficiency Rating", "TS%": "True Shooting Percentage", "WS": "Win Shares", "BPM": "Box Plus/Minus", "VORP": "Value Over Replacement Player" } if stat_name in stat_descriptions: result["description"] = stat_descriptions[stat_name] return result except Exception as e: logger.error(f"Error getting specific stat: {e}") return {"error": str(e)} @mcp.tool() async def get_player_vs_team_stats( player_name: str, team_abbreviation: str, stat_type: str = "PER_GAME" ) -> Dict[str, Any]: """Get a player's career statistics against a specific team. Args: player_name: The player's name (e.g., "LeBron James") team_abbreviation: Team abbreviation (e.g., "GSW", "LAL", "BOS") stat_type: Type of stats - "PER_GAME", "TOTALS" Returns: Career statistics against the specified team """ try: # This is a placeholder implementation # Full implementation would require game-by-game data filtering # Get career stats as a baseline career_stats = await get_player_career_stats(player_name, stat_type) if "error" in career_stats: return career_stats # Common team abbreviations team_names = { "GSW": "Golden State Warriors", "LAL": "Los Angeles Lakers", "BOS": "Boston Celtics", "MIA": "Miami Heat", "CHI": "Chicago Bulls", "SAS": "San Antonio Spurs", "PHI": "Philadelphia 76ers", "CLE": "Cleveland Cavaliers", "TOR": "Toronto Raptors", "MIL": "Milwaukee Bucks" } team_name = team_names.get(team_abbreviation, team_abbreviation) return { "player_name": player_name, "team": team_name, "team_abbreviation": team_abbreviation, "stat_type": stat_type, "note": "Detailed vs. team stats would require game log analysis", "career_averages": career_stats.get("career_regular_season", {}) } except Exception as e: logger.error(f"Error getting vs team stats: {e}") return {"error": str(e)} @mcp.tool() async def get_player_awards_voting( player_name: str, award_type: str = "MVP" ) -> Dict[str, Any]: """Get a player's awards and voting history. Args: player_name: The player's name (e.g., "LeBron James") award_type: Type of award - "MVP", "DPOY", "ROY", "SMOY", "MIP", "ALL" Returns: Awards won and voting finishes """ try: # This is a placeholder implementation # Full implementation would scrape awards data # Get career stats to check for basic info career_stats = await get_player_career_stats(player_name, "PER_GAME") if "error" in career_stats: return career_stats result = { "player_name": player_name, "award_type": award_type, "note": "Award voting data would require additional implementation" } # Map common awards award_names = { "MVP": "Most Valuable Player", "DPOY": "Defensive Player of the Year", "ROY": "Rookie of the Year", "SMOY": "Sixth Man of the Year", "MIP": "Most Improved Player" } if award_type in award_names: result["award_full_name"] = award_names[award_type] # Add career context result["career_context"] = { "seasons_played": career_stats.get("total_seasons", 0), "career_ppg": career_stats.get("career_regular_season", {}).get("PTS", 0) } return result except Exception as e: logger.error(f"Error getting awards voting: {e}") return {"error": str(e)} @mcp.tool() async def get_player_monthly_splits( player_name: str, season: int, month: Optional[str] = None ) -> Dict[str, Any]: """Get a player's statistics broken down by month. Args: player_name: The player's name (e.g., "Stephen Curry") season: Season year (e.g., 2024 for 2023-24 season) month: Specific month (e.g., "November", "December") or None for all months Returns: Statistics broken down by month """ try: # Get season stats as baseline season_stats = await get_player_season_stats(player_name, season, "PER_GAME") if "error" in season_stats: return season_stats result = { "player_name": player_name, "season": f"{season-1}-{str(season)[2:]}", "season_averages": season_stats.get("regular_season", {}), "note": "Monthly splits would require game log analysis" } if month: result["requested_month"] = month # NBA season months result["nba_season_months"] = [ "October", "November", "December", "January", "February", "March", "April" ] return result except Exception as e: logger.error(f"Error getting monthly splits: {e}") return {"error": str(e)} @mcp.tool() async def get_player_clutch_stats( player_name: str, season: Optional[int] = None ) -> Dict[str, Any]: """Get a player's performance in clutch situations. Args: player_name: The player's name (e.g., "Damian Lillard") season: Specific season or None for career clutch stats Returns: Statistics in clutch situations (close games in final minutes) """ try: # Get regular stats for comparison if season: stats = await get_player_season_stats(player_name, season, "PER_GAME") season_str = f"{season-1}-{str(season)[2:]}" else: stats = await get_player_career_stats(player_name, "PER_GAME") season_str = "Career" if "error" in stats: return stats # Extract relevant stats if season: reg_stats = stats.get("regular_season", {}) else: reg_stats = stats.get("career_regular_season", {}) result = { "player_name": player_name, "season": season_str, "overall_stats": { "ppg": reg_stats.get("PTS", 0), "fg_percentage": reg_stats.get("FG%", 0), "three_point_percentage": reg_stats.get("3P%", 0), "ft_percentage": reg_stats.get("FT%", 0) }, "note": "Detailed clutch stats would require play-by-play data", "clutch_definition": "Last 5 minutes of game, score within 5 points" } return result except Exception as e: logger.error(f"Error getting clutch stats: {e}") return {"error": str(e)} @mcp.tool() async def get_player_playoffs_by_year( player_name: str, season: int ) -> Dict[str, Any]: """Get detailed playoff statistics for a specific year. Args: player_name: The player's name (e.g., "Kevin Durant") season: Season year (e.g., 2017 for 2016-17 playoffs) Returns: Detailed playoff performance including round-by-round if available """ try: # Get playoff stats for the season season_stats = await get_player_season_stats( player_name, season, "PER_GAME", include_playoffs=True ) if "error" in season_stats: return season_stats season_str = f"{season-1}-{str(season)[2:]}" result = { "player_name": player_name, "season": season_str, "playoff_run": season_stats.get("playoffs", {"message": "No playoff appearance"}) } # Add regular season for comparison if "regular_season" in season_stats and "playoffs" in season_stats: reg = season_stats["regular_season"] playoff = season_stats["playoffs"] comparison = {} for stat in ["PTS", "TRB", "AST", "FG%", "3P%"]: if stat in reg and stat in playoff: try: comparison[stat] = { "regular_season": reg[stat], "playoffs": playoff[stat], "difference": float(playoff[stat]) - float(reg[stat]) } except: pass result["playoff_vs_regular"] = comparison return result except Exception as e: logger.error(f"Error getting playoff year stats: {e}") return {"error": str(e)} @mcp.tool() async def get_player_career_highlights(player_name: str) -> Dict[str, Any]: """Get career highlights and achievements for a player. Args: player_name: The player's name (e.g., "LeBron James") Returns: Career highlights including best seasons, records, and achievements """ try: # Get various stat types to compile highlights per_game = brs_get_stats(player_name, stat_type="PER_GAME", playoffs=False, ask_matches=False) totals = brs_get_stats(player_name, stat_type="TOTALS", playoffs=False, ask_matches=False) advanced = brs_get_stats(player_name, stat_type="ADVANCED", playoffs=False, ask_matches=False) if per_game.empty: return {"error": f"No stats found for {player_name}"} # Get career stats career_pg = per_game[per_game['SEASON'] == 'Career'].to_dict('records')[0] if not per_game[per_game['SEASON'] == 'Career'].empty else {} career_totals = totals[totals['SEASON'] == 'Career'].to_dict('records')[0] if not totals[totals['SEASON'] == 'Career'].empty else {} # Season data season_pg = per_game[per_game['SEASON'] != 'Career'] season_totals = totals[totals['SEASON'] != 'Career'] season_advanced = advanced[advanced['SEASON'] != 'Career'] highlights = { "player_name": player_name, "career_overview": { "seasons_played": len(season_pg), "games_played": career_totals.get('G', 0), "career_ppg": career_pg.get('PTS', 0), "career_rpg": career_pg.get('TRB', 0), "career_apg": career_pg.get('AST', 0), "total_points": career_totals.get('PTS', 0) }, "single_season_highs": {} } # Find single season highs stats_to_check = { 'PTS': 'points_per_game', 'TRB': 'rebounds_per_game', 'AST': 'assists_per_game', 'STL': 'steals_per_game', 'BLK': 'blocks_per_game', 'FG%': 'field_goal_percentage', '3P%': 'three_point_percentage' } for stat, name in stats_to_check.items(): if stat in season_pg.columns: # For percentages, filter out seasons with low attempts if stat == '3P%': filtered = season_pg[season_pg['3PA'] > 1.0] else: filtered = season_pg if not filtered.empty and filtered[stat].notna().any(): best_idx = filtered[stat].idxmax() if pd.notna(best_idx): highlights["single_season_highs"][name] = { "value": filtered.loc[best_idx, stat], "season": filtered.loc[best_idx, 'SEASON'] } # Find 20+ PPG seasons twenty_ppg_seasons = season_pg[season_pg['PTS'] >= 20.0] highlights["seasons_20plus_ppg"] = len(twenty_ppg_seasons) # Find All-Star appearances (if AWARDS column exists) if 'AWARDS' in season_pg.columns: all_star_seasons = season_pg[season_pg['AWARDS'].notna() & season_pg['AWARDS'].str.contains('AS', na=False)] highlights["all_star_appearances"] = len(all_star_seasons) # Best advanced stat season if not season_advanced.empty and 'PER' in season_advanced.columns: best_per_idx = season_advanced['PER'].idxmax() if pd.notna(best_per_idx): highlights["best_per_season"] = { "value": season_advanced.loc[best_per_idx, 'PER'], "season": season_advanced.loc[best_per_idx, 'SEASON'] } return highlights except Exception as e: logger.error(f"Error getting career highlights: {e}") return {"error": str(e)} @mcp.tool() async def get_player_career_trends( player_name: str, stat_name: str = "PTS", window_size: int = 3 ) -> Dict[str, Any]: """Analyze career trends and progression for a player. Args: player_name: The player's name (e.g., "LeBron James") stat_name: The stat to analyze trends for (e.g., "PTS", "3P%", "PER") window_size: Years for moving average (default 3) Returns: Year-over-year progression, peak years, and trend analysis """ try: # Determine stat type if stat_name in ['PER', 'TS%', 'WS', 'BPM', 'VORP']: stat_type = "ADVANCED" else: stat_type = "PER_GAME" stats = brs_get_stats(player_name, stat_type=stat_type, playoffs=False, ask_matches=False) if stats.empty: return {"error": f"No stats found for {player_name}"} # Get season data season_data = stats[stats['SEASON'] != 'Career'].copy() if stat_name not in season_data.columns: return {"error": f"Stat '{stat_name}' not available"} # Convert stat to numeric season_data[stat_name] = pd.to_numeric(season_data[stat_name], errors='coerce') season_data = season_data.dropna(subset=[stat_name]) # Calculate trends result = { "player_name": player_name, "stat_analyzed": stat_name, "career_progression": season_data[['SEASON', stat_name]].to_dict('records'), "career_average": season_data[stat_name].mean(), "career_peak": season_data[stat_name].max(), "career_low": season_data[stat_name].min() } # Find peak season peak_idx = season_data[stat_name].idxmax() if pd.notna(peak_idx): result["peak_season"] = { "season": season_data.loc[peak_idx, 'SEASON'], "value": season_data.loc[peak_idx, stat_name], "age": season_data.loc[peak_idx, 'AGE'] if 'AGE' in season_data.columns else None } # Calculate year-over-year changes if len(season_data) > 1: season_data['YoY_change'] = season_data[stat_name].diff() season_data['YoY_pct_change'] = season_data[stat_name].pct_change() * 100 result["biggest_improvement"] = { "season": season_data.loc[season_data['YoY_change'].idxmax(), 'SEASON'], "improvement": season_data['YoY_change'].max() } result["biggest_decline"] = { "season": season_data.loc[season_data['YoY_change'].idxmin(), 'SEASON'], "decline": season_data['YoY_change'].min() } # Trend analysis (last 5 years) recent_seasons = season_data.tail(5) if len(recent_seasons) >= 3: trend = "declining" if recent_seasons[stat_name].iloc[-1] < recent_seasons[stat_name].iloc[0] else "improving" result["recent_trend"] = { "direction": trend, "last_5_years_avg": recent_seasons[stat_name].mean(), "current_vs_peak_pct": (season_data[stat_name].iloc[-1] / season_data[stat_name].max()) * 100 } return result except Exception as e: logger.error(f"Error analyzing career trends: {e}") return {"error": str(e)} @mcp.tool() async def get_player_game_highs( player_name: str, threshold_points: int = 40, include_triple_doubles: bool = True ) -> Dict[str, Any]: """Get career high games and milestone performances. Args: player_name: The player's name (e.g., "Kevin Durant") threshold_points: Point threshold for high-scoring games (default 40) include_triple_doubles: Whether to estimate triple-double games Returns: Career highs, 40+ point games, 50+ point games, triple-doubles estimate """ try: # Get career stats per_game = brs_get_stats(player_name, stat_type="PER_GAME", playoffs=False, ask_matches=False) totals = brs_get_stats(player_name, stat_type="TOTALS", playoffs=False, ask_matches=False) if per_game.empty: return {"error": f"No stats found for {player_name}"} season_data = per_game[per_game['SEASON'] != 'Career'] season_totals = totals[totals['SEASON'] != 'Career'] result = { "player_name": player_name, "career_highs": {}, "milestone_games_estimate": {} } # Find single-game career highs (estimates based on season highs) stats_to_check = { 'PTS': 'points', 'TRB': 'rebounds', 'AST': 'assists', 'STL': 'steals', 'BLK': 'blocks', '3P': 'three_pointers_made' } for stat, name in stats_to_check.items(): if stat in season_data.columns: max_idx = season_data[stat].idxmax() if pd.notna(max_idx): # Estimate single-game high as ~1.5-2x season average season_avg = season_data.loc[max_idx, stat] estimated_high = season_avg * 1.8 # Rough estimate result["career_highs"][f"estimated_{name}_high"] = { "value": round(estimated_high), "best_season_avg": season_avg, "season": season_data.loc[max_idx, 'SEASON'] } # Estimate high-scoring games career_ppg = per_game[per_game['SEASON'] == 'Career']['PTS'].values[0] total_games = totals[totals['SEASON'] == 'Career']['G'].values[0] # Rough estimates based on career averages if career_ppg >= 25: result["milestone_games_estimate"]["40_point_games"] = int(total_games * 0.05) # ~5% for elite scorers result["milestone_games_estimate"]["50_point_games"] = int(total_games * 0.005) # ~0.5% elif career_ppg >= 20: result["milestone_games_estimate"]["40_point_games"] = int(total_games * 0.02) # ~2% result["milestone_games_estimate"]["50_point_games"] = int(total_games * 0.001) # ~0.1% else: result["milestone_games_estimate"]["40_point_games"] = int(total_games * 0.005) # ~0.5% result["milestone_games_estimate"]["50_point_games"] = 0 # Triple-double estimates if include_triple_doubles: career_rpg = per_game[per_game['SEASON'] == 'Career']['TRB'].values[0] career_apg = per_game[per_game['SEASON'] == 'Career']['AST'].values[0] # Players who average close to triple-doubles have more if career_rpg >= 7 and career_apg >= 7: result["milestone_games_estimate"]["triple_doubles"] = int(total_games * 0.15) # ~15% elif career_rpg >= 5 and career_apg >= 5: result["milestone_games_estimate"]["triple_doubles"] = int(total_games * 0.02) # ~2% else: result["milestone_games_estimate"]["triple_doubles"] = int(total_games * 0.001) # ~0.1% # Best statistical seasons result["best_scoring_season"] = { "season": season_data.loc[season_data['PTS'].idxmax(), 'SEASON'], "ppg": season_data['PTS'].max() } return result except Exception as e: logger.error(f"Error getting game highs: {e}") return {"error": str(e)} @mcp.tool() async def get_player_situational_splits( player_name: str, season: Optional[int] = None, split_type: str = "home_away" ) -> Dict[str, Any]: """Get situational performance splits (estimated). Args: player_name: The player's name (e.g., "Joel Embiid") season: Specific season or None for career split_type: "home_away", "rest_days", "monthly", "win_loss" Returns: Performance splits based on different situations """ try: # Get base stats stats = brs_get_stats(player_name, stat_type="PER_GAME", playoffs=False, ask_matches=False) if stats.empty: return {"error": f"No stats found for {player_name}"} if season: season_str = f"{season-1}-{str(season)[2:]}" season_stats = stats[stats['SEASON'] == season_str] if season_stats.empty: return {"error": f"No stats for {season_str}"} base_stats = season_stats.iloc[0] else: base_stats = stats[stats['SEASON'] == 'Career'].iloc[0] season_str = "Career" result = { "player_name": player_name, "season": season_str, "split_type": split_type, "overall_stats": { "ppg": base_stats.get('PTS', 0), "rpg": base_stats.get('TRB', 0), "apg": base_stats.get('AST', 0), "fg_pct": base_stats.get('FG%', 0), "games": base_stats.get('G', 0) } } # Estimate splits based on typical patterns if split_type == "home_away": # Most players perform ~5-10% better at home result["splits"] = { "home": { "estimated_ppg": round(base_stats['PTS'] * 1.03, 1), "estimated_fg_pct": round(float(base_stats.get('FG%', 0)) * 1.02, 1), "games": int(base_stats['G'] / 2) }, "away": { "estimated_ppg": round(base_stats['PTS'] * 0.97, 1), "estimated_fg_pct": round(float(base_stats.get('FG%', 0)) * 0.98, 1), "games": int(base_stats['G'] / 2) } } result["note"] = "Home/away splits are estimates based on typical NBA patterns" elif split_type == "rest_days": # Players typically perform better with rest result["splits"] = { "0_days_rest": { "estimated_ppg": round(base_stats['PTS'] * 0.92, 1), "estimated_fg_pct": round(float(base_stats.get('FG%', 0)) * 0.95, 1) }, "1_day_rest": { "estimated_ppg": round(base_stats['PTS'] * 0.98, 1), "estimated_fg_pct": round(float(base_stats.get('FG%', 0)) * 0.99, 1) }, "2+_days_rest": { "estimated_ppg": round(base_stats['PTS'] * 1.05, 1), "estimated_fg_pct": round(float(base_stats.get('FG%', 0)) * 1.02, 1) } } result["note"] = "Rest day impacts are estimates based on typical fatigue patterns" elif split_type == "win_loss": # Players typically have better stats in wins result["splits"] = { "wins": { "estimated_ppg": round(base_stats['PTS'] * 1.1, 1), "estimated_fg_pct": round(float(base_stats.get('FG%', 0)) * 1.08, 1) }, "losses": { "estimated_ppg": round(base_stats['PTS'] * 0.9, 1), "estimated_fg_pct": round(float(base_stats.get('FG%', 0)) * 0.92, 1) } } result["note"] = "Win/loss splits are estimates; better performance typically correlates with wins" return result except Exception as e: logger.error(f"Error getting situational splits: {e}") return {"error": str(e)} @mcp.tool() async def get_player_quarter_stats( player_name: str, season: Optional[int] = None, quarter: str = "4th" ) -> Dict[str, Any]: """Get quarter-by-quarter performance (estimated). Args: player_name: The player's name (e.g., "Luka Dončić") season: Specific season or None for career quarter: "1st", "2nd", "3rd", "4th", "OT", or "all" Returns: Performance by quarter, especially clutch 4th quarter stats """ try: # Get base stats stats = brs_get_stats(player_name, stat_type="PER_GAME", playoffs=False, ask_matches=False) if stats.empty: return {"error": f"No stats found for {player_name}"} if season: season_str = f"{season-1}-{str(season)[2:]}" season_stats = stats[stats['SEASON'] == season_str] if season_stats.empty: return {"error": f"No stats for {season_str}"} base_stats = season_stats.iloc[0] else: base_stats = stats[stats['SEASON'] == 'Career'].iloc[0] season_str = "Career" ppg = float(base_stats.get('PTS', 0)) mpg = float(base_stats.get('MP', 36)) # Minutes per game result = { "player_name": player_name, "season": season_str, "overall_ppg": ppg, "minutes_per_game": mpg } # Estimate quarter distribution # Typical patterns: starters play more in 1st/3rd, clutch players excel in 4th if quarter == "all" or quarter == "4th": # 4th quarter typically sees slight increase for clutch players fourth_q_minutes = mpg * 0.25 # Roughly equal distribution # Clutch players tend to score ~30% of their points in 4th if ppg >= 25: # Star players fourth_q_points = ppg * 0.30 else: fourth_q_points = ppg * 0.25 result["fourth_quarter"] = { "estimated_ppg": round(fourth_q_points, 1), "estimated_mpg": round(fourth_q_minutes, 1), "clutch_factor": "high" if fourth_q_points > ppg * 0.28 else "average", "note": "Elite scorers typically increase output in 4th quarter" } if quarter == "all": # Distribute across all quarters result["quarter_breakdown"] = { "1st": {"estimated_points": round(ppg * 0.26, 1)}, "2nd": {"estimated_points": round(ppg * 0.24, 1)}, "3rd": {"estimated_points": round(ppg * 0.25, 1)}, "4th": {"estimated_points": round(ppg * 0.25, 1)} } # Add usage rate context for 4th quarter if quarter == "4th" and 'USG%' in base_stats: result["fourth_quarter"]["estimated_usage_rate"] = round(float(base_stats['USG%']) * 1.15, 1) result["fourth_quarter"]["go_to_scorer"] = ppg >= 20 result["note"] = "Quarter splits are estimates based on typical NBA patterns and player role" return result except Exception as e: logger.error(f"Error getting quarter stats: {e}") return {"error": str(e)} @mcp.tool() async def get_player_milestone_tracker( player_name: str, milestone_type: str = "points" ) -> Dict[str, Any]: """Track progress toward career milestones and project achievement dates. Args: player_name: The player's name (e.g., "LeBron James") milestone_type: "points", "assists", "rebounds", "3pm", "games" Returns: Current totals, next milestones, and projected achievement dates """ try: # Get career totals totals = brs_get_stats(player_name, stat_type="TOTALS", playoffs=False, ask_matches=False) per_game = brs_get_stats(player_name, stat_type="PER_GAME", playoffs=False, ask_matches=False) if totals.empty: return {"error": f"No stats found for {player_name}"} career_totals = totals[totals['SEASON'] == 'Career'].iloc[0] career_avg = per_game[per_game['SEASON'] == 'Career'].iloc[0] # Get recent season for projection recent_seasons = totals[totals['SEASON'] != 'Career'].tail(3) result = { "player_name": player_name, "milestone_type": milestone_type } # Define milestones milestones = { "points": { "stat": "PTS", "thresholds": [10000, 15000, 20000, 25000, 30000, 35000, 40000], "per_game": "PTS", "name": "career points" }, "assists": { "stat": "AST", "thresholds": [5000, 6000, 7000, 8000, 9000, 10000, 12000, 15000], "per_game": "AST", "name": "career assists" }, "rebounds": { "stat": "TRB", "thresholds": [5000, 7500, 10000, 12500, 15000, 17500, 20000], "per_game": "TRB", "name": "career rebounds" }, "3pm": { "stat": "3P", "thresholds": [1000, 1500, 2000, 2500, 3000, 3500], "per_game": "3P", "name": "three-pointers made" }, "games": { "stat": "G", "thresholds": [500, 750, 1000, 1250, 1500], "per_game": None, "name": "games played" } } if milestone_type not in milestones: return {"error": f"Invalid milestone type. Choose from: {list(milestones.keys())}"} milestone_info = milestones[milestone_type] current_total = int(career_totals.get(milestone_info["stat"], 0)) result["current_total"] = current_total result["career_average"] = career_avg.get(milestone_info["per_game"], 0) if milestone_info["per_game"] else None # Find next milestones next_milestones = [m for m in milestone_info["thresholds"] if m > current_total] if next_milestones: result["next_milestone"] = next_milestones[0] result["needed_for_next"] = next_milestones[0] - current_total # Project games needed if milestone_info["per_game"] and len(recent_seasons) > 0: # Average from last 3 seasons recent_avg = recent_seasons[milestone_info["stat"]].sum() / recent_seasons['G'].sum() games_needed = result["needed_for_next"] / recent_avg if recent_avg > 0 else None if games_needed: result["projected_games_to_milestone"] = int(games_needed) # Assume 70 games per season result["projected_seasons_to_milestone"] = round(games_needed / 70, 1) # Calculate projected date if games_needed < 82: result["projected_achievement"] = "This season" elif games_needed < 164: result["projected_achievement"] = "Next season" else: seasons = int(games_needed / 70) + 1 result["projected_achievement"] = f"In approximately {seasons} seasons" # Show multiple upcoming milestones result["upcoming_milestones"] = next_milestones[:3] else: result["message"] = f"Already achieved all standard {milestone_info['name']} milestones!" # Add context for historic significance if milestone_type == "points" and current_total > 30000: result["historic_context"] = "Among the all-time scoring leaders" elif milestone_type == "assists" and current_total > 10000: result["historic_context"] = "Elite playmaker in NBA history" return result except Exception as e: logger.error(f"Error tracking milestones: {e}") return {"error": str(e)} @mcp.tool() async def get_player_rankings( player_name: str, category: str = "points" ) -> Dict[str, Any]: """Get all-time rankings for a player in various categories. Args: player_name: The player's name (e.g., "Stephen Curry") category: "points", "assists", "rebounds", "3pm", "steals", "blocks" Returns: Estimated all-time ranking and context """ try: # Get career totals totals = brs_get_stats(player_name, stat_type="TOTALS", playoffs=False, ask_matches=False) if totals.empty: return {"error": f"No stats found for {player_name}"} career_totals = totals[totals['SEASON'] == 'Career'].iloc[0] # All-time thresholds for rough ranking estimates ranking_thresholds = { "points": { "stat": "PTS", "thresholds": [ (40000, 1), (38000, 2), (35000, 3), (33000, 4), (32000, 5), (31000, 6), (30000, 8), (28000, 10), (27000, 12), (26000, 15), (25000, 20), (23000, 25), (21000, 30), (20000, 35), (19000, 40), (18000, 50), (15000, 75), (12000, 100), (10000, 150) ], "goat_value": 38387, # Kareem "goat_name": "Kareem Abdul-Jabbar (38,387)" }, "assists": { "stat": "AST", "thresholds": [ (15000, 1), (12000, 2), (11000, 3), (10000, 4), (9000, 5), (8000, 8), (7000, 12), (6000, 20), (5000, 35), (4000, 60) ], "goat_value": 15806, # Stockton "goat_name": "John Stockton (15,806)" }, "rebounds": { "stat": "TRB", "thresholds": [ (23000, 1), (22000, 2), (21000, 3), (17000, 5), (16000, 8), (15000, 12), (14000, 15), (13000, 20), (12000, 30), (10000, 50) ], "goat_value": 23924, # Wilt "goat_name": "Wilt Chamberlain (23,924)" }, "3pm": { "stat": "3P", "thresholds": [ (3500, 1), (3000, 2), (2800, 3), (2600, 4), (2400, 5), (2200, 8), (2000, 12), (1800, 20), (1500, 35), (1000, 100) ], "goat_value": 3747, # Curry (as of 2024) "goat_name": "Stephen Curry (3,700+)" } } if category not in ranking_thresholds: return {"error": f"Invalid category. Choose from: {list(ranking_thresholds.keys())}"} ranking_info = ranking_thresholds[category] current_total = int(career_totals.get(ranking_info["stat"], 0)) # Estimate ranking estimated_rank = None for threshold, rank in ranking_info["thresholds"]: if current_total >= threshold: estimated_rank = rank break if not estimated_rank: estimated_rank = "Outside top 150" result = { "player_name": player_name, "category": category, "career_total": current_total, "estimated_all_time_rank": estimated_rank, "all_time_leader": ranking_info["goat_name"], "percentage_of_leader": round((current_total / ranking_info["goat_value"]) * 100, 1) } # Add context if isinstance(estimated_rank, int): if estimated_rank <= 5: result["context"] = "All-time great in this category" elif estimated_rank <= 10: result["context"] = "Top 10 all-time - legendary status" elif estimated_rank <= 25: result["context"] = "Among the very best to ever play" elif estimated_rank <= 50: result["context"] = "Elite historical standing" else: result["context"] = "Significant career achievement" # Special notes for active players seasons_played = len(totals[totals['SEASON'] != 'Career']) if seasons_played <= 15: # Likely still active result["note"] = "Still active - ranking will improve" return result except Exception as e: logger.error(f"Error getting rankings: {e}") return {"error": str(e)} # Main entry point def main(): """Main entry point for the server""" mcp.run() if __name__ == "__main__": main()

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ziyadmir/nba-player-stats-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server