get_match_data
Retrieve detailed Dota 2 match information including player stats, scores, and game metrics for analysis or reference.
Instructions
Get detailed data for a specific match.
Args:
match_id: ID of the match to retrieve
Returns:
Detailed match information including players, scores, and stats
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| match_id | Yes |
Implementation Reference
- src/opendota_server/server.py:496-512 (handler)The core handler function for the 'get_match_data' tool. Decorated with @mcp.tool() for automatic registration in the FastMCP server. It fetches match data from the OpenDota API using make_opendota_request and formats it using format_match_data.@mcp.tool() async def get_match_data(match_id: int) -> str: """Get detailed data for a specific match. Args: match_id: ID of the match to retrieve Returns: Detailed match information including players, scores, and stats """ match_data = await make_opendota_request(f"matches/{match_id}") if "error" in match_data: return f"Error retrieving match data: {match_data['error']}" return format_match_data(match_data)
- Helper function that formats the raw match dictionary from the API into a detailed, human-readable string including match details, scores, teams, and per-player stats.def format_match_data(match: Dict[str, Any]) -> str: """Format match data into a readable string.""" if not match or "match_id" not in match: return "Match data not found." # Basic match info match_id = match.get("match_id", "Unknown") duration = match.get("duration", 0) duration_formatted = format_duration(duration) start_time = format_timestamp(match.get("start_time", 0)) game_mode = match.get("game_mode", "Unknown") radiant_win = match.get("radiant_win", False) winner = "Radiant" if radiant_win else "Dire" # Scores radiant_score = match.get("radiant_score", 0) dire_score = match.get("dire_score", 0) # Teams radiant_team_data = match.get("radiant_team", {}) dire_team_data = match.get("dire_team", {}) # Handle the case where these might be strings instead of dicts if isinstance(radiant_team_data, dict): radiant_team = radiant_team_data.get("name", "Radiant") else: radiant_team = "Radiant" if isinstance(dire_team_data, dict): dire_team = dire_team_data.get("name", "Dire") else: dire_team = "Dire" # Format players data player_data = [] players = match.get("players", []) for player in players: account_id = player.get("account_id", "Anonymous") hero_id = player.get("hero_id", "Unknown") hero_name = player.get("hero_name", "Unknown Hero") kills = player.get("kills", 0) deaths = player.get("deaths", 0) assists = player.get("assists", 0) gpm = player.get("gold_per_min", 0) xpm = player.get("xp_per_min", 0) team = "Radiant" if player.get("player_slot", 0) < 128 else "Dire" player_data.append( f"Player ID: {account_id}\n" f"- Team: {team}\n" f"- Hero: {hero_name} (ID: {hero_id})\n" f"- K/D/A: {kills}/{deaths}/{assists}\n" f"- GPM/XPM: {gpm}/{xpm}" ) joined_player_data = "\n\n".join(player_data) formatted_output = ( f"Match ID: {match_id}\n" f"Date: {start_time}\n" f"Duration: {duration_formatted}\n" f"Game Mode: {game_mode}\n" f"Teams: {radiant_team} vs {dire_team}\n" f"Score: {radiant_score} - {dire_score}\n" f"Winner: {winner}\n\n" f"Player Details:\n" f"{'-' * 40}\n" f"{joined_player_data}" ) return formatted_output
- Core utility function used by get_match_data to perform API requests to OpenDota. Includes rate limiting, caching (5min TTL), retries, and comprehensive error handling.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)}"}
- src/opendota_server/server.py:496-496 (registration)The @mcp.tool() decorator registers the get_match_data function as an MCP tool in the FastMCP server.@mcp.tool()