A lightweight godot MCP written in gd script.
Godot MCP (pure-GDScript)
A Model Context Protocol server for the Godot game engine, implemented entirely as a Godot editor plugin — no Node, no external daemon, no compiled binary. The editor is the MCP server.
Why
Two problems with existing Godot MCPs motivated a fresh build:
- No Node requirement. Existing servers need a Node/npm runtime installed and version-matched. We want zero non-Godot dependencies.
- Multiple Claude Code instances at once. stdio-transport servers are spawned per client, so each instance fights to own the connection to Godot. We want many agents driving one editor simultaneously.
How it works
The Godot editor plugin hosts an HTTP MCP server (Streamable HTTP transport) on loopback. Claude Code connects to it as a remote server — it never spawns a process.
Claude Code #1 ─┐
Claude Code #2 ─┼──HTTP──▶ Godot editor plugin ──▶ in-process tools
Claude Code #3 ─┘ (TCPServer @127.0.0.1:8765) (scene/script/...)
- No spawning → no collisions. Every instance points at the same URL and just connects. Multi-instance is structural, not bolted on.
- No bridge. Tools run in-process on the editor's main thread. There is no second protocol, no WebSocket, no glue process to maintain.
- Truly zero extra runtime. Ships as one addon.
git cloneintoaddons/, enable, done.
Connecting Claude Code
claude mcp add --transport http godot http://127.0.0.1:8765/mcp
or project-scoped .mcp.json:
{
"mcpServers": {
"godot": { "type": "http", "url": "http://127.0.0.1:8765/mcp" }
}
}
Note: there is no "command" field — Claude Code only ever connects.
Lifecycle & scope
- The editor must be open — the editor hosts the server. No headless per-operation spawning.
- One editor = one server on one port (default
8765, overridable). Run two projects → two editors → two ports. - Loopback only, no auth in v1. Optional bearer token is a later add.
Why pure GDScript (not Rust/Python)
A separate daemon's only job would be translating MCP ⇄ Godot. But all Godot work already lives in GDScript, and every Godot editor API is main-thread only — so tool execution serializes on the editor's thread regardless of transport language. Rust's concurrency advantage never materializes; the bridge is pure overhead. Hosting MCP directly in the editor deletes the daemon, the cross-process protocol, and the per-platform distribution problem in one move.
The one cost — GDScript has no built-in HTTP server — is bounded: MCP's
Streamable HTTP transport permits plain POST → application/json responses, so
v1 needs only HTTP/1.1 request parsing + JSON-RPC, not long-lived SSE streams.
v1 scope (35 tools implemented)
The daily agent loop is read code/scene → edit → run → see errors:
- Files/scripts:
read_file,list_dir,search_project,create_script,edit_script(whole-file overwrite or find/replace viafind/replace/replace_all),validate_script,list_scripts(recursive.gdlisting withclass_name/extends),get_open_scripts(scripts open in the editor + the current one). Note: dedicatedread_script/search_in_files(godot-mcp-pro parity) are intentionally covered by the genericread_file/search_projectrather than added as separate tools. - Project:
get_project_settings,list_project_resources,get_project_info, plus MCP resourcesgodot://project/info,godot://scene/current,godot://script/current - Scenes (implemented):
get_scene_tree,get_node_properties,create_node(accepts optionalproperties),delete_node,modify_node,duplicate_node,move_node,rename_node— all operate on the currently-open scene; the mutating tools (create_node,delete_node,modify_node,duplicate_node,move_node,rename_node) are registered as editor undo/redo actions (Ctrl-Z works) - Signals/groups/resources (implemented):
get_signals,connect_signal,disconnect_signalwire node signals to methods (persistent connections that serialize into the.tscn);get_node_groups,set_node_groups,find_nodes_in_groupmanage persistent group membership across the edited scene;add_resourceinstantiates aResourcesubtype and assigns it to a node property;set_anchor_presetapplies aControllayout preset;attach_scriptattaches an existing.gdto a node — all mutating tools are undoable - Editor (implemented):
get_output_logandget_editor_errorsread engine log/error output captured by a ring-bufferLoggerthe plugin installs viaOS.add_logger(Godot exposes no public API to read the editor's Output dock, so capturing our own stream is the only robust path);clear_outputclears the MCP capture buffer, not the editor's Output dock;get_editor_screenshotcaptures the editor window (PNG to ares://path or base64) and requires a windowed, non-headless editor — in--headlessit returns a clean error;reload_projecttriggers an editor filesystem rescan. Note:reload_plugin(godot-mcp-pro parity) is intentionally deferred — a plugin disabling/reloading itself would tear down the HTTP server and_processloop servicing the very request, so it can't safely respond. - Input map (implemented):
get_input_actionsandset_input_actionread and write the project input map (theinput/*entries inProjectSettings, each an{deadzone, events}action). They operate on the project definition and work fully headlessly (no running game needed), and are distinct from the deferred running-game input-injection tools (v2 runtime).set_input_actionsupports partial updates (omitevents/deadzoneto keep existing values);eventsare Godotvar_to_strInputEventstrings, and non-InputEvententries are skipped and reported in anerrorslist. - Run/feedback (pending):
run_scene,stop_scene,get_errors,get_console_log— not yet implemented
Deferred to v2: in-game runtime tools (screenshot, input injection, live node query) — they need a second live connection from the running game. Near-full tool parity (signals, resources, project settings, input maps, asset generation, visualizers) is also a later expansion.