Give your agent a live line into a running Unity Mono development build: reflect types, evaluate C# expressions, and read & write ECS state, with no code injection.
Generic tooling: works with any dev-Mono Unity game exposing the Mono Soft Debugger (SDB) agent, not just Cities: Skylines II.
The plugin ships unity-devtools-mcp, a C# MCP server that attaches to the game's Mono Soft Debugger agent, the same channel an IDE debugger uses, and drives the game from the outside: no mod, no DLL, no game modification. Through live mirror invokes it resolves types, calls methods, and queries, reads, and writes Unity ECS entities, components, and dynamic buffers, in the running simulation.
It also ships the unity-driving skill: the operating manual for the tools, field-verified
procedures for the session lifecycle, suspend windows, entity identity, and the traps the tool
schemas cannot tell.
Note
Generic, but developed against Cities: Skylines II. The server makes no assumptions about a specific game: it discovers any dev-Mono Unity process by its SDB port signature. It is developed and verified against Cities: Skylines II (development Mono build), the reference target.
Warning
Writes are live. Component and buffer writes, and method invokes, mutate the running simulation immediately and persist; there is no undo. Test against a throwaway save.
- What it looks like in practice
- Tools
- How it works
- Requirements
- Install
- Configuration
- Troubleshooting
Short, abridged transcripts, replayed from real live sessions against Cities: Skylines II.
You: Rename one of my high schools after you, go ahead.
Agent:
ecs_queryonGame.Buildings.Schoolwithlabel: Game.UI.NameSystem:GetRenderedLabelNamelists the six schools with their in-game names: "Winchelsea", "Leechburg", "Fowey"… Then oneevalcalls the game's own name system:world.GetExistingSystemManaged(typeof(Game.UI.NameSystem))with.SetCustomName(entity, name). Reading the label back returns"Claude High School", and it is on the map.
You: Top the city treasury up to a round 100 million.
Agent:
find_typesonGame.City.PlayerMoneyshows a tidy component:m_Money: Int32plusAdd/Subtract.ecs_queryfinds exactly one entity carrying it, the city singleton. Oneecs_set_componentonm_Moneydoes it, and the result carries the proof in a single call:before: m_Money=61343907, after: m_Money=100000000.
You: Unpause at max speed and jump the clock six hours; I want to see the evening commute.
Agent:
evalgrabs the simulation system viaworld.GetExistingSystemManaged(typeof(Game.Simulation.SimulationSystem)): itsselectedSpeedreads0, the city is paused. AssigningselectedSpeed = 4unpauses it, and a beat latersmoothSpeedreads1.83: that is the eased, actual speed caught mid-climb, so the simulation is genuinely running, not just flagged. ThenGame.Simulation.TimeSystem.DebugAdvanceTimejumps the clock six hours:normalizedTimemoves from0.42(mid-morning) to0.67(early evening). Evening rush incoming.
Bare names for the generic Unity tools, an ecs_* prefix for the ECS layer:
| Tool | What it does |
|---|---|
status |
Find dev-Mono game processes and their SDB port (no attach) + session state. |
suspend / resume |
Hold the game frozen across calls: a consistency window for multi-step edits. |
detach |
Free the exclusive debugger slot (e.g. for your IDE); reattach is automatic. |
find_types |
Resolve a type live by fully-qualified name; optionally list its members. |
eval |
Evaluate a C# statement sequence against the live game, like an IDE debugger would. |
ecs_query |
Count/list entities having ALL given components, optionally labeled via a system call. |
ecs_get_component / ecs_set_component |
Read, or field-write with read-back, one entity's component. |
ecs_get_buffer / ecs_buffer_edit |
Read, append to, or remove from a DynamicBuffer. |
There is no "attach" tool: the first tool that needs the VM attaches lazily, the session persists, and a dropped connection (or game restart) re-discovers and reattaches on the next call.
- Mono Soft Debugger protocol, the wire protocol behind "Attach to Unity" in your IDE. The
server embeds Unity's own
Mono.Debugger.Softclient and talks to the game's SDB agent directly. - Client-side C# evaluation: SDB has no expression-evaluation command, so
evalparses your C# with Roslyn (parse-only) and interprets it as a sequence of mirror primitives (member reads, property getters, invokes, indexers), the way IDE debuggers evaluate watch expressions. - The game keeps running between calls; each operation opens a brief suspend window around
itself (invokes need a suspended VM). The
suspend/resumetools hold a window across calls when several reads/writes must see one consistent state; the game is fully frozen meanwhile. - Always resumed: every path (failure, detach, server shutdown, even a killed connection) resumes the game; a closed socket auto-resumes the VM as the last-resort safety net.
- One debugger at a time: while the session is attached, an IDE debugger cannot attach to the
game, and vice versa;
detachfrees the slot.
- A Unity game running as a development Mono build with the SDB agent live (for Cities: Skylines II: a dev build; a retail build exposes no SDB port and cannot be driven).
- Windows (process/port discovery is netstat-based for now).
- The .NET 10 SDK to launch the server. No build step: the plugin launches the
UnityDevtools.McpNuGet dotnet tool throughdotnet dnx, version-pinned to the plugin (downloaded on first launch, cached after).
Add the marketplace, then install the plugin (see the repository README for the marketplace overview).
Claude Code:
/plugin marketplace add CitiesSkylinesModding/agents-plugins
/plugin install unity-devtools@csmodding
Once enabled, Claude Code autoloads the unity MCP server from the plugin's
.mcp.json.
Codex CLI:
codex plugin marketplace add CitiesSkylinesModding/agents-plugins
codex plugin add unity-devtools@csmoddingOnce enabled, Codex autoloads the unity MCP server from
.codex-plugin/mcp.json.
Either way, run /mcp to confirm it connected, then ask the agent to call status to check the
MCP is working properly (with the game running).
All are optional; with nothing set, the server auto-discovers any running dev-Mono Unity game by its SDB port signature.
| Variable | Default | Purpose |
|---|---|---|
UNITY_MCP_PROCESS |
(unset) | Process-name prefix narrowing discovery. |
UNITY_MCP_PORT |
(discovery) | Pin the SDB port instead of discovering it. |
UNITY_MCP_HOST |
127.0.0.1 |
Debugger host. |
On Claude Code, the plugin's .mcp.json forwards them from your environment.
On Codex CLI, plugin servers get no environment block, so the server always starts with the
defaults above (auto-discovery).
- "no dev-Mono Unity game found": the game is not running, or is not a development Mono build
(no SDB agent). Run
statusto see what discovery finds. - "several dev-Mono Unity candidates found": other processes listen in the SDB port range;
narrow discovery with
UNITY_MCP_PROCESSor pinUNITY_MCP_PORT. - Attach fails while your IDE debugger is connected: the SDB slot is exclusive. Detach the IDE
(or call
detachbefore attaching the IDE); it looks like a connection refusal, not a "slot is taken" message. - Read the MCP server logs: Claude Code records each server's connection attempts and stderr
to per-project
.jsonlfiles under the Claude CLI cache, in anmcp-logs-unity/folder keyed by the project path; the newest.jsonlshows why a launch failed:- Windows:
%LocalAppData%\claude-cli-nodejs\Cache\<project-path>\mcp-logs-unity\
- Windows: