Skip to main content
Glama

wc3-mcp

An MCP server and Claude Code plugin for making Warcraft III maps with Claude. It works directly on map files and can also drive the World Editor and the game.

  • Maps: create maps (JASS or Lua), and edit map info, imports, object data, GUI/JASS/Lua triggers, terrain, regions, cameras, sounds and placed units, items and doodads.

  • Scripts: build war3map.j/war3map.lua the way the World Editor does, validate them with pjass/JassHelper, and check maps for errors.

  • More content: campaigns (.w3n), AI files (.wai), and models and textures (MDX/MDL, BLP/DDS).

  • Desktop: drive the World Editor and run maps in the game (game_test).

The plugin also installs a skill (wc3-map-making) that tells Claude how to use the tools.

Prerequisites

Requirement

Why

Windows 10/11

The editor and game drivers use the Win32 API (pywin32).

Warcraft III 3.x (Reforged), installed with Battle.net

Game data, pjass and JassHelper are read from the install. The install is found automatically; otherwise set WC3MCP_INSTALL.

uv

Runs the server. On first start it downloads Python 3.13 (if missing) and the Python packages mcp, pywin32, numpy and pillow.

Claude Code

Hosts the plugin (any MCP client works with the manual setup below).

The Battle.net desktop app, logged in (for game_test only)

A game started on its own has no Battle.net session and asks for a login. game_test therefore lets the app start the game, the way its Play button does, and the app hands the game its own session. Log in to the app once yourself with "Keep me logged in"; the tools never type, read or store credentials. The first run stores the map's path in the app's launch options for Warcraft III and restarts the app once; without the app, game_test flashes the game window and waits for you to log in there.

Install uv (PowerShell):

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Related MCP server: UEMCP

Install in Claude Code (plugin)

Run in Claude Code:

/plugin marketplace add BenqsXd/wc3-mcp
/plugin install wc3-mcp@wc3-mcp

Or from a terminal:

claude plugin marketplace add BenqsXd/wc3-mcp
claude plugin install wc3-mcp@wc3-mcp

Restart Claude Code. /mcp should list the wc3 server. The first start takes a minute while uv installs the packages.

Try it: "Create a 64x64 Lordaeron Summer map at C:\Maps\Arena.w3x with a hero for player 1, then test it in the game."

Manual setup (any MCP client, no plugin)

git clone https://github.com/BenqsXd/wc3-mcp
cd wc3-mcp
uv sync
claude mcp add wc3 --scope user -e PYTHONPATH="$PWD\src" -- uv run --directory "$PWD" python -m wc3mcp.server

For other clients, run uv run --directory <repo> python -m wc3mcp.server over stdio with PYTHONPATH=<repo>\src. With plain pip instead of uv, run pip install mcp pywin32 numpy pillow on Python 3.13+, then python -m wc3mcp.server with the same PYTHONPATH. To get the skill too, copy skills/wc3-map-making to %USERPROFILE%\.claude\skills\.

Configuration

All settings are optional environment variables:

Variable

Default

WC3MCP_INSTALL

Warcraft III folder from the registry, else C:\Program Files (x86)\Warcraft III

WC3MCP_DOCUMENTS

Documents\Warcraft III (follows OneDrive redirection)

WC3MCP_HOME

%LOCALAPPDATA%\wc3mcp (working copies, backups, logs, tool copies)

The game install is never modified. Maps are edited in working copies; map_save backs up the original before replacing it.

What's new in 1.5

