opendota-mcp-server
Provides access to Dota 2 player statistics, match data, hero information, item builds, and matchup data through the OpenDota API, enabling AI agents to query player profiles, recent matches, hero performance, and more.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@opendota-mcp-server get recent matches for player 86745991"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
OpenDota MCP Server
A Model Context Protocol (MCP) server that provides seamless access to the OpenDota API for querying Dota 2 player statistics, match data, and hero information through AI assistants like Claude.
Features
20+ Tools across 5 categories for comprehensive Dota 2 data access
Natural Language Support - Fuzzy matching for hero names, lane positions, and stat fields
Dual Transport - Supports both stdio (local) and HTTP (remote) modes
Rate Limiting - Built-in protection respecting OpenDota API limits
Reference Data Caching - Fast lookups with local hero/item data
Related MCP server: mlbb-mcp
Available Tools
Player Tools (6 tools)
Query player statistics, win/loss records, hero preferences, and performance metrics:
get_player_info- Get player profile, win rate, and favorite heroesget_player_win_loss- Win/loss stats with advanced filteringget_heroes_played- Detailed statistics for all heroes playedget_player_peers- Find frequent teammatesget_player_totals- Aggregated player statisticsget_player_histograms- Distribution of matches across statistical fields
Hero Tools (4 tools)
Access hero information, matchups, and item builds:
get_heroes- List all heroes with attributesget_hero_matchups- Matchup data and win ratesget_hero_item_popularity- Popular item builds by game phaseget_hero_stats- Aggregated hero statistics and pick rates
Match Tools (3 tools)
Retrieve detailed match information and parsed data:
get_match_details- Comprehensive match informationget_parsed_match_details- Parsed match data with advanced analyticsget_player_recent_matches- Recent matches for a player
Lookup Tools (4 tools)
Convert names to IDs with fuzzy matching:
get_hero_id_by_name- Hero name → ID (handles typos)get_hero_by_id- Get hero details by IDconvert_lane_name_to_id- Lane/position → lane_role IDresolve_time_name- Time period names → OpenDota filter parameters
Misc Tools (3 tools)
Search and utility functions:
search_players- Search for players by nameget_constants- Get OpenDota constants and reference dataget_player_counts- Get player count by rank tier
Installation
Prerequisites
Python 3.10 or higher
pip package manager
Install from Source
# Clone the repository
git clone https://github.com/hkaanengin/opendota-mcp-server.git
cd opendota-mcp-server
# Create virtual environment
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install with dev dependencies
pip install -e ".[dev]"
# Or install runtime only
pip install -e .Usage
Option 1: Local Server (stdio mode)
Best for Claude Desktop integration on your local machine.
Using Claude Desktop
Config file location:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.jsonLinux:
~/.config/Claude/claude_desktop_config.json
Recommended: Using the installed command
After running pip install -e ., add to your claude_desktop_config.json:
{
"mcpServers": {
"opendota": {
"command": "/path/to/your/opendota-mcp-server/.venv/bin/opendota-mcp"
}
}
}Note: Replace
/path/to/your/opendota-mcp-serverwith the actual path where you cloned this repository. On macOS/Linux, runpwdin the project directory to find this path. On Windows, runcdto see the full path.
Alternative: Using Python module directly
{
"mcpServers": {
"opendota": {
"command": "/path/to/your/opendota-mcp-server/.venv/bin/python",
"args": ["-m", "opendota_mcp.server"]
}
}
}Alternative: Using the shell script
{
"mcpServers": {
"opendota": {
"command": "/path/to/your/opendota-mcp-server/start_mcp.sh"
}
}
}Using Docker:
{
"mcpServers": {
"opendota": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e", "MCP_TRANSPORT=stdio",
"hkaanengin/opendota-mcp-server:latest"
]
}
}
}After updating the config:
Save the file
Restart Claude Desktop completely
Start a new conversation
Your OpenDota tools should now be available!
Manual Testing
# Run the server directly
opendota-mcp
# Or using Python module
python -m opendota_mcp.server
# Or using the shell script
./start_mcp.shOption 2: Remote Server (HTTP mode)
Access a deployed server from anywhere.
Public Server
Use the publicly deployed server at:
https://opendota-mcp-server-jylza6gata-ew.a.run.app/mcpClaude Desktop (Remote Connection)
Add to your claude_desktop_config.json:
{
"mcpServers": {
"opendota": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-remote-http",
"https://opendota-mcp-server-jylza6gata-ew.a.run.app/mcp"
]
}
}
}Claude.ai Web Interface
Go to Claude.ai → Settings → MCP Servers
Click "Add Server"
Enter Server URL:
https://opendota-mcp-server-jylza6gata-ew.a.run.app/mcpSave and start chatting!
Option 3: Deploy Your Own
Deploy to Google Cloud Run or any container platform:
# Build Docker image
docker build -t opendota-mcp-server .
# Run in HTTP mode
docker run -p 8080:8080 \
-e MCP_TRANSPORT=http \
-e PORT=8080 \
opendota-mcp-serverConfiguration
Environment Variables
Create a .env file in the project root (or copy from .env.example):
# OpenDota API Key (optional)
# Get your API key from: https://www.opendota.com/api-keys
OPENDOTA_API_KEY=
# Logging level (DEBUG, INFO, WARNING, ERROR)
LOG_LEVEL=INFO
# Transport mode (stdio or http)
MCP_TRANSPORT=stdio
# HTTP server port (for HTTP mode)
PORT=8080OpenDota API Key (Optional)
An API key is optional but recommended for higher rate limits and better performance.
Rate Limits:
Without API key: 60 requests/minute (anonymous)
With API key: Higher limits for registered users
How to get an API key:
Sign in with your Steam account
Click "Create API Key"
Copy the generated API key
Add it to your
.envfile:OPENDOTA_API_KEY=your-api-key-hereRestart the server
The server will automatically detect and use the API key when configured. You'll see a confirmation message in the logs:
✅ OpenDota API key configured (higher rate limits enabled)Contributing
Contributions are welcome! Please:
Fork the repository
Create a feature branch (
git checkout -b feature/amazing-feature)Commit your changes (
git commit -m 'Add amazing feature')Push to the branch (
git push origin feature/amazing-feature)Open a Pull Request
Acknowledgments
OpenDota for providing the excellent Dota 2 API
FastMCP for the MCP framework
The Model Context Protocol team at Anthropic
The Dota 2 community
Support
For issues, questions, or contributions:
Open an issue on GitHub
Check existing issues for solutions
Review the OpenDota API documentation
Made with ❤️ for the Dota 2 community
Available Tools
18 toolsget_aghs_detailsA
Get Aghanim's Scepter and Shard upgrade details for a hero with fuzzy matching.
Use this when users ask about Aghanim's upgrades:
"What does Aghanim's Scepter do for Pudge?"
"Show me Anti-Mage's shard upgrade"
"What are Invoker's Aghs upgrades?"
"Does Rubick have a shard?"
Supports fuzzy matching for hero names:
"Pudge", "pudge", "Anti-Mage", "antimage" all work
"Shadow Fiend", "shadowfiend", "sf" all work (if alias exists)
Args: hero: Hero name (display name, internal name, fuzzy match) or hero ID Examples: "Pudge", "antimage", "Shadow Fiend", 1
Returns: Dictionary with Aghanim's upgrade information: - hero_id: Hero ID - hero_name: Internal hero name (e.g., "npc_dota_hero_pudge") - has_scepter: Whether the hero has a scepter upgrade (bool) - scepter_desc: Description of scepter upgrade (if has_scepter) - scepter_skill_name: Name of skill affected by scepter (if has_scepter) - scepter_new_skill: Whether scepter adds a new skill (bool, if has_scepter) - has_shard: Whether the hero has a shard upgrade (bool) - shard_desc: Description of shard upgrade (if has_shard) - shard_skill_name: Name of skill affected by shard (if has_shard) - shard_new_skill: Whether shard adds a new skill (bool, if has_shard)
Examples: get_aghs_details("Pudge") → Full Aghanim's info for Pudge get_aghs_details("Anti-Mage") → Scepter and Shard details for Anti-Mage get_aghs_details(1) → Aghanim's details by hero ID
Inputs: hero (required). Returns: object
get_benchmarksA
Get statistical benchmarks for a hero (average performance metrics across all skill levels).
Use this when users ask:
"What are the average stats for [hero]?"
"What's a good GPM/XPM for [hero]?"
"Show me benchmark stats for [hero]"
"What's the typical performance for [hero]?"
"How do I compare to other [hero] players?"
Returns percentile-based benchmarks showing what constitutes below-average, average, good, and exceptional performance for various metrics on this hero. Useful for comparing a player's performance to the general population.
Supports both hero IDs and natural language hero names.
Args: hero_id: Hero to get benchmarks for. Accepts: - Integer: Hero ID (e.g., 86 for Rubick) - String: Hero name (e.g., "Rubick", "Anti-Mage")
Returns: Dictionary containing benchmark statistics organized by metric. Each metric shows percentile breakdowns (e.g., 0.1, 0.25, 0.5, 0.75, 0.9) representing the 10th, 25th, 50th (median), 75th, and 90th percentiles.
Common metrics included:
- gold_per_min: GPM benchmarks by percentile
- xp_per_min: XPM benchmarks by percentile
- kills_per_min, deaths_per_min, assists_per_min: KDA-related benchmarks
- last_hits_per_min: Farming efficiency benchmarks
- hero_damage_per_min: Combat effectiveness benchmarks
- hero_healing_per_min: Support/healing benchmarks
- tower_damage: Objective damage benchmarks
- stuns_per_min: Crowd control benchmarks
- And many more...Common queries: - General benchmarks: get_benchmarks("Rubick") - Compare player stats: First get player stats with get_player_totals(), then compare to benchmarks from this function
Example: get_benchmarks("Rubick") -> { "gold_per_min": { "0.1": 250.5, # 10th percentile - below average "0.25": 310.2, # 25th percentile - low average "0.5": 380.7, # 50th percentile (median) - average "0.75": 450.3, # 75th percentile - above average "0.9": 520.8 # 90th percentile - excellent }, "kills_per_min": { "0.1": 0.15, "0.5": 0.35, "0.9": 0.65 }, ... }
Interpretation: If a player has 460 GPM on Rubick, they're performing around the 75th percentile (better than 75% of Rubick players).
Inputs: hero_id (required). Returns: object
get_hero_detailsA
Get detailed hero information by hero name or ID with fuzzy matching.
Use this when users ask about heroes:
"What are Anti-Mage's stats?"
"Tell me about Pudge"
"Show me Invoker details"
Supports fuzzy matching for typos and variations:
"Anti-Mage", "antimage", "anti mage" all work
"Pudge", "pudge", "PUDGE" all work
"Shadow Fiend", "shadowfiend", "nevermore" all work
Args: hero: Hero name (display name, internal name, fuzzy match) or hero ID Examples: "Anti-Mage", "antimage", "pudge", 1
Returns: Dictionary with complete hero information including: - id: Hero ID - name: Internal name (e.g., "npc_dota_hero_antimage") - localized_name: Display name (e.g., "Anti-Mage") - primary_attr: Primary attribute ("str", "agi", "int", "all") - attack_type: "Melee" or "Ranged" - roles: List of roles (e.g., ["Carry", "Escape", "Nuker"]) - Base stats: base_health, base_mana, base_armor, etc. - Attribute gains: str_gain, agi_gain, int_gain - Combat stats: attack_range, attack_rate, move_speed, vision - All other hero statistics
Examples: get_hero_details("Anti-Mage") → Full Anti-Mage data get_hero_details("antimage") → Same result (fuzzy match) get_hero_details("Pudge") → Full Pudge data get_hero_details(1) → Anti-Mage data (by ID)
Inputs: hero (required). Returns: object
get_heroes_playedA
Get detailed statistics for all heroes a player has played, with performance metrics.
Use this when users ask about HERO POOLS or HERO PERFORMANCE:
"What heroes does [player] play?"
"Show me [player]'s hero pool"
"Which heroes is [player] best at?"
"What's [player]'s most played hero?"
"How good is [player] at Rubick?"
"What heroes does [player] play in mid lane?"
"When did [player] last play Invoker?"
This returns detailed stats for EACH hero (win rate, games played, last played date). For simple win/loss totals without hero breakdown, use get_player_win_loss() instead.
Supports both IDs and natural language for flexible querying.
Args: player_name: The Dota 2 player name to search for limit: Maximum number of heroes to return (default: all heroes) offset: Skip first N heroes in results (for pagination) lane_role: Filter by lane (accepts "mid", "carry", "offlane", "pos 1-4", etc.) hero_id: Focus on specific hero only (accepts name or ID) included_account_id: Only include matches with these teammates (accepts names or IDs) excluded_account_id: Exclude matches with these players (accepts names or IDs) with_hero_id: Require these heroes on player's team (accepts IDs or names, single or list) against_hero_id: Require these heroes on enemy team (accepts IDs or names, single or list) having: Minimum games played threshold to include a hero in results
Returns: List of hero statistics (sorted by games played, descending), each containing: - hero_id (str): Hero name (localized, e.g., "Rubick") - last_played (str): Date string (e.g., "December 09, 2024") - wins (int): Number of wins with this hero - games_played (int): Total games with this hero - win_rate (str): Win percentage as string (e.g., "65.3")
Common queries: - Most played heroes: get_heroes_played("kürlo") - Top 5 heroes: get_heroes_played("kürlo", limit=5) - Mid lane heroes: get_heroes_played("kürlo", lane_role="mid") - Heroes with 10+ games: get_heroes_played("kürlo", having=10) - Performance vs Pudge: get_heroes_played("kürlo", against_hero_id="Pudge") - Specific hero stats: get_heroes_played("kürlo", hero_id="Rubick")
Example: get_heroes_played("kürlo", lane_role="mid", having=5) -> [ {"hero_id": "Rubick", "last_played": "December 09, 2024", "wins": 42, "games_played": 80, "win_rate": "52.5"}, {"hero_id": "Invoker", "last_played": "December 05, 2024", "wins": 15, "games_played": 25, "win_rate": "60.0"}, ... ]
Inputs: player_name (required), limit, having, offset, +6 more. Returns: Generic wrapper for non-object return types.
get_hero_item_popularityA
Get item popularity statistics for a hero organized by game phase (start, early, mid, late).
Use this when users ask about:
"What items should I buy on Pudge?"
"Show me Anti-Mage's item build"
"What's the most popular starting items for Invoker?"
"What do people build late game on Phantom Assassin?"
"Show me Rubick's core items"
Returns aggregated item statistics from thousands of matches, useful for:
Understanding optimal item progression for a hero
Identifying core items vs situational items
Learning meta item builds and timings
Seeing which items are most popular at each game stage
Supports fuzzy matching for hero names:
"Pudge", "pudge", "PUDGE" all work
"Anti-Mage", "antimage", "anti mage" all work
"Shadow Fiend", "shadowfiend", "sf" all work
Args: hero: Hero name (display name, internal name, fuzzy match) or hero ID Examples: "Pudge", "antimage", "Shadow Fiend", 86
Returns: Dictionary with game phases as keys, each containing: - start_game_items: Items purchased at game start (0-10 min) - early_game_items: Items purchased early (0-25 min) - mid_game_items: Items purchased mid game (25-40 min) - late_game_items: Items purchased late game (40+ min)
Each phase contains item names mapped to their statistics:
- wins (int): Times this item was in winning matches
- games (int): Total matches where this item was purchasedExamples: get_hero_item_popularity("Anti-Mage") -> { "start_game_items": { "Quelling Blade": {"wins": 15234, "games": 25123}, "Tango": {"wins": 18234, "games": 26234}, "Slippers of Agility": {"wins": 12345, "games": 21234}, ... }, "early_game_items": { "Power Treads": {"wins": 8234, "games": 15234}, "Battle Fury": {"wins": 12234, "games": 18234}, ... }, "mid_game_items": { "Manta Style": {"wins": 9234, "games": 14234}, "Black King Bar": {"wins": 5234, "games": 8234}, ... }, "late_game_items": { "Butterfly": {"wins": 4234, "games": 6234}, "Abyssal Blade": {"wins": 3234, "games": 5234}, ... } }
get_hero_item_popularity(1) # Anti-Mage by ID
-> Same format as aboveInputs: hero (required). Returns: object
get_hero_matchupsA
Get matchup statistics showing how a hero performs against all other heroes.
Use this when users ask about:
"Which heroes counter Pudge?"
"What are Anti-Mage's best matchups?"
"Show me heroes that Invoker struggles against"
"Who should I pick against Phantom Assassin?"
"What's Rubick's win rate against Storm Spirit?"
Returns win/loss statistics for every hero matchup, useful for:
Identifying counter-picks (heroes with high win rates against your hero)
Finding favorable matchups (heroes your hero performs well against)
Draft analysis and hero selection strategy
Supports fuzzy matching for hero names:
"Pudge", "pudge", "PUDGE" all work
"Anti-Mage", "antimage", "anti mage" all work
"Shadow Fiend", "shadowfiend", "sf" all work
Args: hero: Hero name (display name, internal name, fuzzy match) or hero ID Examples: "Pudge", "antimage", "Shadow Fiend", 86
Returns: List of matchup dictionaries, each containing: - hero_name (str): Name of the opponent hero - games (int): Total games played in this matchup - win (int): Games won against this hero - loss (int): Games lost against this hero - win_rate (float): Win percentage (0-100) against this hero
Sorted by game count (most common matchups first)Examples: get_hero_matchups("Pudge") -> [ { "hero_name": "Anti-Mage", "games": 15234, "win": 7123, "loss": 8111, "win_rate": 46.75 }, { "hero_name": "Invoker", "games": 14521, "win": 8234, "loss": 6287, "win_rate": 56.70 }, ... ]
get_hero_matchups(86) # Rubick by ID
-> Same format as aboveInputs: hero (required). Returns: Generic wrapper for non-object return types.
get_item_detailsA
Get detailed item information by item name with fuzzy matching.
Use this when users ask about items:
"What does Diffusal Blade do?"
"Show me Octarine Core"
"Tell me about BKB"
Supports fuzzy matching for typos and variations:
"Diffusal", "diffusal blade", "Diffusal Blade" all work
"Octarine", "octarine core", "Octarine Core" all work
"bkb", "Black King Bar", "black king bar" all work
Args: item_name: Item name (display name, internal name, or fuzzy match) Examples: "Diffusal Blade", "diffusal", "octarine core"
Returns: Dictionary with complete item information including: - dname: Display name (e.g., "Diffusal Blade") - cost: Gold cost - abilities: List of active/passive abilities with descriptions - hint: Usage hints and tips - stats: Attribute bonuses (damage, mana, health, etc.) - notes: Additional information
Examples: get_item_details("Diffusal Blade") → Full Diffusal Blade data get_item_details("diffusal") → Same result (fuzzy match) get_item_details("Octarine Core") → Full Octarine Core data get_item_details("octarine") → Same result (fuzzy match)
Inputs: item_name (required). Returns: object
get_match_detailsA
Get comprehensive details for a specific match, with automatic detection of parse status.
Use this when users ask:
"Show me match [match_id]"
"Analyze match [match_id]"
"What happened in match [match_id]?"
"Show me the teamfights in match [match_id]"
"Who won match [match_id]?"
"What were the item builds in match [match_id]?"
"Show me the gold graph for match [match_id]"
This tool automatically detects whether a match has been parsed (has detailed replay data) or not:
Parsed matches: Returns organized summary with teamfights, objectives, gold/XP advantage graphs, chat logs, and detailed player performance benchmarks
Unparsed matches: Returns basic match data with player KDA, GPM/XPM, and game outcome
For matches that aren't parsed yet, use request_parse_match() first, wait a few minutes, then call this function to get the detailed data.
Args: match_id: The match ID to retrieve (e.g., 8123456789)
Returns: Dictionary with structure depending on parse status:
If parsed=True (detailed replay data available):
- parsed (bool): True
- metadata (dict): Match info (duration, game mode, winner, etc.)
- teamfights_summary (dict):
- count (int): Number of teamfights detected
- teamfights (list): Detailed teamfight breakdowns with:
- start/end time, location, deaths, gold swing
- objectives (list): Timeline of objectives (towers, barracks, Roshan)
- chat (list): In-game chat messages with timestamps
- picks_bans (list): Draft phase picks and bans
- players_summary (dict):
- count (int): Number of players (always 10)
- players (list): Per-player statistics including:
- account_id, hero_name, team, KDA
- gold_per_min, xp_per_min, net_worth
- hero_damage, tower_damage, hero_healing
- last_hits, denies
- benchmarks: Performance percentiles for 7 key metrics
- gold_advantage (list): Gold advantage over time (array of values)
- xp_advantage (list): XP advantage over time (array of values)
If parsed=False (basic match data only):
- parsed (bool): False
- data (dict):
- players (list): Basic player stats (KDA, GPM, damage, benchmarks)
- radiant_win (bool): True if Radiant won, False if Dire won
- duration (str): Game length in MM:SS format
- match_id (int): The match IDCommon queries: - Basic match info: get_match_details(8123456789) - Teamfight analysis: After parsing, check teamfights_summary - Performance comparison: Compare benchmarks between players - Gold advantage: Check gold_advantage array for momentum swings
Example (parsed match): get_match_details(8123456789) -> { "parsed": true, "metadata": { "match_id": 8123456789, "duration": "45:23", "radiant_win": true, "game_mode": 22 }, "teamfights_summary": { "count": 12, "teamfights": [ { "start": 1245, "end": 1267, "deaths": 6, "gold_delta": 3500, ... } ] }, "players_summary": { "count": 10, "players": [ { "hero_name": "Rubick", "team": "radiant", "kills": 8, "deaths": 5, "assists": 25, "gold_per_min": 380, "benchmarks": { "gold_per_min": {"raw": 380, "pct": 65.3}, "xp_per_min": {"raw": 425, "pct": 58.2}, ... } }, ... ] }, "gold_advantage": [0, 200, 450, 800, ...], ... }
Example (unparsed match): get_match_details(8123456789) -> { "parsed": false, "data": { "players": [...], "radiant_win": true, "duration": "45:23", "match_id": 8123456789 } }
Inputs: match_id (required). Returns: object
get_player_histogramsA
Get the DISTRIBUTION of a specific statistic across matches (performance consistency analysis).
Use this when users ask about PERFORMANCE RANGES or CONSISTENCY:
"How often does [player] get 10+ kills?"
"What's [player]'s typical GPM range?"
"How consistent is [player]'s farming?"
"Show me [player]'s kill distribution"
"What GPM does [player] usually achieve?"
"How often does [player] get a rampage (5+ kills)?"
This shows HOW OFTEN different values occur (e.g., "30 games with 5-6 kills, 20 games with 7-8 kills").
DO NOT use this for simple averages - use get_player_totals() instead. DO NOT use this for win/loss counts - use get_player_win_loss() instead.
Supports both IDs and natural language for flexible querying.
Args: player_name: The Dota 2 player name to search for field: Statistical field to analyze. Accepts variations like: - "kills", "deaths", "assists" - "gpm" or "gold_per_min" or "gold per min" - "xpm" or "xp_per_min" - "cs" or "last_hits" - "damage" or "hero_damage" - "healing" or "hero_healing" - "duration" - "comeback", "stomp", "loss" limit: Number of matches to analyze (default: all available) offset: Skip first N matches (for pagination) lane_role: Filter by lane (accepts "mid", "carry", "pos 1-4", etc.) hero_id: Filter to specific hero (accepts name or ID) included_account_id: Only matches with these teammates (accepts names or IDs) excluded_account_id: Exclude matches with these players (accepts names or IDs) with_hero_id: Require these heroes on player's team (accepts IDs or names) against_hero_id: Require these heroes on enemy team (accepts IDs or names) having: Minimum games played threshold
Returns: List of histogram buckets showing frequency distribution, each containing: - count (int): The value or range (e.g., 10 for "10 kills") - games_played (int): Number of games with this value - win (int): Games won at this performance level - win_rate (str): Win rate at this value (0.0 to 1.0)
The buckets are typically in ranges (e.g., 0-1, 1-2, 2-3 kills) or exact values depending on the field. Higher performance levels often correlate with higher win rates.
Common queries: - Kill distribution: get_player_histograms("kürlo", field="kills") - GPM consistency: get_player_histograms("kürlo", field="gpm") - Hero-specific: get_player_histograms("kürlo", field="last_hits", hero_id="Anti-Mage")
Example: get_player_histograms("kürlo", field="kills", hero_id="Rubick") -> [ {"count": 0, "games_played": 2, "win": 0, "win_rate": 0.0}, {"count": 1, "games_played": 5, "win": 1, "win_rate": 0.2}, {"count": 2, "games_played": 8, "win": 3, "win_rate": 0.375}, {"count": 3, "games_played": 12, "win": 7, "win_rate": 0.583}, {"count": 4, "games_played": 15, "win": 10, "win_rate": 0.667}, {"count": 5, "games_played": 20, "win": 16, "win_rate": 0.8}, ... ]
This tells you: player had 20 games with 5 kills, winning 16 of them (80% win rate at that performance level).
Inputs: field (required), player_name (required), limit, having, +7 more. Returns: Generic wrapper for non-object return types.
get_player_infoA
Get complete Dota 2 player profile with overview statistics.
Use this for initial player lookups when users ask:
"Who is [player]?"
"Tell me about [player]"
"What's [player]'s profile?"
"Show me [player]'s stats"
General player information requests
This is the FIRST tool to use when a user mentions a player - it provides a comprehensive overview. For detailed analysis of specific aspects, use the specialized tools afterward.
Retrieves:
Player profile (name, avatar, profile URL)
Overall win/loss statistics and win rate
Top 10 most played heroes with individual performance metrics
Args: player_name: The Dota 2 player name to search for
Returns: Dictionary containing: - personaname (str): Player's display name - avatarfull (str): URL to player's avatar image - profileurl (str): Steam profile URL - win_count (int): Total wins - lose_count (int): Total losses - win_rate (float): Overall win percentage - fav_heroes (list): Top 10 heroes, each with: - hero_name (str): Hero's display name - games_played (int): Games played with this hero - win_count (int): Wins with this hero - win_rate (float): Win percentage with this hero
Example: get_player_info("kürlo") -> Full profile with top heroes like Rubick, Invoker, etc.
Inputs: player_name (required). Returns: object
get_player_peersA
Get players who frequently play WITH the specified player (teammates, not opponents).
Use this when users ask about TEAMMATES or PARTY MEMBERS:
"Who does [player] play with?"
"Who are [player]'s teammates?"
"What's [player]'s win rate with [teammate]?"
"Show me [player]'s most common party members"
"Who does [player] duo with?"
"Find [player]'s frequent teammates"
This returns players who have been ON THE SAME TEAM as the specified player. Results are sorted by number of games played together (most frequent first).
Supports both IDs and natural language for flexible querying.
Args: player_name: The Dota 2 player name to search for limit: Number of matches to analyze (default: all recent matches) offset: Skip first N matches (for pagination) lane_role: Filter to games where player was in specific lane hero_id: Filter to games where player played specific hero included_account_id: Get stats for SPECIFIC teammate(s) only (accepts names or IDs) excluded_account_id: Exclude specific players from results (accepts names or IDs) with_hero_id: Filter to games with these heroes on player's team against_hero_id: Filter to games against these enemy heroes peers_count: Number of teammates to return (default 5, increase for more results)
Returns: List of teammate statistics (sorted by games together, descending), each containing: - account_id (int): Teammate's account ID - personaname (str): Teammate's display name - last_played (str): Date of most recent game together (e.g., "December 09, 2024") - wins (int): Games won together - games_played (int): Total games played together - win_rate (str): Win rate as percentage string (e.g., "67.5") - average_gpm (float): Player's average GPM when playing with this teammate - average_xpm (float): Player's average XPM when playing with this teammate
Common queries: - Top teammates: get_player_peers("kürlo", peers_count=10) - Specific teammate stats: get_player_peers("kürlo", included_account_id="hotpocalypse") - Teammates on Rubick: get_player_peers("kürlo", hero_id="Rubick") - Recent duos: get_player_peers("kürlo", limit=100, peers_count=5)
Example: get_player_peers("kürlo", peers_count=3) -> [ {"account_id": 123456, "personaname": "hotpocalypse", "last_played": "December 09, 2024", "wins": 45, "games_played": 78, "win_rate": "57.7", "average_gpm": 456.3, "average_xpm": 523.1}, ... ]
Inputs: player_name (required), limit, offset, hero_id, +6 more. Returns: Generic wrapper for non-object return types.
get_player_totalsA
Get aggregated performance statistics across ALL tracked metrics (GPM, KDA, damage, etc.).
Use this when users ask about AVERAGE PERFORMANCE or AGGREGATE STATS:
"What's [player]'s average GPM?"
"How much damage does [player] do per game?"
"What's [player]'s average kills per game?"
"Show me [player]'s average stats"
"What's [player]'s average last hits?"
"How much XPM does [player] average?"
Returns comprehensive statistics for ALL fields tracked by OpenDota including:
Combat: kills, deaths, assists, kda, hero_kills, hero_damage, hero_healing
Economy: gold, gold_per_min, xp_per_min, gold_spent
Farming: last_hits, denies, neutral_kills, lane_kills
Objectives: tower_damage, tower_kills, courier_kills, observer_uses, sentry_uses
And many more...
For simple win/loss counts, use get_player_win_loss() instead. For performance distribution analysis, use get_player_histograms() instead.
Supports both IDs and natural language for flexible querying.
Args: player_name: The Dota 2 player name to search for limit: Number of matches to analyze (default: all available matches) offset: Skip first N matches (for pagination) lane_role: Filter by lane (accepts "mid", "carry", "pos 1-4", etc.) hero_id: Filter to specific hero (accepts name or ID like "Rubick") included_account_id: Only include matches with these teammates (accepts names or IDs) excluded_account_id: Exclude matches with these players (accepts names or IDs) with_hero_id: Require these heroes on player's team (accepts IDs or names) against_hero_id: Require these heroes on enemy team (accepts IDs or names) having: Minimum games played threshold for included data
Returns: List of statistical totals (one object per metric), each containing: - field (str): Name of the statistic (e.g., "kills", "gold_per_min", "hero_damage") - games_played (int): Number of games in this dataset - count (float): Total sum across all games - average (float): Mean value per game (count / games_played)
Common queries: - All average stats: get_player_totals("kürlo") - Mid lane averages: get_player_totals("kürlo", lane_role="mid") - Rubick averages: get_player_totals("kürlo", hero_id="Rubick") - With teammate: get_player_totals("kürlo", included_account_id="hotpocalypse")
Example: get_player_totals("kürlo", hero_id="Rubick", lane_role="mid") -> [ {"field": "kills", "games_played": 80, "count": 418, "average": 5.225}, {"field": "deaths", "games_played": 80, "count": 520, "average": 6.5}, {"field": "gold_per_min", "games_played": 80, "count": 33840, "average": 423.0}, {"field": "hero_damage", "games_played": 80, "count": 1640000, "average": 20500.0}, ... ]
Inputs: player_name (required), limit, having, offset, +6 more. Returns: Generic wrapper for non-object return types.
get_player_win_lossA
Get simple win/loss counts for a player with optional filters.
Use this when users ask about WIN RATES or WIN/LOSS RECORDS:
"What's [player]'s win rate?"
"How many games has [player] won?"
"What's [player]'s win rate with Rubick?"
"How does [player] perform in mid lane?"
"What's [player]'s win rate when playing with [teammate]?"
"How does [player] do against Pudge?"
Returns ONLY win and loss counts. For detailed hero statistics with dates and performance metrics, use get_heroes_played() instead. For aggregate statistics like GPM/XPM/KDA, use get_player_totals() instead.
Supports both IDs and natural language for flexible querying.
Args: player_name: The Dota 2 player name to search for limit: Number of matches to limit analysis to offset: Skip first N matches (for pagination) lane_role: Lane filter. Accepts: - Integer: 1 (Safe), 2 (Mid), 3 (Off), 4 (Jungle) - String: "mid", "safe lane", "offlane", "jungle", "carry", "pos 1", etc. hero_id: Hero filter. Accepts: - Integer: Hero ID (e.g., 86 for Rubick) - String: Hero name (e.g., "Rubick", "Anti-Mage") included_account_id: Filter by teammate. Accepts: - String: Player name (e.g., "hotpocalypse") - List[String]: Multiple player names (games with ANY of these players) excluded_account_id: Exclude matches with these players (accepts names or IDs) with_hero_id: Require these heroes on player's team (accepts IDs or names) against_hero_id: Require these heroes on enemy team. Accepts: - Integer/String: Single hero ID or name - List: Multiple hero IDs or names
Returns: Dictionary with exactly two fields: - win (int): Number of wins matching the filters - lose (int): Number of losses matching the filters
Common queries: - Overall record: get_player_win_loss("kürlo") - Hero-specific: get_player_win_loss("kürlo", hero_id="Rubick") - Lane-specific: get_player_win_loss("kürlo", lane_role="mid") - With teammate: get_player_win_loss("kürlo", included_account_id="hotpocalypse") - Vs counter: get_player_win_loss("kürlo", hero_id="Rubick", against_hero_id="Pudge")
Example: get_player_win_loss("kürlo", lane_role="mid", hero_id="Rubick") -> {"win": 42, "lose": 38}
Inputs: player_name (required), limit, offset, hero_id, +5 more. Returns: object
get_recent_matchesA
Get a player's 20 most recent Dota 2 matches with performance statistics.
Use this when users ask:
"Show me [player]'s recent matches"
"What are [player]'s last games?"
"How has [player] been performing lately?"
"Show me [player]'s match history"
"What heroes has [player] been playing?"
"How did [player] do in their last game?"
Returns detailed statistics for each match including hero played, KDA, farm efficiency (GPM/XPM), damage dealt, and more. Useful for analyzing recent performance trends and hero picks.
Args: player_name: The Dota 2 player name to search for
Returns: List of 20 most recent matches (sorted newest first), each containing: - match_id (int): Unique match identifier - match_date (str): Date match was played (e.g., "December 09, 2024") - duration (str): Game length in MM:SS format (e.g., "45:23") - game_mode (int): Game mode ID (e.g., 22 for All Pick, 2 for Captain's Mode) - hero_name (str): Hero played (e.g., "Rubick", "Anti-Mage") - match_rank_tier (str): Skill bracket (e.g., "Ancient [5]", "Divine [3]") - kills (int): Number of kills - deaths (int): Number of deaths - assists (int): Number of assists - xp_per_min (int): Experience gained per minute - gold_per_min (int): Gold earned per minute - hero_damage (int): Total damage dealt to enemy heroes - tower_damage (int): Total damage dealt to towers - hero_healing (int): Total healing provided to allies - last_hits (int): Creeps killed (farming stat)
Common queries: - Recent performance: get_recent_matches("kürlo") - Check specific player: get_recent_matches("hotpocalypse")
Example: get_recent_matches("kürlo") -> [ { "match_id": 8123456789, "match_date": "December 09, 2024", "duration": "45:23", "game_mode": 22, "hero_name": "Rubick", "match_rank_tier": "Divine [3]", "kills": 8, "deaths": 5, "assists": 25, "xp_per_min": 425, "gold_per_min": 380, "hero_damage": 15234, "tower_damage": 1250, "hero_healing": 2340, "last_hits": 45 }, ... ]
Inputs: player_name (required). Returns: Generic wrapper for non-object return types.
get_recordsA
Get top world record performances in a specific statistical field.
Use this when users ask:
"What's the world record for most kills in a game?"
"Show me the highest GPM ever achieved"
"What's the longest Dota 2 game ever?"
"Who has the world record for [stat]?"
"What are the top performances for [stat]?"
"Has anyone ever gotten 50+ kills?"
Returns the all-time best performances globally for the specified metric, including match details, hero used, and player information. These are the absolute highest values ever recorded in tracked matches.
Supports natural language field names with fuzzy matching and variations.
Args: field: Statistical field to get records for. Accepts variations like: - Combat: "kills", "deaths", "assists", "kda" - Economy: "gold_per_min", "xp_per_min", - Farming: "last_hits", "denies" - Damage: "hero_damage", "hero_healing", "tower_damage" - Other: "duration"
Returns: List of top record performances (sorted by field value, descending), each containing: - match_id (int): Match ID for the record game - start_time (str): Date of the match (e.g., "December 09, 2024") - hero_id (int): Hero ID used in the record - hero_name (str): Hero's display name (e.g., "Rubick", "Anti-Mage") - score (float): The record value achieved (e.g., 45 kills, 1245 GPM) - Additional match details may be included depending on the field
Common queries: - Kill record: get_records("kills") - GPM record: get_records("gpm") or get_records("gold_per_min") - Longest game: get_records("duration")
Example: get_records("kills") -> [ { "match_id": 1234567890, "start_time": "March 15, 2024", "hero_id": 1, "hero_name": "Anti-Mage", "score": 45.0, ... }, { "match_id": 9876543210, "start_time": "January 08, 2024", "hero_id": 86, "hero_name": "Rubick", "score": 43.0, ... }, ... ]
Inputs: field (required). Returns: Generic wrapper for non-object return types.
get_scenarios_item_timingsA
Get win rates for heroes based on item purchase timing (when key items are completed).
Use this when users ask:
"When should I buy [item] on [hero]?"
"What's the optimal [item] timing?"
"How does [item] timing affect win rate?"
"Show me item timing statistics for [hero]"
"Which heroes benefit most from early [item]?"
"What's a good [item] timing?"
"Is [hero] better with early or late [item]?"
"When do pros buy [item]?"
Provides time-segmented data showing how item purchase timing correlates with win rates. Generally, earlier timings have higher win rates for core items, helping identify optimal farming targets and build orders.
Supports natural language item and hero names.
Args: item_name: Item to analyze. Accepts item names like: - Core items: "bfury" (Battle Fury), "radiance", "midas" (Hand of Midas) - Mobility: "blink" (Blink Dagger), "force" (Force Staff) - Defense: "bkb" (Black King Bar), "linkens" (Linken's Sphere) - Boots: "travels" (Boots of Travel), "phase" (Phase Boots) - Support: "wards", "mek" (Mekansm) - And many more item names/abbreviations hero_name: Hero to analyze. Accepts: - Integer: Hero ID (e.g., 86 for Rubick) - String: Hero name (e.g., "Rubick", "Anti-Mage")
Note: At least one parameter must be provided. You can provide:
- Only item_name: See which heroes buy this item and when
- Only hero_name: See all item timings for this hero
- Both: See specific item timing for specific heroReturns: Dictionary organized by timing brackets (formatted as "MM:SS"). Structure depends on parameters provided:
If only item_name provided:
- item_name (str): The item being analyzed
- [time brackets]: Keys like "12:30", "15:00", etc., each containing list of:
- hero_name (str): Hero name
- games (int): Games with this timing
- wins (int): Wins with this timing
- win_rate (str): Win rate percentage (e.g., "65.3")
If only hero_name provided:
- hero_name (str): The hero being analyzed
- [time brackets]: Keys like "12:30", "15:00", etc., each containing list of:
- item_name (str): Item name
- games (int): Games with this timing
- wins (int): Wins with this timing
- win_rate (str): Win rate percentage
If both provided:
- hero_name (str): The hero
- item_name (str): The item
- [time brackets]: Keys like "12:30", "15:00", etc., each containing list of:
- games (int): Games with this timing
- wins (int): Wins with this timing
- win_rate (str): Win rate percentageCommon queries: - Optimal item timing: get_scenarios_item_timings(item_name="bfury", hero_name="Anti-Mage") - Best heroes for item: get_scenarios_item_timings(item_name="blink") - Hero's item timings: get_scenarios_item_timings(hero_name="Anti-Mage")
Example: get_scenarios_item_timings(item_name="bfury", hero_name="Anti-Mage") -> { "hero_name": "Anti-Mage", "item_name": "bfury", "10:00": [{"games": 12, "wins": 10, "win_rate": "83.3"}], "12:00": [{"games": 45, "wins": 32, "win_rate": "71.1"}], "15:00": [{"games": 234, "wins": 145, "win_rate": "62.0"}], "18:00": [{"games": 189, "wins": 98, "win_rate": "51.9"}], "21:00": [{"games": 87, "wins": 38, "win_rate": "43.7"}], ... }
This clearly shows that Anti-Mage's win rate with Battle Fury decreases significantly as the timing gets later. A 12-minute Battle Fury has 71% win rate, while an 18-minute Battle Fury only has 52% win rate, suggesting you should aim for Battle Fury before 15 minutes for optimal results.
Inputs: hero_name, item_name. Returns: object
get_scenarios_lane_rolesA
Get win rates for heroes in specific lane roles segmented by game duration.
Use this when users ask:
"What's [hero]'s win rate in mid lane by game time?"
"How does [hero] perform in offlane at different game lengths?"
"Show me lane role statistics for [hero]"
"Which heroes are best in [lane] for long games?"
"What's the win rate for mid laners at 30 minutes?"
"Does [hero] scale better in late game when played as carry?"
"Which lane should I play [hero] in for short games?"
Provides time-segmented win rate data showing how heroes perform in specific lanes at different stages of the game. This helps understand which heroes thrive in early/mid/late game scenarios and optimal lane choices.
Supports both IDs and natural language for flexible querying.
Args: lane_role: Lane to analyze. Accepts: - Integer: 1 (Carry-Position 1/Hard Support-Position 5), 2 (Mid), 3 (Offlane-Position 3/Soft Support-Position 4), 4 (Jungle/Support) - String: "mid", "safe lane", "offlane", "jungle", "carry", "support" "pos 1-5", etc. hero_name: Hero to analyze. Accepts: - Integer: Hero ID (e.g., 86 for Rubick) - String: Hero name (e.g., "Rubick", "Anti-Mage")
Note: At least one parameter must be provided. You can provide:
- Only hero_name: See all lanes for this hero by game time
- Only lane_role: See all heroes in this lane by game time
- Both: See specific hero in specific lane by game timeReturns: Dictionary with structure depending on parameters provided:
If only hero_name provided:
- hero_name (str): The hero being analyzed
- [lane_role keys]: One key per lane (e.g., "Safe Lane", "Mid Lane"), each containing:
- List of timing data with time, games, wins, win_rate
If only lane_role provided:
- lane_role (str): The lane being analyzed (e.g., "Mid Lane")
- [hero_name keys]: One key per hero, each containing:
- List of timing data with time, games, wins, win_rate
If both provided:
- hero_name (str): The hero
- lane_role (str): The lane
- timings (list): List of timing data with time, games, wins, win_rate
Each timing entry contains:
- time (str): Game duration in MM:SS format (e.g., "25:30")
- games (int): Number of games at this duration
- wins (int): Number of wins at this duration
- win_rate (str): Win percentage as string (e.g., "56.7")Common queries: - Hero in specific lane: get_scenarios_lane_roles(lane_role="mid", hero_name="Rubick") - All heroes in lane: get_scenarios_lane_roles(lane_role="mid") - Hero in all lanes: get_scenarios_lane_roles(hero_name="Anti-Mage")
Example: get_scenarios_lane_roles(lane_role="mid", hero_name="Rubick") -> { "hero_name": "Rubick", "lane_role": "Mid Lane", "timings": [ {"time": "20:00", "games": 145, "wins": 78, "win_rate": "53.8"}, {"time": "30:00", "games": 234, "wins": 125, "win_rate": "53.4"}, {"time": "40:00", "games": 189, "wins": 98, "win_rate": "51.9"}, {"time": "50:00", "games": 87, "wins": 42, "win_rate": "48.3"}, ... ] }
This shows Rubick's mid lane win rate decreases slightly as games go longer, suggesting he's stronger in early-mid game than late game.
Inputs: hero_name, lane_role. Returns: object
request_parse_matchA
Submit a request to parse a specific match for detailed replay analysis.
Use this when users ask:
"Parse match [match_id]"
Parsing a match extracts detailed information from the replay including:
Teamfight breakdowns (who killed who, where, when)
Objectives timeline (tower kills, Roshan, etc.)
Gold/XP advantage graphs over time
Chat logs
Player movement and positioning data
Note: Parsing takes time (usually 1-5 minutes). After requesting parse, wait a bit before calling get_match_details() to retrieve the parsed data.
Not all matches can be parsed - very old matches or matches from private lobbies may not have replay data available.
Args: match_id: The match ID to parse (e.g., 8123456789)
Returns: Dictionary containing parse request status: - job: Information about the parse job (if queued) - status: Current status of the parse request - May include error information if parse cannot be queued
Common workflow: 1. Request parse: request_parse_match(8123456789) 2. Wait 1-5 minutes 3. Get parsed data: get_match_details(8123456789)
Example: request_parse_match(8123456789) -> { "job": { "jobId": "12345" } }
Inputs: match_id (required). Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 18 tools
Each tool targets a distinct resource: player profile, win/loss, heroes, totals, histograms, peers, match details, heroes, items, aghs upgrades, matchups, item popularity, benchmarks, records, and scenario timings. The descriptions explicitly cross-reference similar tools to prevent confusion, e.g., 'For simple win/loss counts, use get_player_win_loss() instead'.
All tools use a 'get_' prefix except for request_parse_match, which breaks the pattern. While most names are clear and consistent (get_player_*, get_hero_*, get_scenarios_*), minor grammatical variations like get_heroes_played (past participle) and get_player_win_loss (compound noun) create slight inconsistency.
18 tools is slightly above the typical well-scoped range of 3-15, but justified given the broad Dota 2 domain covering players, matches, heroes, items, records, and scenarios. The count is manageable and each tool has a unique purpose, though it borders on heavy.
The toolset covers the core lifecycle: player overview, detailed match info (including parse request), hero reference, item reference, and various analytics. Minor gaps exist (no list-all-heroes/items tool, no opponent-only stats, no global hero tier list), but agents can work around these using the provided tools.
Maintenance
Related MCP Connectors
Live competitive-game predictions, meta data and decision tools for AI agents.
Query your app's logs, metrics and incidents from your AI assistant.
Query your Twitch streams, events, supporters, raids & rankings from an AI assistant via OAuth.
Query, explore, and manage your databases through natural conversation.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables interaction with League of Legends game data through the Riot Games API. Allows users to query player statistics, match history, and game information using natural language.-
- FlicenseNot gradedqualityDmaintenanceEnables querying Mobile Legends: Bang Bang hero data including win rates, counters, synergies, and builds via natural language.-
- AlicenseNot gradedqualityCmaintenanceEnables querying Dota 2 data (hero stats, player matches, player heroes) via the STRATZ API, with support for arbitrary GraphQL queries.311 npmMIT
- AlicenseBqualityBmaintenanceEnables LLMs and agents to query Dota 2 matches, players, heroes, teams, and pro scene data through the OpenDota API, with numeric IDs resolved to localized names and readable labels.4254 npmMIT