MCP server providing unified query access to ~462 AzerothCore game datastores — DBC binary files, SQL tables, SQL overlays, and auxiliary stores. Plus terrain/pathfinding queries against MMap navmesh data, creature spawn analysis, database state audit, encounter rollups, record digests, mod configuration lookup, and C++ enum decoding. Exposes twelve tools (query, lookup, list, spawns, dbversion, encounter, travel, explain, config, enums, sql, terrain) over the JSON-RPC based Model Context Protocol.
AzerothCore stores game data in four categories of datastores. This server gives any MCP-compatible client (LLM, IDE plugin, CLI) a single entry point to query all of them by name, with automatic name resolution, type-aware field annotation, and smart SQL routing.
| Category | Count | Description |
|---|---|---|
dbc_backed |
~112 | Binary .dbc files loaded into packed C structs, with optional SQL overlay (*_dbc tables) |
sql_objectmgr |
~95 | ObjectMgr SQL tables — creature templates, gameobjects, items, quests, gossip, etc. |
sql_manager |
~48 | Tables loaded by other singleton managers (SpellMgr, PoolMgr, GameEventMgr, LootStore, …) |
sql_auxiliary |
~171 | Discovered SQL tables not in the static registry |
For the full technical reference on how each category is loaded in AzerothCore, see docs/datastores/README.md.
Query any datastore by name. Accepts a DBC file name ("Spell"), SQL table name ("quest_template"), or C++ struct name ("SpellEntry").
| Parameter | Type | Description |
|---|---|---|
name |
string | Required. Datastore name. Use lookup to find valid names. |
id |
number | Primary key for O(1) lookup. If a filter is also given, both must match (AND). |
filter |
object | Named field filters. Supports $like / $ilike patterns. |
fields |
array | Select fields by index [38, 39] or name ["BaseLevel", "SpellLevel"]. Strict: unknown names are errors with suggestions. |
limit |
number | Max records to return (default 100). |
compact |
boolean | Strip null/zero fields (default true). |
annotate |
boolean | DBC rows as legacy per-field arrays with index/type/sql_column/source (default false = flat {name: value} rows). On SQL tables, attaches column types. |
hints |
boolean | Include field_references / referenced_by cross-reference metadata (default false). |
links |
boolean | Add metadata.links: one-hop relation map (related NPC/item/spell names) for the returned rows (default false; capped at 25 rows × 10 fields). |
resolve |
boolean or array | Resolve type-specific data fields. true = all, or ["dbc", "sql", "loot"]. |
resolve_max |
number | Max items per loot table resolution (default 10). Use 0 for unlimited. |
Result shape: rows are flat {field: value} objects. id/row_index lookups return a single object (or a specific error if the id is absent / does not match the filter); filter and unconstrained queries return a list. Locale arrays (e.g. name[0..15]) collapse to a scalar (or a list of non-empty values).
Examples:
query(name="Spell", id=118)
→ O(1) lookup for Polymorph, ~1 KB flat object
query(name="quest_template", filter={"Title": {"$ilike": "%murloc%"}})
→ SQL $ilike search across all quest titles
query(name="gameobject_template", id=180013, resolve=true)
→ Returns gameobject data with data[0-19] annotated (lockId, lootId, spellId, etc.)
query(name="quest_template", id=46, links=true)
→ metadata.links: quest NPC (Guard Thomas), required item (Torn Murloc Fin), reward items
query(name="Spell", id=118, fields=[38, 39])
→ Only BaseLevel and SpellLevel fields
For DBC-backed stores that also have an SQL overlay table, query merges both sources — SQL overlay data replaces or supplements the binary DBC data, and errors report which source failed.
Get schema and metadata for any datastore. Resolves by C++ struct name, SQL table, DBC file, or store variable (e.g. sSpellStore).
| Parameter | Type | Description |
|---|---|---|
query |
string | Required. Name to resolve. |
detail |
string | "schema" (default) — full field list. "summary" — 10 sample fields. |
Returns the per-field mapping (index, name, type, sql_column, plus notes/references where available), referenced_by cross-references, and access hints (e.g. sSpellStore.LookupEntry(id) -> SpellEntry const*). SQL-native tables additionally list live columns as name:type [PK].
Examples:
lookup(query="SpellEntry")
→ Full schema with all 183 fields, SQL columns, and cross-references
lookup(query="creature_template", detail="summary")
→ Compact 10-field sample + live SQL columns from the database
lookup(query="sSpellStore")
→ Resolves store variable to SpellEntry with access pattern hints
List available datastores with optional search and category filtering.
| Parameter | Type | Description |
|---|---|---|
search |
string | Filter by struct name, table, DBC file, or store variable. |
category |
string | "all" (default), "dbc_backed", "sql_objectmgr", "sql_manager", "sql_auxiliary". |
limit |
number | Max entries to return (default 50). When truncated, metadata.total reports the full count. |
Examples:
list(category="dbc_backed")
→ First 50 DBC-backed stores with field counts (total reported in metadata)
list(search="Quest")
→ Stores matching "Quest" in struct name, table, or DBC file
Creature spawn analysis for a creature_template entry — total world spawns, per-map breakdown (with map names resolved from DBC), and sample positions. Read-only; uses the creature, creature_template and map/Map.dbc tables. No mod required — works on any azerothcore-wotlk install.
| Parameter | Type | Description |
|---|---|---|
entry |
number | Required. creature_template entry (the creature id). |
map |
number | Restrict the analysis to a single map ID. |
limit |
number | Sample positions to return (default 3, max 20). |
Examples:
spawns(entry=32820)
→ Wild Turkey: 3125 spawns, map 0 (Eastern Kingdoms), sample positions
spawns(entry=1)
→ template exists but has_world_spawn=false (Waypoint, GM-only)
Database state audit for this install — no arguments. Reports the core/DB version row (acore_world.version), per-database SQL update state (RELEASED/ARCHIVED/CUSTOM/MODULE/PENDING counts plus applied/pending totals), pending update names, and which of the four AzerothCore databases are present. acore_playerbots only exists with mod-playerbots; its absence is reported as installed: false, not an error.
Examples:
dbversion()
→ core_version (with fork/branch marker), db_version, per-DB tables/applied/pending,
mod_playerbots_installed=true|false
C++ enum decoder — indexes every named enum in the AzerothCore source tree (~1,044 enums, ~17.7k members, one cached scan) and resolves magic numbers. Answers "what does this value mean?" (enums(enum='Mechanics', value=17) → MECHANIC_POLYMORPH) without the agent grepping the source. The source tree is optional (override with ACORE_SRC_ROOT); absence is a clean error.
| Parameter | Type | Description |
|---|---|---|
enum |
string | Exact enum name. |
value |
number | Value to decode (with enum=) or scan across all enums. |
member |
string | Member-name substring for reverse lookup (with enum=). |
search |
string | Case-insensitive substring match on enum names. |
Examples:
enums()
→ 1044 enums, 17713 members, largest enums by size
enums(enum="Mechanics", value=17)
→ MECHANIC_POLYMORPH (src/server/shared/SharedDefines.h)
enums(enum="SpellEffects") full value table (capped)
enums(value=17) which enums contain 17 (ambiguity note)
mod-playerbots configuration index — indexes playerbots.conf.dist (setting → default + line) and the C++ GetOption call sites that read each key in one cached pass. Answers "what does this setting do / what's its default / where is it used" without reading the 900-line conf or grepping the mod. The mod source tree is optional (override its location with PLAYERBOTS_ROOT); absence is a clean error.
| Parameter | Type | Description |
|---|---|---|
key |
string | Exact setting key → full detail incl. code refs. |
search |
string | Case-insensitive substring match on key names (capped 50). |
rebuild |
boolean | Force re-scanning the mod conf + source. |
Examples:
config()
→ 888 settings, 384 code refs, counts by prefix
config(search="strategy")
→ EnableNewRpgStrategy, Max/MinRandomBotChangeStrategyTime, ...
config(key="AiPlayerbot.RandomBotCombatStrategies")
→ default "", conf line 1370, code ref: PlayerbotAIConfig.cpp:459
Agent-friendly digest of a single record from any datastore — wraps the query pipeline (annotate + links) and distills it to: a one-line summary, the non-trivial fields (capped at 20, name pinned first), one-hop cross-references with resolved target names, and source provenance. For DBC-backed stores it reports which fields the live SQL overlay changed vs the vanilla DBC (overlay_overrides), and flags overlay-only records (ids that exist only in the *_dbc overlay tables, as in this reduced build).
| Parameter | Type | Description |
|---|---|---|
name |
string | Required. Datastore name. |
id |
number | Required. The record's primary key (0 allowed). |
Examples:
explain(name="Spell", id=118)
→ Polymorph (dbc) — 46 fields with values, 20 shown — 4 cross-reference(s)
explain(name="Spell", id=19)
→ SWORDSPECIAL (DND) — overlay-only record (no vanilla DBC row)
explain(name="quest_template", id=46)
→ Bounty on Murlocs — starters/enders: Guard Thomas, reward items resolved
Map/instance encounter rollup — what is in this place. Top creatures by spawn count with level ranges, rank (0=normal 1=rare 2=elite 3=worldboss) and loot item names for the top 5; top game objects (ores, doors, chests); instance metadata (script, allow_mount) when the map is an instance; and the mod-playerbots travel graph size when installed. Read-only, acore_world based — works on any azerothcore-wotlk install.
| Parameter | Type | Description |
|---|---|---|
map |
number | Required. Map id. |
limit |
number | Max creatures to list (default 20, max 100). |
Examples:
encounter(map=43)
→ Wailing Caverns (instance_wailing_caverns): Druid of the Fang (19, rare),
Deviate Lasher (19), ... with loot; travel graph: 10 nodes / 1968 points
Inspect the mod-playerbots travel graph and verify paths against the MMap navmesh — no running server needed. Three modes (all read-only; requires acore_playerbots, its absence is a clean error):
| Parameter | Type | Description |
|---|---|---|
map |
number | Required. Map id. |
node |
number | Node mode: node details + named neighbours (capped 50 each). |
from / to |
number | Path mode: decode the stored path between two nodes (requires both). |
verify |
boolean | Path mode: check each point against MMap ground (default true; degrades to a note where the install has no .map data for the map). |
Examples:
travel(map=0)
→ 644 nodes, 3010 edges, 295623 path points, sample named nodes
travel(map=0, node=0)
→ "Human start" + neighbours (Goldshire innkeeper, Northshire Valley spirithealer, ...)
travel(map=0, from=0, to=2776)
→ "Human start" → "Elwynn Forest Goldshire": 103 path points + navmesh_verification
Execute raw SQL queries with automatic database routing and typo suggestions.
| Parameter | Type | Description |
|---|---|---|
query |
string | Required. SQL query (SELECT, INSERT, UPDATE, DELETE only). |
Features:
- Smart routing — automatically directs queries to
acore_world,acore_characters, oracore_authbased on table name - Typo suggestions — suggests correct table and column names on errors
- Safety — blocks DROP, TRUNCATE, ALTER, GRANT, REVOKE
- Context hints — e.g. empty loot_template results suggest trying questitem tables
Examples:
sql(query="SELECT entry, name FROM creature_template WHERE entry = 1")
→ Routes to acore_world
sql(query="SELECT id, username FROM account LIMIT 5")
→ Auto-routes to acore_auth
sql(query="SELECT * FROM creature_templat LIMIT 1")
→ Error with suggestion: "Did you mean: creature_template?"
Query map, VMap, and MMap terrain data — terrain height, liquid, area IDs, navmesh tiles, and cross-tile pathfinding on the Detour navmesh.
| Parameter | Type | Description |
|---|---|---|
subcommand |
string | Required. One of: list_maps, list_tiles, height, liquid, area, coord, tile_info, vmap_info, tile_stats, map_info, pathfind. |
mapId |
string or number | Map ID (numeric) or name (e.g. "Eastern Kingdoms", "571"). |
x, y, z |
number | World coordinates (required by subcommand). |
tileX, tileY |
number | Tile coordinates 0-63 (for tile-level queries). |
data_type |
string | "maps", "vmaps", or "mmaps" (for list_tiles, tile_info). |
x1, y1, z1, x2, y2, z2 |
number | Start/end coordinates (for pathfind). |
flying |
boolean | Ignore height constraints (for pathfind, default false). |
Subcommands:
| Subcommand | Purpose |
|---|---|
list_maps |
List all maps with file counts (ADT, VMap, MMap) |
list_tiles |
List tiles for a map (maps, vmaps, or mmaps) |
height |
Terrain height at (x, y) |
liquid |
Liquid type/height at (x, y, z) |
area |
Area table ID at (x, y) |
coord |
Convert world coordinates to grid/tile |
tile_info |
MMap/VMap tile header info |
vmap_info |
VMap model info for a tile |
tile_stats |
Navmesh statistics for a tile (poly count, vertex count) |
map_info |
MMap navmesh parameters for a map |
pathfind |
A* pathfinding between two points with cross-tile support |
Pathfinding:
The pathfind subcommand runs A* on the Detour navmesh with on-demand tile loading, cross-tile external edge resolution, and funnel-algorithm corridor steering. Coordinates are in world space. The AC world→Detour transform (world_y, world_z, world_x) is applied automatically.
terrain(subcommand="height", mapId=0, x=1620, y=1530)
→ {"height": 52.34, "map_id": 0, "position": {"x": 1620, "y": 1530}}
terrain(subcommand="list_tiles", mapId=571, data_type="mmaps")
→ 433 MMap tiles for Wintergrasp
terrain(subcommand="tile_stats", mapId=571, tileX=23, tileY=24)
→ {"poly_count": 3639, "vert_count": 5832, "tile": [23, 24]}
terrain(subcommand="pathfind", mapId=571, x1=4683, y1=3824, z1=355, x2=4538, y2=3230, z2=403)
→ {"found": true, "distance": 649.0, "raw_path_length": 38, "smooth_path_length": 3,
"smooth_path": [{"x": 4683, "y": 3824, "z": 355}, {"x": 4528, "y": 3244, "z": 357}, {"x": 4538, "y": 3230, "z": 403}]}
Cross-tile pathfinding works automatically: tiles are loaded on-demand as the A* search expands, and external edges (DT_EXT_LINK) are resolved by finding matching polygons in neighbor tiles via BV-tree search. Long-distance paths (2000+ yards) across many tiles are supported.
The resolve parameter on query enables type-aware field resolution for tables whose fields change meaning based on a type column. Resolution types:
"dbc"— resolve to DBC entries (e.g.LockEntry,SpellEntry,MapEntry)"sql"— resolve to SQL tables (e.g.quest_template,gossip_menu,page_text)"loot"— expand loot templates into item lists with names
Any table with cross-reference metadata in datastore_registry.json gets automatic field resolution via _resolve_generic(). Fields like faction, lootId, spellId are resolved to their target entries by name.
In addition to generic registry-driven resolution, these tables have dedicated resolver modules that enrich results with custom data:
| Table | Resolver module | What it resolves |
|---|---|---|
gameobject_template |
resolvers/gameobject.py |
type-aware data[0-19] annotation (lockId, lootId, spellId …) |
smart_scripts |
resolvers/smart_scripts.py |
EVENT_ID/ACTION_ID/TARGET_ID → enum names + value meaning |
quest_template |
resolvers/quest.py |
starter/ender NPCs, POIs, quest chain (prev/next/breadcrumb) |
conditions |
resolvers/condition.py |
polymorphic SourceType → entity name, ConditionType (~49 types: AURA, QUEST, ITEM, ALIVE, CLASS, etc.), TYPEID/GENDER/RACE enums |
achievement_criteria_data |
resolvers/achievement_criteria.py |
CriterionType-specific field interpretation |
item_template |
resolvers/item.py |
loot template for openable items (Flags & 0x04) |
Spell (DBC) |
resolvers/spell.py |
cast conditions from conditions table with full enum resolution |
query(name="quest_template", id=4512, resolve=true)
→ Start/ender NPCs, chain info, POIs, plus faction/spell/item refs
query(name="Spell", id=15698, resolve=true)
→ Cast conditions: "OBJECT_ENTRY_GUID(UNIT)=creature_template [Cursed Ooze], NOT_ALIVE"
query(name="item_template", id=11912, resolve=["loot"])
→ Openable item → 6x Empty Cursed Jar, 6x Empty Tainted Jar
query(name="conditions", filter={"SourceTypeOrReferenceId": 17, "SourceEntry": 15698}, resolve=true)
→ Polymorphic: source type name, condition type enum, resolved entity names
query(name="gameobject_template", id=12345, resolve=["loot"], resolve_max=20)
→ Expand loot template into up to 20 item names
- Python 3.9+
pymysql— installed viarequirements.txtinto.venv- Access to an AzerothCore MySQL instance (for SQL tools)
- DBC binary files and
DBCfmt.hfrom the AzerothCore source/build - MMap data files (
.mmtile,.mm) for terrain/pathfinding queries
| Variable | Default |
|---|---|
ACORE_DBC_PATH |
/root/azerothcore-wotlk/env/dist/bin/dbc |
ACORE_FORMAT_FILE |
/root/azerothcore-wotlk/src/server/shared/DataStores/DBCfmt.h |
ACORE_MMAP_PATH |
/root/azerothcore-wotlk/env/dist/bin/mmaps |
ACORE_VMAP_PATH |
/root/azerothcore-wotlk/env/dist/bin/vmaps |
ACORE_MAP_PATH |
/root/azerothcore-wotlk/env/dist/bin/maps |
DB_HOST |
Auto-detected |
DB_PORT |
3306 |
DB_USER |
Auto-detected |
DB_PASSWORD |
Auto-detected |
DB_NAME |
acore_world |
When DB_HOST and DB_USER are empty, the server attempts auto-detection from common AzerothCore configuration files.
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python3 server.pyRecommended: launch with the .venv Python (.venv/bin/python3). Without pymysql the server falls back to a mysql CLI subprocess — it works (params are interpolated and MYSQL_PWD is honored) but is slower.
If run via an MCP client, point the command at the venv's Python:
"command": ["/path/to/acore-data/.venv/bin/python3", "server.py"]To test manually:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | .venv/bin/python3 server.pyThis project ships a first-class pi bridge at .pi/extensions/acore-data.ts. pi loads it automatically and registers the server's tools (query, lookup, list, sql, terrain) as native pi tools. It hardcodes no paths or credentials — it inherits the ambient environment and the server self-configures (DBC path defaults + DB creds auto-detected from worldserver.conf). To use a non-default project root or a non-default AzerothCore layout, export the relevant vars (ACORE_DATA_ROOT, DB_*, DBC_PATH, …) in pi's environment.
# Regression suite — 21 tests, the output-shape/strictness/protocol contract
.venv/bin/python3 tests/test_regression.py
# Integration tests — 75 tests (requires running MySQL + DBC files)
.venv/bin/python3 tests/test_integration.py
# Fast unit tests — 20 tests (no DB)
.venv/bin/python3 tests/test_helpers.py
# Everything at once
.venv/bin/python3 -m pytest tests/ -qOutput-size baselines (for LLM context budgeting):
.venv/bin/python3 scripts/capture_output_sizes.py /tmp/after
.venv/bin/python3 scripts/capture_output_sizes.py --compare /tmp/before /tmp/afteracore-data/
├── server.py # MCP server entry point (JSON-RPC over stdio)
├── datastore_registry.json # Static metadata for all ~467 datastores
├── requirements.txt # pymysql >= 1.1, pytest >= 7.0
│
├── core/
│ ├── annotation.py # DBC field annotation, filter conversion, schema errors
│ ├── database.py # MySQL connection (pymysql), table discovery, smart routing
│ ├── dbc.py # WDBC binary file reader
│ ├── enums.py # Shared enum dicts: condition types, SOURCE_TYPE, TYPEID …
│ ├── formats.py # DBCfmt.h parser (format strings → field types)
│ ├── registry.py # Datastore registry: name resolution, fuzzy matching
│ ├── type_resolver.py # Dispatcher + generic registry-driven resolution engine
│ ├── resolvers/ # Specialized table-specific resolver modules
│ └── terrain/ # Terrain data: map/vmap/mmap readers, navmesh pathfinding
│ ├── coords.py # World ↔ tile coordinate conversions
│ ├── map_reader.py # ADT map file reader (height, liquid, area)
│ ├── vmap_reader.py # VMap model file reader
│ ├── mmap_reader.py # MMap navmesh tile index reader
│ ├── detour_parser.py # Detour tile parser (polygons, BV tree, vertices)
│ ├── tile_manager.py # On-demand tile loading, cross-tile link resolution
│ └── pathfinder.py # A* search, corridor steering, cross-tile pathfinding
│
│ ├── __init__.py # Resolver registry (table_name → func)
│ ├── gameobject.py # data[0-19] annotation for GAMEOBJECT_TYPE subtypes
│ ├── smart_scripts.py # EVENT_ID/ACTION_ID/TARGET_ID enum + value meaning
│ ├── quest.py # Starter/ender NPCs, POIs, chain info (prev/next/breadcrumb)
│ ├── condition.py # Polymorphic: SourceType → entity, ConditionType (~49 types), TYPEID/GENDER/RACE enums
│ ├── achievement_criteria.py # CriterionType-specific field interpretation
│ ├── item.py # Loot template for openable items (Flags & 0x04)
│ ├── spell.py # Cast conditions from `conditions` table with full enum resolution
│ └── ref_utils.py # Shared helpers: resolve_dbc_ref, resolve_sql_ref, batch_resolve_sql, resolve_loot_ref
│
├── tools/
│ ├── query.py # Unified query tool (DBC + SQL + overlay merge)
│ ├── lookup.py # Schema/metadata lookup tool
│ ├── list.py # Datastore listing tool
│ ├── sql.py # Raw SQL execution with routing and suggestions
│ └── terrain.py # Terrain/pathfinding tool (map/vmap/mmap, A* navmesh)
│
├── tests/
│ ├── test_integration.py # Integration tests (live DB)
│ └── test_helpers.py # Unit tests
│
├── docs/datastores/ # Technical reference for AzerothCore datastore internals
│ ├── README.md # Overview of DBC pipeline, SQL overlay, format strings
│ ├── dbc-backed-stores.md
│ ├── sql-objectmgr-stores.md
│ ├── sql-manager-stores.md
│ ├── sql-auxiliary-stores.md
│ └── cross-reference.md
│
├── generators/ # Registry tooling (docs/datastores is human reference)
│ ├── generate_registry.py # docs->registry check (read-only; --write to regenerate)
│ └── generate_supplementary.py
│
└── scripts/ # Utility scripts for cross-refs, column mappings, etc.
├── add_cross_references.py
├── update_sql_column_mappings.py
├── audit_registry.py # registry health gate (structural drift checks)
└── archive/ # one-off registry migrations (phase_a–f, triage)
MCP Client (LLM / IDE / CLI)
│
│ JSON-RPC over stdio
▼
server.py ───────────────────────────────────────
│
├─► registry.py datastore_registry.json
│ Name resolution, fuzzy matching (462 entries)
│
├─► tools/query.py ┌──► dbc.py .dbc binary files
│ Unified query │ WDBCReader (Spell.dbc, Map.dbc, …)
│ │
│ ├──► database.py MySQL
│ │ Smart routing (acore_world, _characters, _auth)
│ │
│ ├──► annotation.py Field annotation
│ │ DBC ↔ SQL merge + type-aware resolution
│ │
│ └──► type_resolver.py gameobject_template
│ data[] → lockId/lootId/spellId
│
├─► tools/lookup.py Schema + live SQL columns + cross-refs
│
├─► tools/list.py Category filtering + search
│
├─► tools/sql.py Raw SQL with routing + typo suggestions
│
└─► tools/terrain.py Terrain queries + pathfinding
(map/vmap/mmap, A* navmesh)