From the arena map's phases 6-7 and two playtests.

  • game_test gives the user's launcher back. Every run - however it ends - puts the Battle.net app's launch options back and deletes the launch copy; before, a Play in the app started the last tested map in a window. game_status reports what a Play would start today, and game_close restores after a crashed run.

  • Shops. objdata_edit no longer calls isto 0 "unlimited" (it means never in stock) and warns on it; map_validate reports shop_stock, shop_hotkey and icon (a path the game does not have), and balance_report kind="shop" reads a whole shop card with what stops each purchase.

  • Order strings. data_search kind=order says whether a shipped ability uses a string (backed) and whether the game resolved it (resolves); map_validate reports channel_order for a Channel on a string OrderId does not know, and channel_levels for a Channel with no button at some ranks. A game test sweeps every string.

  • Object data. upsert, quiet=["extended_levels"], buttons off the card (arpy -11), the classic-graphics default the World Editor compares art fields with, and learn_card for hero abilities on one learn-menu cell.

  • game_test probe_init runs at map initialization, before any dialog; ProbeSkipDialogs() keeps dialogs shut.

  • data_get kind=native carries observed: what the game was seen to do where a native's name promises more.

  • Smaller: globs for every data_search kind (kind=sound query="*"), placed_list type (one or a list), .mdl paths resolve in data_get, button_cells over every ability, wc3_batch calls inherit path, a snapshot without a label names the parameter, and ui_edit writes a .toc the game can load.

What's new in 1.4.1

The live World Editor calibration, which 1.4 left open, has run. It confirmed the 1.3 cliff rule - the editor rewrote none of the levels terrain_edit settles - and found one defect in the derived pathing.

  • A cell counts as deep water only where its shallowest point is deep. The tools judged a cell by the water depth at its centre, the editor by the cell's shallowest point, so a sloping shore came back one cell narrower than the game has it. Against an editor-saved war3map.wpm that took the disagreement from 0.58 % of the cells to 0.05 %, with the deep-water threshold unchanged.

  • editor_map action="save" answers the Reminder box a map still named "Just another Warcraft III map" gets on every save. It used to wait for that box to close by itself, which hung the save until its timeout.

What's new in 1.4

The rest of the map-session feedback that 1.3 started.

  • Doodads and destructibles with a fixed rotation (dfxr/bfxr) now stand and path at that angle, as the World Editor and the game do. Gates and trees no longer leave slits that only existed in the tools' own footprint.

  • mirror refuses an image that overlaps its source, and axis: "rot4" fills the other three quadrants from one untouched quadrant.

  • A brush area narrower than the 128-unit corner spacing snaps to the nearest corner line (result: snapped) instead of failing.

  • map_flow warns while the terrain pathing it used is derived, and layout_check reports narrowest: the tightest gap on the walk between two start locations.

  • Scenery says what it blocks. data_search carries pathing and blocks per doodad and destructible; scatter and cluster warn about blocking types, and cluster warns when it places fewer than count.

  • data_get lists field names that matched nothing under unknown_fields, and finds an icon or model by its bare name (BTNChainLightning).

  • data_search kind=order: every order string with its targeting and the abilities that use it.

  • Per-level values from a line or a list: {"from": a, "step": s}, {"from": a, "to": b}, [v1, v2, ...], and a text field from {"template": "... {Htb1} ... {level} ..."} rendered per level from the object's own values.

  • objdata_edit warns about a lowercase hero copy and about more than 5 abilities in uhab; the isit range error names the unlimited-stock shape.

  • map_validate gains hero_id_case, hero_ability_slots, hero_skill_points, channel_target and waygate_self; balance_report kind="ability" lists abilities sharing a command-card cell.

  • A map with no triggers stays workable: the Triggers section is put back instead of not_editor_script, and deleting the last trigger warns.

  • damage_detection handles the event as a condition, inside the damage call; new lint rules cover effect leaks, corpses in group enumerations, item-event re-entry and damage handlers registered as actions.

  • Probes write their report at the end (a PreloadGenClear() in probe code no longer loses it) and every run reports handles, with ProbeHandleCount() for sampling around a loop.

  • editor_map action=quit force=true ends an editor stuck in a long operation; map_status reports file sizes and custom/modified object counts per kind.

