Architecture
For readers who know MCP
This is the technical track. New here and just want to make a game? → Set up with your AI agent.
godot-mcp is two halves joined by one seam. Keep the seam clean; keep logic out of the handlers.
The bold arrow is the transport — the editor dials out and reconnects. Once connected, the server still initiates every command; the addon responds. The direction of control never changed, only who dials.
Pages in this section
- The four-layer transport chain — this page
- The Godot addon — the GDScript half: dial-out client, command router, status dock, undo
- Bridge & transport — lifecycle, backoff, timeouts, config
- The JSON envelope — command/response shapes, error codes, id correlation
- Toolset gating — the enabled set, list_changed, version gates
- The safety model — classes, preconditions, dry_run/confirm, where safety lives
- Persistence truth — the owner chain, parent rule, per-target verdicts
- The readiness envelope — reason tokens, poll_ready, why nothing stalls silently
The layers
| Layer | Where | Owns | Never touches |
|---|---|---|---|
| AI client | any MCP host | tool selection, prompt context | Godot, the bridge |
| MCP server | mcp_server/ (Python 3.11+, FastMCP) | safety, preconditions, Pydantic models, tool surface, the WebSocket listener | the Godot editor API |
| Godot addon | godot/addons/godot_mcp/ (GDScript, @tool) | the Godot Editor API, UndoRedo, serialization, reconnection | safety decisions, permissions |
| Godot | the running editor + project | game state | — |
Two rules keep the seam clean:
- Library-first: tool/resource/prompt handlers are delegation only — validate, call a service or the bridge, return a typed model. Zero domain logic in the handler body. Reusable logic lives in
bridge.py,safety.py, andmcp_server/models/. - Service isolation: the bridge client is the only way Python talks to Godot; the server owns all safety; the addon owns all Godot API calls. Neither crosses into the other.
Why four layers at all?
A thinner design (client → addon directly) would skip the MCP server, but then safety, gating, and typing would live in GDScript, version-locked to the editor, and unshareable across stdio/HTTP clients. A thinner one (client → Godot over a socket, no Python) loses typed models and the eval/skill ecosystem that runs on the Python side. The four-layer shape lets each half evolve on its own Cadver/contract terms: the addon pins Godot APIs, the server pins the MCP protocol, and the envelope is the versioned glue.
The bridge seam is also the test boundary: the entire suite runs against a fake addon connection — no sockets, no editor, deterministic — and a separate live-e2e suite exercises the real editor on a self-hosted runner (run it on a PR touching the addon/server, or dispatch it manually).
The data path, end to end
- The agent calls
godot_scene_edit_create_node(parent_path=".", node_type="Node2D", node_name="Player")over stdio. - FastMCP resolves the tool (transform maps the exposed name to the handler), runs safety:
active_sceneprecondition, safety-class params. - The server writes
{id, command: "cmd_create_node", params}to the WebSocket. - The addon's router dispatches to the
cmd_create_nodehandler: resolves the parent, probes the persistence verdict, registers the create withEditorUndoRedoManager, commits. - The handler returns
{id, ok, result: {node_path, created, persisted, …}}. - The server builds a typed Pydantic model (
CreateNodeResult), FastMCP serializes it asstructuredContenton the MCP response. - The agent reads
persisted: trueand proceeds — or readspersisted: false, reason: instanced_child_not_editableand acts on the hint (enable Editable Children, retarget, …).
Each numbered hop has a contract test pinning its shape.