Why & how it works
Two minutes here and the rest of the docs will click.
The problem
An AI agent can already edit Godot files on disk — but it edits blind:
- it can't see the live scene (instanced scenes,
ownerrelationships, the open tab, the selection, unsaved edits); - it can't run anything, so "does this work?" is unanswerable without a human pressing play;
- its file writes race the editor's importer, so its next check can read a stale class cache;
- a mistake has no undo unless the editor's own stack was involved.
godot-mcp closes that gap: the agent talks to the live editor over MCP, so every action lands on the project you're actually looking at — with undo, verification, and honest reporting.
Why it's built this way (the short version)
- Editor-first, not file-first. The editor owns state the disk doesn't — unsaved changes,
owner, editable-instance flags, the selection, the importer cache. Every mutation goes through the editor's own APIs (EditorUndoRedoManager,EditorInterface,ProjectSettings), so your undo stack covers the agent's actions and the editor's own rules apply. - The server listens; the editor connects out. The editor is the app that comes and goes, so it's the one that dials and reconnects (with backoff). The server can start before Godot, before the agent's first call, or in Docker — launch order never matters. The direction of commands is unchanged: the server still initiates every one.
- Safety lives in the server, not the addon. The addon only executes; it never decides whether an action is allowed. Safety classes, previews,
confirmgates, and preconditions all live inmcp_server/safety.py, so a misbehaving addon can't bypass the confirm gate. - Honest results over optimistic ones. A result that reads like success but isn't is the worst failure mode for an agent with no human watching. So nothing ever says "done" without saying what happened to disk (see the honesty fields below).
- Game-agnostic by design. No game vocabulary ships here. A game-specific project layers its own tools and prompts on top; anything that only makes sense for one game belongs there, not here.
The ideas every page assumes
1. The toolset (the gating unit)
Every tool carries exactly one category tag. core (diagnostics and toolset management) is always on; everything else ships gated off. The agent switches one on with godot_enable_toolset("scene_edit"), which updates a server-global enabled set and announces the change (notifications/tools/list_changed, plus a subscriptions/listen stream on the 2026-07-28 protocol). Because many clients ignore the notification, the enable result also carries the enabled tools' names and input schemas, so even a client that never refreshes can call them.
Why gate at all? 194 tools in the context window degrade tool-selection and burn tokens. The default surface (core + inspection — 24 tools) is enough to orient and inspect; the agent pulls in scene_edit only when it means to edit. godot_list_toolsets() is always authoritative.
A few toolsets are version-gated (scene_edit, input_map, tilemap, scene_3d need Godot 4.4+); enabling one on an older editor returns a structured PRECONDITION_FAILED … [required=godot_version].
2. The safety class
Every tool is one of:
| Class | Meaning | Extra params |
|---|---|---|
read_only | Never mutates | none |
mutating | Reversible editor change (UndoRedo) | dry_run: bool = False |
destructive | Deletes/overwrites; maybe irreversible | dry_run and confirm: bool = True |
runtime | Controls game execution | varies |
dry_run=True runs the checks and returns what would happen — nothing is sent. confirm=True is required for destructive tools; without it the tool returns PRECONDITION_FAILED … [required=confirm]. godot_undo covers every UndoRedo-wrapped change (batches above 20 nodes bypass the stack and say so via undoable: false).
3. The envelope (and why errors look like this)
// server → addon (command)
{ "id": "42", "command": "cmd_create_node", "params": { "parent_path": ".", "node_type": "Node2D", "name": "Player" } }
// addon → server (response)
{ "id": "42", "ok": true, "result": { "node_path": "Player", "created": true } }Failures never cross as stack traces:
{ "id": "42", "ok": false, "error": "PRECONDITION_FAILED", "hint": "Open a scene before creating nodes.", "required": "active_scene" }error is from a fixed enum; hint tells the agent what to do next; required names the precondition to satisfy. The server shapes these into FastMCP ToolErrors, so the agent sees the same structured failure on every transport.
Almost every response is JSON like the above. The one exception is a screenshot: the same round-trip returns PNG bytes that the server hands the model as an image, so the agent can look at the editor viewport or the running game — see Seeing the scene.
4. Naming: godot_<toolset>_<action>
The exposed name is the gating toolset made visible: create_node in the scene_edit toolset is godot_scene_edit_create_node. The prefix is the gate — an agent can read the name and know what it must enable. Always-on core/meta tools are just godot_<action> (godot_health_check, godot_enable_toolset). The mapping is applied centrally and enforced by a contract test.
5. Honesty fields
Results carry structured self-reporting that reads as ground truth:
| Field | On | Meaning when it matters |
|---|---|---|
persisted + reason + hint | most mutations | whether a scene save will keep the change; instanced_child_not_editable, node_not_owned, embedded_in_other_resource, group_from_base_scene are the reason tokens |
undoable | batch_set_property | false above 20 nodes — undo will not revert the batch |
aborted_at, skipped_count, hint | run_commands | which sub-command stopped the batch and how many never ran |
may_have_applied | any mutating/destructive tool, on TIMEOUT | the timed-out mutation may still run on the editor (it drains commands serially) — re-read state before retrying, don't double-apply (#644) |
rescan_pending | get_parse_errors | the editor's rescan hadn't flushed; a fresh-class_name error may be stale cache |
reason (readiness) | poll-based tools | why the tool is still pending (capture_pending, scan_in_flight, …) |
scanning | asset_import_get_status, rescan_filesystem | the editor's filesystem scan is in flight |
6. Contract versioning
godot_get_server_info carries contract_version (a breaking-change counter, currently 1) and min_compatible_contract. A client is compatible when min ≤ client ≤ contract. Additive changes (new tools, optional fields) never bump it; removals and renames do. The CalVer build version (2026.10.15) moves every release and is not the compatibility signal.
Deep dive next: Under the hood — the four-layer chain, the bridge, and the full contract.