← Back to blog

How We Built a Real-Time MCP Server for Godot

Inside VberAI's Godot MCP architecture: how a real-time Model Context Protocol server bridges Godot's editor to AI clients without freezing the scene tree.

Published
  • vberai
  • godot
  • mcp
  • architecture
  • realtime

Why “Real-Time” Matters for an MCP Server in Godot

Most MCP demos work against a REST-shaped world: the model asks a question, a tool returns JSON, and nobody cares if the round trip took two seconds. Godot is different. The editor owns a live scene tree, resource imports, and a play-mode loop. If your MCP server blocks the main thread—or only sees a stale dump of the project—AI clients stop feeling like co-pilots and start feeling like remote file editors with extra steps.

When we designed Godot MCP for VberAI, the brief was explicit:

  • An AI assistant (Cursor, Claude, Windsurf, and similar MCP clients) must operate the editor, not just read .gd files from disk
  • Actions like create node, rename, attach script, and query selection must complete while the editor stays responsive
  • Play-mode and edit-mode contexts must stay honest—tools should fail loudly when an operation is unsafe mid-play

This post is the engineering story: how we built a real-time MCP server that sits next to Godot instead of pretending the project is a static repo.

The Problem With “File-Only” MCP for Engines

A naïve approach is tempting:

  1. Point the MCP server at the project folder
  2. Expose read_file / write_file / list_dir
  3. Let the model invent GDScript and hope the editor reloads nicely

That works for documentation sites. It fails for engine work because:

  • Scene ownership lives in memory — unsaved .tscn diffs, open scenes, and editor selection are invisible on disk
  • Import pipelines are stateful — writing a PNG is not the same as “resource ready with correct import settings”
  • Signals and node paths are graph data — string edits break wiring silently
  • Latency compounds — every “did the node appear?” confirmation becomes another full filesystem scan

We needed an MCP surface that mirrors what a human already sees in the Godot dock: hierarchy, inspector-shaped properties, and resource handles—not only path strings.

Architecture Overview

At a high level, Godot MCP is three cooperating layers:

MCP Client (Cursor / Claude / …)
        │  JSON-RPC over stdio or local transport

MCP Server (VberAI Godot bridge process)
        │  command queue + result envelopes

Godot Editor Plugin (GDExtension / editor plugin)
        │  deferred calls on the main thread

Editor Scene Tree / ResourceDB / Script Editor

Layer 1 — MCP tool surface

Tools are intentionally small and verb-shaped:

  • godot_get_scene_tree — snapshot of the edited scene with node types and paths
  • godot_create_node / godot_set_property
  • godot_attach_script / godot_run_script_snippet (guarded)
  • godot_list_resources / godot_get_selection

We avoid one giant do_anything tool. Smaller tools are easier to validate, easier to log, and harder for a model to abuse into unbounded patches.

Layer 2 — the real-time bridge

The bridge is where “real-time” actually lives:

  • A bidirectional channel between the MCP process and the editor plugin (local socket or named pipe in development; product builds may wrap this behind the VberAI desktop helper)
  • A command queue that serializes mutations so two overlapping tool calls cannot race the scene tree
  • Request IDs + acknowledgements so the MCP client can wait for “applied” without polling the filesystem
  • Heartbeat / editor liveness so clients know when Godot quit mid-session

Layer 3 — main-thread safety in Godot

Godot’s UI and scene APIs are not free-threaded. The plugin never mutates nodes on the MCP I/O thread. Instead:

  1. MCP command arrives on the bridge thread
  2. Payload is enqueued
  3. Editor deferred callback runs on the main thread (call_deferred / idle frame hook)
  4. Result envelope returns success, structured error, or “retry after play mode ends”

That pattern is boring by design. Boring is what keeps “real-time” from becoming “random editor freezes.”

Making It Feel Real-Time (Without Lying About Latency)

“Real-time” here does not mean magical zero latency. It means the feedback loop matches how humans work in-editor.

Snapshot vs stream

Early prototypes returned the entire scene tree on every call. That collapsed on big open-world setups. We switched to:

  • Shallow snapshots by default (root + one level, or selection-focused)
  • Path-scoped reads when the model already knows a subtree
  • Optional change tokens so subsequent calls can ask “what changed since X?” instead of re-serializing everything

Diff-friendly results

Tool results include:

  • Canonical NodePath strings
  • Type names (CharacterBody2D, Control, …)
  • Property keys that map cleanly to inspector fields
  • Explicit warnings when a write was applied but the scene is still dirty / unsaved

Models iterate faster when results look like UI state, not like a blog post of free-form text.

Bounded play-mode behavior

Play mode is where half of “AI broke my project” tickets start. Our rule set:

ModeAllowedBlocked or gated
EditScene mutations, resource queriesDestructive project deletes without confirm
PlayMostly read / query runtime nodesStructural edits to the edited scene
TransitionWait / retry hintsSilent no-ops

Clear errors beat cleverness. If the model cannot edit during play, the MCP response says so in structured form—so the client can tell the user to stop play, then retry.

Hard Problems We Hit (and Kept)

1. The editor is not a database

Node order, owner relationships, and packed scenes interact in ways that look simple in a GIF and messy in a .tscn. We leaned on Godot’s own APIs (Node, EditorInterface, resource loaders) rather than inventing a parallel scene model that would drift.

2. Scripts vs scenes as two sources of truth

Attaching a script is not the same as ensuring the script compiles and the class name resolves. The server reports compile/attach outcomes separately. That stopped a class of “tool said success, Inspector shows nothing” failures.

3. Multi-window / multi-project sessions

Developers open more than one Godot instance. The bridge binds to an explicit editor session ID so tool calls do not land in the wrong project after a weekend of sleep + reopen.

4. Security boundaries

An MCP server that can rewrite scenes is powerful. Local-first transport, explicit tool allowlists, and no silent cloud exfiltration of the full project tree are non-negotiables. “AI helper” and “remote shell over your game” must stay distinct product categories.

How This Fits Next to AI Studio and the Rest of VberAI

Godot MCP is the engine operator. Complementary pieces:

  • VberAI AI Studio (AI Studio) — design-to-engine structure for Figma/PSD → Control hierarchies; MCP then wires buttons and renames nodes after import
  • Unity MCP / Cocos MCP — same MCP idea, different editor hosts and safety rules
  • AI Super Matting — cleans alpha before textures become engine resources the MCP later assigns

The shared thesis: AI should touch the live production surface (editor, assets, scenes), not only the repo as text.

Practical Tips If You Are Building Your Own Engine MCP

  1. Queue mutations on the engine main thread — never pretend game engines are shared-nothing servers
  2. Prefer small tools with schemas — validation beats prompt poetry
  3. Return NodePaths and types, not prose — models need operable handles
  4. Encode play/edit modes in every response — ambiguity here destroys trust
  5. Measure round-trip to “visible in dock” — not only JSON encode time

If you only optimize the MCP process and ignore the editor bridge, you will ship a fast hallucination loop.

Conclusion

Building a real-time MCP server for Godot meant treating the editor as a live collaborator: a queued, main-thread-safe bridge; tools shaped like editor actions; and honesty about play-mode limits. File-only MCP is easier—and incomplete for scene-native work.

Want the shipped path instead of a weekend prototype? Start with VberAI Godot MCP, connect your preferred MCP client, and try a trivial first tool call: list the current scene tree, then create one node under the selection. That single loop—query → mutate → see it in the dock—is the product. Everything else is reliability engineering around it.

More guides you might like