The architecture behind Godot Catalyst

by Fireal Software · ~7 min read

Three processes, two protocols, one goal: let an agent drive the Godot editor as if it were sitting at the keyboard.

┌───────────────┐  stdio JSON-RPC   ┌────────────────┐  WebSocket (6505)  ┌────────────────┐
│  MCP Client   │ ◄──────────────►  │  TS MCP Server │ ◄──────────────►  │  Godot Plugin  │
│  (Claude Code)│                    │    (Node.js)   │                    │  (GDScript)    │
└───────────────┘                    └────────────────┘                    └────────────────┘

This post is about why every arrow is there and what it costs.

The MCP Client (Claude Code, Cursor, Windsurf, etc.) spawns the TypeScript server as a subprocess and talks to it over stdin/stdout. This is the standard MCP transport. The TS server registers 240+ tools, handles the JSON-RPC request/response protocol, and translates tool calls into WebSocket messages.

src/index.ts is 20 lines. It creates an McpServer, wires it to StdioServerTransport, creates a WebSocketBridge, and calls registerAllTools. That’s the whole entry point.

Why subprocess + stdio? MCP is designed this way for sandbox reasons. The client controls the server’s lifecycle. If Claude Code exits, the server exits. No orphaned processes. No “did I remember to kill that server” problem.

This is the unusual part. Most MCP servers are self-contained — they do their work in the server process, maybe hit an external API. Godot Catalyst can’t, because the thing it’s driving (the Godot editor) is a separate process with its own scene tree, undo stack, plugin lifecycle, and scripting runtime.

The TS server can’t call EditorPlugin.add_node directly. GDScript lives inside the editor. Editor state lives inside the editor. To mutate a scene, the TS server has to ask the editor to do it.

The WebSocket on port 6505 is the answer. src/transport/websocket-bridge.ts is a WebSocket client. godot-plugin/addons/godot_catalyst/mcp_server.gd is a WebSocket server running inside the Godot editor. They speak JSON-RPC 2.0 to each other.

When Claude calls godot_create_node, the flow is:

  1. Claude → TS server (stdio): tool call
  2. TS server → Godot plugin (WebSocket): JSON-RPC method node.create with params
  3. Godot plugin → NodeHandler.create(): GDScript executes the mutation via EditorUndoRedoManager
  4. Godot plugin → TS server (WebSocket): JSON-RPC response
  5. TS server → Claude (stdio): tool result

Five hops. In practice it’s ~20-40ms per call on localhost. The hot loop for a scene edit is dominated by step 3 (Godot doing actual work).

Why not stdin to Godot directly?

Because Godot doesn’t have a well-defined stdin protocol for the editor. The engine has headless mode with stdin scripting, but the editor process does UI and expects interactive input. You can’t pipe JSON into a running editor and have it respond.

WebSocket sidesteps this. Godot has TCP networking in GDScript. Writing a WebSocketPeer-based server inside an EditorPlugin is ~150 lines of GDScript. The editor runs normally, the plugin opens a TCP listener, and external processes talk to it.

Alternatives considered and rejected:

The plugin side

godot-plugin/addons/godot_catalyst/ is what ships to your Godot project. Structure:

The executor pattern means adding a new tool is: write a GDScript function in the right handler, return a dict, done. The TS side gets a matching tool registration and the dispatch just works.

The debugger bridge

Editor plugins can only touch editor state. Running games live in a separate process. When Claude wants to inspect a running game’s node tree or evaluate GDScript at runtime, there’s another hop involved.

Godot’s built-in EditorDebuggerPlugin is how IDEs get stack traces from running games. Godot Catalyst uses it as a two-way channel. catalyst_debugger_plugin.gd sends messages from the editor to the running game; runtime/catalyst_game_agent.gd (an autoload registered by the plugin) receives them and responds with runtime state.

This is how godot_eval_gdscript, godot_get_runtime_tree, and godot_inspect_runtime_node work. The tool calls go from Claude → TS server → editor plugin → debugger bridge → running game → back up the stack.

The autoload is registered automatically on plugin activation:

if not ProjectSettings.has_setting("autoload/" + AUTOLOAD_NAME):
    add_autoload_singleton(AUTOLOAD_NAME, AUTOLOAD_PATH)

Idempotent. If you already had an autoload with that name, the plugin won’t clobber it.

Hot reload without the plugin toggle cycle

Developing new handlers means iterating on GDScript. The normal Godot workflow is: edit the script, toggle the plugin off in Project Settings, toggle it back on. This works but is slow — every toggle takes 2-3 seconds and occasionally crashes the editor.

godot_reload_handlers skips the cycle. It calls tool_executor.reload() which re-imports the handler scripts and rebuilds the action dispatch table. The WebSocket connection stays up. No toggle needed.

This is one of the tools tagged minimal in the mode map — available everywhere because it’s load-bearing during development and never hurts to have in production sessions.

Connection management

The TS server’s WebSocketBridge has three states: disconnected, connecting, connected. src/transport/connection-manager.ts handles the state machine.

On tool call, if not connected, the call throws:

“Not connected to Godot (state: disconnected). Is the Godot editor running with the Godot Catalyst plugin enabled?”

On disconnect (editor closed, plugin toggled off, network blip), the bridge schedules exponential-backoff reconnect. Claude might get one error call, but the next attempt lands once the editor is back. No manual MCP restart needed.

The initial connect on MCP server startup uses connectWithRetry, which silently retries. This covers the common case of “Claude Code starts up faster than the Godot editor” — the MCP server is ready, it just waits for the plugin to come online.

Status panel

The bottom panel in Godot is where you see what’s happening. status_panel.tscn shows:

“Copy MCP Config” button writes a JSON snippet you can paste into your client’s settings. “Docs” opens the portal.

Agents getting lost debugging themselves is a real failure mode. The status panel lets you see in real time that Claude is actually calling tools, which ones, how often. If calls stop landing, you’ll see it.

The three-tier tradeoff

More layers mean more places to fail. But the design gives us:

Two processes wouldn’t have been enough. One process would have been impossible — Godot’s plugins can’t speak MCP natively, and the MCP spec doesn’t describe how to drive a desktop editor.

Turn Claude into a Godot co-developer

Godot Catalyst is an MCP server with 240+ tools for Godot 4.x. GDScript LSP, DAP debugging, offline parsing, asset pipelines. 7-day free trial, $25 one-time.

Try Godot Catalyst