What's new in 1.3

  • Cliff steps settle themselves. The World Editor keeps neighbouring corners (diagonals included) at most 2 cliff levels apart and rewrites anything steeper when it loads a map. terrain_edit now does it first: an op's own corners keep the level asked for, the ground around them steps, and the result reports cliffs per op. A carved corridor keeps its full width, a 2-tile doorway stays open, and what the tools read is what the editor keeps. Terrain written by an older version is lowered to valid steps on the next edit, with a warning.

  • Profile sections match ids whatever their case, so Ycgd's model (section [YCgd]) is found and model_ok tells the truth.

  • constants_get takes query (over keys and the editor's display names) and returns keys it does not know under unknown beside the ones it answered.

  • A library custom script gets an InitTrig that only creates the trigger, instead of registering a function that takes parameters (which fails pjass).

What's new in 1.2

  • Game tests no longer ask for a Battle.net login. The game started on its own (Warcraft III.exe -launch -loadfile ...) has no session, and signing the Battle.net app in does not give it one - which is why 1.1's sign-in did not help. game_test (login="auto") now has the app itself start the game, the way its Play button does (-launch -uid w3), and the app hands the game its own session: no login screen, and no password read, typed or stored anywhere. The map is copied to one fixed path inside the server's folder and that path is stored in the app's launch options for Warcraft III, so only the first run has to restart the app; the copy is removed when the run closes the game. Verified live: from --exec=launch W3 to the map's loading screen in 13 s, login.screen_seen: false. The app has to be logged in once, with "Keep me logged in".

  • constants_get / constants_edit: the World Editor's Gameplay Constants (war3mapMisc.txt) as a typed tool - the hero level cap, the experience formula, HeroAbilityLevelSkip, revive costs, illusion and aura rules. Every key the game defines with its default and where it comes from; a misspelt key is refused instead of silently ignored, and map_validate warns about one already in a map.

  • More ability ranks now carry values. Raising alev above the base ability's own level count used to leave every per-level field at the base's ranks; objdata_edit repeats the last value into the new ranks, as the World Editor does, and reports what it filled in (extended_levels).

  • objdata_edit accepts the numeric strings data_get hands back ("0", "600.5"), and null clears a field or one of its levels - there was no way to unset aubx before.

  • balance_report reads a hero as a hero: life, mana, armour and damage computed from ustr/uagi/uint and the gameplay constants at level 1 and at the level cap, and compared with the stock heroes instead of Peasants (every hero's unit fields say life 100, mana 0, damage 2).

What's new in 1.1

  • No more logins by hand for game tests (usually): when the game shows the Battle.net login screen, game_test (login="auto") closes it, starts the game once through the Battle.net desktop app - which signs it in with the app's own remembered login, no credentials involved - and runs the map again. When that is not possible it flashes the game window, plays a warning sound and waits login_wait seconds for you, then carries on in the same call. login="wait", "battlenet" and "stop" pick one behaviour. A game that lands on its main menu instead of the map after a login is started again (relaunched).

  • Deep water is unwalkable in every walkability the tools compute (terrain pathing, terrain_render pathing=true, map_flow, layout_check, map_validate reachable): deeper than about 53, measured against the editor's own pathing on the shipped maps (flat water up to 51.95 deep is always walkable there, from 56.4 never). Water heights are now right on every tileset: Ashenvale, Underground, Dungeon and Outland draw their water at their own offset (Outland water is never walkable).

  • map_flow origins/targets: can a unit walk from these points, regions or start locations to those, on the game's 32-unit pathing cells, with every placed object's footprint turned the way the editor turns it? sealed: true proves a tree wall, a moat or a cliff ring closed; a leak comes back with the gap and where it is.

  • The river brush put its water 89.6 too low and ignored cliff levels; it now takes water_level, joins standing water at its level, keeps a promise with walkable: false (a channel units cannot wade) or true (a ford), and reports the depths it made. coast had the same bug and is fixed; every water op reports its stored level and depth range.

  • game_test screenshots=N works while the map runs (the series used to start on a log line the game writes late, so it was empty in real runs); it starts at the probe's start marker and lists frames it could not take with the reason.

  • terrain_render area draws only that part of the map (up to 16 px per tile) and says which rectangle it drew; a heightmap export now returns its base and amount.

  • elements_edit, info_edit and strings_edit take ops_file; terrain_edit quiet silences the unbuildable-tile or stale-files warnings on a scenery map.

  • layout_check says why objects stand on bad ground, per kind: deep or shallow water, cliff, boundary, unwalkable tile, outside the playable area.

  • sound_add kind=ambient uses the settings the World Editor writes for region ambience (DoodadsEAX, volume 127, distances 0/10000/3000); one sound may serve many regions, as it does in the shipped maps.

  • map_snapshot restore no longer marks every file dirty; placed_edit accepts a scale within 0.01 of a fixed-scale type; data_get kind=ability names the order string the game accepted for the three abilities checked in the game.

What's new in 1.0

Ground, shaped like ground. terrain_edit gained the landscape brushes: river (a bed deepest in the middle, water in it, banks painted), coast (floods under a waterline, slopes the sea floor, lays a beach band), ridge, erosion (thermal, so hills stop looking like cones), terrace, blend (a speckled tile seam) and stamp (copy a patch, rotated or mirrored).

Symmetry. mirror on both terrain_edit and placed_edit: build one half or quadrant, then reflect or rotate it, facings turned and, with owner_map, the copies handed to another player.

Heightmaps. A picture over an area goes in ({"op": "heightmap", ...}, any Pillow format, any channel, set or add) and terrain_render heightmap="height"|"cliff"|"water" writes a 16-bit PNG back out over the same corner window.

Does it play? map_flow gives walking distances between starts, to mines and expansions, the narrowest choke on each route and the pockets nobody reaches on foot. melee_check measures the map the way the 272 melee maps shipped with the game are measured and reports every metric against their p10, median and p90 for the same player count. balance_report computes damage per second, effective life and cost efficiency from the object fields and flags a custom object far outside what the stock objects of the same price show.

Systems and text. script_recipe holds nine tested JASS systems as triggers_edit ops (damage detection, unit indexer, respawn, waves, scoreboard, hero tavern, quest, camera, cinematic), each compiled with pjass by the tests. strings_get / strings_edit read and write the string table, including a replace that renames one thing everywhere and an import for a translated table. sound_add imports audio, registers it in war3map.w3s and hands back the script that plays it.

Cheaper calls. wc3_batch runs up to 20 tools in one round trip. wc3_help(topic) holds the op catalogues the tool descriptions used to carry (32.6 KB of descriptions down to 27 KB, with sixteen reference pages a caller asks for when it needs them). terrain_get format="runs" packs rows into [value, count] pairs and format="summary" answers with ranges and counts: a whole map's three layers go from 48 KB to 36 KB or 1 KB.

Tests in the game. ProbeStartAI gives an empty slot a melee AI, so a run has an opponent, and ProbeGold pays for what the test builds.

There are deliberately no map templates: the tools are pieces to compose for the map someone actually asked for, and the skill's references/building-a-map.md is the order to compose them in.

What's new in 0.9

  • Scenery that looks placed. placed_edit gained generator ops that build a layout instead of a heap, with Poisson-disk spacing: forest (density from noise, thinning toward the edge and around clearings, kept to the right tiles), line (even spacing along a path, with offset, sides and facing), town (a row of buildings along every block side facing its street, props between them, streets returned for terrain_edit to pave), cluster (dense in the middle, objects shrinking to the rim) and clear (empty an area first).

  • layout_check reports what a top-down picture cannot show: nearest-neighbour spacing with its spread (a grid stamp is near 0, hand-placed work 0.15-0.45), objects on water or on ground no unit can stand on, the tiles they ended up on, and how much of the walkable map the start locations still reach.

  • game_test screenshots=N saves a series of pictures while the map runs, and the probe helper ProbeCamera(x, y, distance, seconds) walks the camera over the scenery, so the result can be looked at in the game.

  • data_search / data_get kind=native answer from the installed build's common.j, Blizzard.j and common.ai: natives, functions, constants and handle types with their signatures.

  • ui_get / ui_edit read and write custom UI: .fdf layouts (the game's own included), the .toc beside them and the loader script, with a check for unknown frame types, bad anchors, a SetPoint to a frame that does not exist and missing textures.

  • objdata_diff shows what a batch changed against the map on disk or a snapshot, field by field.

  • map_validate gained ability_order (two abilities of one unit sharing an order string) and reachable (start locations cut off from each other, or a preplaced unit its own player cannot walk to).

  • script_validate lint=true gained a desync rule: game state changed, or a random number drawn, inside a GetLocalPlayer() block.

  • ProbeExpect(name, condition) records pass/fail checks, so a test run answers with a verdict (probe.checks, checks_failed) instead of text.

  • terrain_render write_preview=true writes war3mapPreview.tga, the picture the map list shows.

What's new in 0.8

  • Fewer, smaller calls: data_get and objdata_get take a list of ids and answer compactly (raw code -> value, empty fields left out, about a tenth of the size); verbose=true brings back the field metadata.

  • game_test wait=false runs the test in the background and returns at once: game_status reports the run and its whole result when it ends, so the map can be edited while a long probe runs.

  • script_validate lint=true reports the runtime traps pjass cannot see: leaked handles, event data read after a wait, a trigger with an action but no event, endless or operation-limit loops, a handle used after it was destroyed, and a local or parameter named after a JASS type (the cause of a pile of misleading pjass errors).

  • Replacing a trigger keeps its position in its category, so the triggers after it still see its functions; index and after move one on purpose, and map_validate reports a call into a later trigger (function_order).

  • data_get kind=ability returns an orders block with the ability data's order, the World Editor's presets and their targeting, and disagree: true for the ~15 abilities where only one of the two works; map_validate warns when a script issues one of those or an order string nothing knows (order_string).

  • The variable-type error names the editor's own name for a handle type (rect for a region, location for a point) and what to use instead where the editor has no global (itempool, code, boolexpr).

  • The skill is split into a short core plus references/*.md loaded on demand, so a session starts with about 6 KB instead of 34 KB of skill text.

  • The tool log records the result size in bytes and the duration of failed calls too.

What's new in 0.7

  • Probes observe more: ProbeCountEvent / ProbeEventCount count player-unit events, probe_functions adds your own callback functions, and probe.messages includes text shown with DisplayTextToPlayer, DisplayTimedTextToPlayer and the force variants.

  • map_validate warns about object data pitfalls: command_card (buttons sharing a position, including Rally and Cancel), inherited_builds (a copied worker that keeps its base's build list) and locked_ability (a required research nothing in the map offers).

  • terrain_get snaps an area narrower than the corner spacing to the nearest corner line (window.snapped).

  • terrain_render gives tree marks a light rim so they show on dark grass.

What's new in 0.6

  • game_test after login_required: once the user has logged in, the same call continues in the game left open instead of launching again. Each probe run gets its own copy, so an open game never blocks the next run.

  • Terrain buildability: data_search kind=tile gives buildable, walkable and flyable; terrain_edit warns when it paints an unbuildable or unwalkable tile over an area; terrain_get pathing and terrain_render pathing=true show pathing derived from the current terrain before an editor save.

  • objdata_edit create copies a custom object when base is one of the map's custom ids.

  • objdata_get checks the model the map sets (umdl, dfil, bfil) instead of the base model.

  • data_get kind=tile honours fields; file globs match without the storage layer prefix (PathTextures/8x8*).

What's new in 0.5

  • game_test probe_script can call the map's own trigger functions: the probe trigger now comes after all map triggers.

  • game_test presses a key on a loading screen that waits for one (maps with loading-screen text), and ends a run stuck on the Battle.net login screen after about 30 seconds with login_required, leaving the game open for the user.

  • game_test returns Preload strings unescaped, and a run without results hints that an open dialog pauses a single-player game.

  • objdata_get sizes levels by the map's own alev/glvl and lists values beyond it as unused_levels.

  • data_search kind=icon|model|file returns one result per file with ref (the object-data path, .blp icons and .mdl models) and its storage layers.

What's new in 0.4

  • Model checks cover classic graphics, which the World Editor uses: HD-only files such as ZPsh variation 3 no longer pass as present, and data_search lists the variations_ok of a partly usable type.

  • placed_edit warns when a doodad or destructible scale is outside its type's minimum and maximum, which the World Editor clamps on save.

  • triggers_edit script_replace changes one exact piece of a script trigger or the map header without resending the script.

  • map_save compiles a regenerated or edited map script and refuses to save when it does not compile (validation.script).

  • game_test probe_script runs caller test code in the throwaway probe copy, with ProbeReport(text) for reports of any length; result lines at the Preload length limit are listed under truncated.

  • Stock unit Solid texture substituted log lines count as benign.

  • objdata_edit refuses op keys it does not know (a misspelt set no longer drops the values silently).

What's new in 0.3

  • Bulk work stays out of the conversation: ops_file (and script_file for triggers), compact columns/rows adds, a scatter op with weights, exclusion zones and spacing, and created as ref ranges.

  • Missing models are caught before the editor shows them: data_search reports model_ok, data_get lists missing model files, placed_edit warns and map_validate flags every placed object without a model.

  • map_save merge_external=true (and map_open merge_external=true) keeps working-copy edits and takes what a World Editor save recomputed, instead of a lossy choice.

  • Moving a start location with placed_edit updates the player start in war3map.w3i; map_validate warns when they disagree.

  • terrain_render draws doodads and destructibles; data_search tileset= also scopes doodads and destructibles.

  • editor_log returns the editor's viewport messages and missing files (and says the editor writes its log only on quit); game_test reports missing_files, captures the game window behind other windows, and the probe adds hero counts and BJDebugMsg output.

What's new in 0.2

  • Terrain brushes take a rectangle, a path stroke (roads) or no area at all (the whole map), not only circles; results report the 16-slot tile palette.

  • map_new takes and reports fill_tile, and data_search kind=tile can be scoped to a tileset.

  • Script triggers get the editor's InitTrig_ wrapper automatically, and triggers_edit validate=true checks the script in the same call.

  • game_test probe=true reports what a running map looks like without a reporting trigger of your own; screenshots capture the game window only.

  • Clearer failures: map_save names the World Editor when it holds the map, working copies survive a server restart, and map_validate warns while pathing, shadows and the minimap are older than the terrain.

Development

uv sync
uv run pytest              # offline tests (the corpus tests use the local install and ladder maps)
uv run pytest -m editor    # drives the World Editor
uv run pytest -m game      # launches Warcraft III (log in to Battle.net when asked)

Design notes and phase plans are in docs/superpowers.

License

MIT, see LICENSE. Free to use, change and share, for personal projects and others. Warcraft III and its game data belong to Blizzard Entertainment; this project reads a local install and ships none of it.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables Claude to design and build interactive 3D games within the Portals virtual platform through direct API integration. It facilitates automated asset placement, interaction logic configuration, and quest management using natural language commands.
    4
    -
  • A
    license
    B
    quality
    C
    maintenance
    Enables Claude to control Unreal Engine 5 editor, spawn actors, build materials, author Blueprints, and more, with zero plugin installation.
    31
    77 PyPI
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables Claude to create, modify, and delete parts and scripts in OVERDARE Studio worlds via natural language, with automatic backup and rollback for safe operations.
    13
    1
    MIT