Why batch operations matter for Godot MCP

by Fireal Software · ~7 min read

Ask Claude to build a grid of 10×10 tiles. Without batch tools, that’s 100 individual godot_create_node calls, each one a full MCP round trip — tool call, TS server, WebSocket, GDScript handler, response back up the stack. At 25ms per call on localhost, that’s 2.5 seconds of latency for a task that takes Godot maybe 50ms to actually do.

Multiply by a whole session — an agent doing scene generation, property tweaks, signal wiring — and the call overhead dominates. Batch operations exist because “one call per intent” doesn’t scale.

The batch tools

Five tools in src/tools/batch-tools.ts:

The first is a generic escape hatch. The other four are common-case wrappers.

Chatty vs bulk: the numbers

A back-of-envelope for creating 20 nodes:

Chatty (20 separate calls):

Batch (one call):

6× faster end-to-end, and 20× less context overhead. Both numbers matter, but the context one matters more for long sessions where tool schemas compound.

The N>3 heuristic

AGENT_PROMPT.md in the Godot Catalyst repo tells Claude:

Batch tools exist for bulk property changes and bulk node creation. Use them instead of N individual calls when N > 3.

Three is the cutoff where the batch overhead (writing the array arg) starts saving over three individual calls. For 1-3 calls, individual is about as fast and has clearer error semantics. For 4+, batching wins.

Claude mostly follows this without prompting after a few sessions, but reinforce it in your project’s CLAUDE.md if you see the model regressing to chatty mode:

Use batch tools when operating on more than 3 nodes:
- godot_batch_create_nodes for creating multiple nodes
- godot_batch_set_properties for updating multiple nodes
- godot_batch_delete_nodes for removing multiple nodes
- godot_batch_operations for mixed operations

How batch_create_nodes works

Input:

{
  "nodes": [
    {
      "parent_path": "/root/Main",
      "node_type": "CharacterBody2D",
      "node_name": "Player",
      "properties": {"position": "Vector2(100, 100)"}
    },
    {
      "parent_path": "/root/Main/Player",
      "node_type": "Sprite2D",
      "node_name": "Sprite"
    },
    {
      "parent_path": "/root/Main/Player",
      "node_type": "CollisionShape2D",
      "node_name": "Collision"
    }
  ]
}

Creates sequentially in array order. Parent-before-child is the caller’s responsibility — if you ask to create a child before its parent exists, you get an error for that entry and the rest continue.

Return is an array of {name, status, result|error} entries matching the input. Partial success is the expected behavior: if three nodes create and one fails, you get three successes and one error. The caller decides whether to roll back.

How batch_set_properties works

Input:

{
  "updates": [
    {"node_path": "Player", "properties": {"speed": 300, "jump_height": 500}},
    {"node_path": "Player/Sprite", "properties": {"modulate": "Color(1,0,0)"}},
    {"node_path": "Enemy1", "properties": {"active": false}}
  ]
}

Under the hood this runs Promise.allSettled — every update goes out in parallel. The editor handles them on its main thread in the order the WebSocket delivered them, but from the MCP server’s perspective they’re concurrent.

Return shape is again an array of successes and errors. Partial success is normal.

When parallelism bites you

godot_batch_operations runs operations in Promise.allSettled, which means concurrent fire. For read-only queries this is safe. For mutations this is mostly safe because Godot’s editor main thread serializes them, but there are corner cases.

Creating a parent and a child in the same batch, in parallel? Nondeterministic. The child creation might land before the parent exists. This is why godot_batch_create_nodes specifically goes sequential — the sequential order is load-bearing for parent-child creation patterns.

If you’re using godot_batch_operations for mutations that have ordering dependencies, split them into separate batches and await each. Or just use the specialized batch tools where the ordering is sorted out for you.

Batch delete needs reverse order

godot_batch_delete_nodes sorts the input by path depth, deepest first:

const sorted = [...node_paths].sort(
  (a, b) => b.split("/").length - a.split("/").length
);

Deleting Player/Sprite before Player is safe. Deleting Player before Player/Sprite would leave a dangling reference (or more realistically, the second delete would fail because the node is already gone).

The sort is one line but it’s the kind of thing an agent would miss if you asked it to write the tool from scratch. Wrapping this in godot_batch_delete_nodes means Claude doesn’t have to think about it.

When not to batch

Batch tools are for “I have a list of things to do”. They’re not for “I need to decide what to do based on the result of the first operation”.

Claude usually picks correctly. If you see it doing chatty calls where a batch would work, prompt it: “batch the node creations into one call”.

The runtime tool bridge

There’s also godot_execute_snippet which isn’t technically a batch tool but serves a similar purpose — run arbitrary GDScript in the editor process. For very complex batch operations where even batch_operations is awkward, writing a GDScript block and calling it as a snippet can be the cleanest path.

This is the “escape hatch above the escape hatch”. Use sparingly — arbitrary code execution has obvious risks, and a well-designed tool call is almost always cleaner than a GDScript string.

The design principle

Every tool call has cost. Optimize for calls that do real work per call. If you find yourself making 50 small calls, there should be a batch tool for that pattern. If there isn’t, the tool set is incomplete.

The batch category exists because “make a grid” is a common ask, “update all these nodes” is a common ask, “clean up this subtree” is a common ask. Each of those should be one tool call, not twenty.

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