get_player_by_id
Retrieve Dota 2 player information including rank, match history, and statistics using their Steam32 account ID to analyze gameplay data.
Instructions
Get a player's information by their account ID.
Args:
account_id: The player's Steam32 account ID
Returns:
Player information including rank, matches, and statistics
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| account_id | Yes |
Implementation Reference
- src/opendota_server/server.py:410-432 (handler)The primary handler function for the 'get_player_by_id' tool. It is decorated with @mcp.tool() which also serves as the registration. Fetches player data, win/loss records, and recent matches from the OpenDota API, then formats and returns the information as a string.@mcp.tool() async def get_player_by_id(account_id: int) -> str: """Get a player's information by their account ID. Args: account_id: The player's Steam32 account ID Returns: Player information including rank, matches, and statistics """ player_data = await make_opendota_request(f"players/{account_id}") if "error" in player_data: return f"Error retrieving player data: {player_data['error']}" # Get win/loss stats wl_data = await make_opendota_request(f"players/{account_id}/wl") # Get recent matches recent_matches = await make_opendota_request(f"players/{account_id}/recentMatches") return format_player_data(player_data, wl_data, recent_matches)
- Key helper function used by the handler to format the raw player data, win/loss stats, and recent matches into a human-readable string response.def format_player_data( player: Dict[str, Any], wl: Optional[Dict[str, Any]] = None, recent_matches: Optional[Union[List[Dict[str, Any]], Dict[str, Any]]] = None, ) -> str: """Format player data into a readable string.""" if not player: return "Player data not found." # Parse the player data player_obj = parse_player(player) # Basic info account_id = player_obj.account_id name = player_obj.personaname or "Anonymous" rank = format_rank_tier(player_obj.rank_tier) mmr = player_obj.mmr_estimate or "Unknown" # Win/Loss record wins = wl.get("win", 0) if wl else 0 losses = wl.get("lose", 0) if wl else 0 total_games = wins + losses win_rate = (wins / total_games * 100) if total_games > 0 else 0 # Format recent matches if available recent_matches_text = "" if recent_matches and isinstance(recent_matches, list): match_texts = [] matches_to_show = recent_matches[:5] if len(recent_matches) > 0 else [] for match in matches_to_show: hero_id = match.get("hero_id", "Unknown") kills = match.get("kills", 0) deaths = match.get("deaths", 0) assists = match.get("assists", 0) win = ( "Won" if (match.get("radiant_win") == (match.get("player_slot", 0) < 128)) else "Lost" ) match_date = format_timestamp(match.get("start_time", 0)) match_texts.append( f"Match ID: {match.get('match_id')}\n" f"- Date: {match_date}\n" f"- Hero: {hero_id}\n" f"- K/D/A: {kills}/{deaths}/{assists}\n" f"- Result: {win}" ) recent_matches_text = "\n\nRecent Matches:\n" + "\n\n".join(match_texts) # Professional player info if applicable pro_info = "" if player_obj.is_pro: pro_info = ( f"\nProfessional Player: Yes\nTeam: {player_obj.team_name or 'Unknown'}" ) return ( f"Player: {name} (ID: {account_id})\n" f"Rank: {rank}\n" f"Estimated MMR: {mmr}\n" f"Win/Loss: {wins}/{losses} ({win_rate:.1f}% win rate){pro_info}{recent_matches_text}" )
- Helper function to parse raw API player data into a structured Player dataclass instance, used within format_player_data.def parse_player(player_data: Dict[str, Any]) -> Player: """Parse API response into a Player object.""" profile = player_data.get("profile", {}) account_id = player_data.get("account_id") if account_id is None: # Default to 0 if account_id is None to satisfy the type checker account_id = 0 return Player( account_id=account_id, personaname=profile.get("personaname"), name=profile.get("name"), steam_id=profile.get("steamid"), avatar=profile.get("avatarfull"), profile_url=profile.get("profileurl"), rank_tier=player_data.get("rank_tier"), mmr_estimate=player_data.get("mmr_estimate", {}).get("estimate"), country_code=profile.get("loccountrycode"), is_pro=bool(player_data.get("is_pro", False)), team_name=player_data.get("team_name"), team_id=player_data.get("team_id"), )
- Core helper function for making HTTP requests to the OpenDota API, including caching, rate limiting, and comprehensive error handling. Used by the handler to fetch data.async def make_opendota_request( endpoint: str, params: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Make a request to the OpenDota API with proper error handling and caching.""" # Apply rate limiting await apply_rate_limit() url = f"{OPENDOTA_API_BASE}/{endpoint}" request_params = API_PARAMS.copy() if params: request_params.update(params) # Create a cache key manually cache_key = endpoint if request_params: param_str = "&".join(f"{k}={v}" for k, v in sorted(request_params.items())) cache_key = f"{endpoint}?{param_str}" # Check cache cache_entry = api_cache.get(cache_key) if cache_entry: timestamp, data = cache_entry if time.time() - timestamp < CACHE_TTL: logger.debug(f"Cache hit for {cache_key}") return data logger.info(f"Making request to {endpoint} with params {request_params}") async with httpx.AsyncClient() as client: try: response = await client.get( url, params=request_params, headers={"User-Agent": USER_AGENT}, timeout=10.0, ) response.raise_for_status() data = response.json() # Cache the response api_cache[cache_key] = (time.time(), data) return data except httpx.HTTPStatusError as e: if e.response.status_code == 429: logger.error(f"Rate limit exceeded for {endpoint}") return { "error": "Rate limit exceeded. Consider using an API key for more requests." } if e.response.status_code == 404: logger.error(f"Resource not found: {endpoint}") return {"error": "Not found. The requested resource doesn't exist."} if e.response.status_code >= 500: logger.error(f"OpenDota API server error: {e.response.status_code}") return {"error": "OpenDota API server error. Please try again later."} logger.error( f"HTTP error {e.response.status_code} for {endpoint}: {e.response.text}" ) return {"error": f"HTTP error {e.response.status_code}: {e.response.text}"} except Exception as e: logger.error(f"Unexpected error for {endpoint}: {str(e)}") return {"error": f"Unexpected error: {str(e)}"}