Skip to main content
Glama

MCP npm Godot Godot Editor Godot Runtime .NET release Discord Stars Docker Image License Stand With Ukraine

Godot MCP is an AI-powered game development assistant for the Godot Editor. Connect Claude, Cursor, Copilot, or any MCP-aware agent to Godot and let it inspect and drive your project — create nodes, edit scenes, manage resources and scripts, capture screenshots, and more.

Godot-MCP is the Godot counterpart of Unity-MCP: a C# editor addon that exposes Godot Editor operations as AI Tools and connects them to an MCP server through the same hosted cloud backend (ai-game.dev) that powers Unity-MCP — or your own self-hosted server. The MCP / reflection stack is not forked: it is shared with Unity-MCP and consumed from nuget.org as PackageReferences.

💬 Join our Discord Server — Ask questions, showcase your work, and connect with other developers!

Features

  • ✔️ AI agents — Use the best agents from Anthropic, OpenAI, Google, or any other provider with no vendor lock-in

  • ✔️ 24 built-in Tools — MCP Tools across 9 families for operating the Godot Editor, led by RQL: one query language for the whole editor

  • ✔️ C# & GDScript — Read, create, and update both .cs and .gd scripts, and attach them to nodes

  • ✔️ Scene & Node control — Query and edit the scene tree, Godot properties (GDScript variables included), .tscn scenes and .tres/.res resources with RQL — bulk, all-or-nothing, undoable

  • ✔️ Visual feedback — Capture viewport, camera, and isolated-node screenshots the LLM can inspect

  • ✔️ Call anything — call CallMethod("name", [args]) reaches any Godot method (GDScript ones too); in a running game, find and call C# methods via ReflectorNet

  • ✔️ Cloud or self-hosted — Connect to ai-game.dev out of the box, or point at your own server

  • ✔️ Natural conversation — Chat with AI like you would with a human

AI Game Developer — Godot MCP

Quick Start

Get up and running from a terminal using the godot-cli (the Godot analog of unity-mcp-cli) — no manual file copying or csproj editing required:

# 1. Install godot-cli
npm install -g godot-cli

# 2. (Optional) Scaffold a fresh Godot C# project — skip if you already have one
godot-cli create-project --dotnet ./MyGodotProject

# 3. Install the godot_mcp addon: downloads addons/godot_mcp/ from the matching
#    GitHub release, adds the required NuGet packages + the extension-catalog
#    EmbeddedResource to your .csproj, and enables the plugin in project.godot —
#    all idempotently
godot-cli install-plugin ./MyGodotProject

# 4. Sign in to the ai-game.dev cloud — OAuth 2.1 device login (opens a browser,
#    saves a machine-wide credential the editor plugin auto-adopts; no token to copy)
godot-cli login

# 5. Pick an AI agent (Claude Code, Cursor, Copilot, …) and write its MCP config —
#    pinned to THIS project's cloud route by default (pass --no-pin for the bare URL)
godot-cli setup-mcp claude-code ./MyGodotProject

# 6. Open the Godot editor — builds the C# assembly first (so the addon loads on
#    the very first open) then auto-connects with the right GODOT_MCP_* env vars
godot-cli open ./MyGodotProject

# 7. Wait until the plugin answers the readiness probe
godot-cli wait-for-ready ./MyGodotProject

That's it. Ask your AI "Create 3 cubes in a circle with radius 2" and watch it happen. ✨

Offline / dev install: install-plugin --source <path-to>/addons/godot_mcp copies the addon from a local directory instead of downloading it. Prefer the matching release version with install-plugin --version <x.y.z> if you need a specific addon build. The manual route (copy the addon + add the NuGet packages yourself) is still documented under Installation Steps 1–2 for the Asset Library / hand-managed flows.

See the full CLI documentation for every command, editor-resolution order, and connection env vars.

Contents

AI Game Developer — Godot MCP

Tools Reference

Godot-MCP ships 24 built-in tools grouped into 9 families. The editor's data layer is RQL (Reflection Query Language): rql-query, rql-mutate and rql-schema read and change scene nodes, Godot properties, resources, files, scenes and editor state in one query instead of one tool per operation (they replace the former node-*, scene-*, resource-*, filesystem-* and editor-* tools; in the editor also console-* and reflection-*, which stay available in a running game). With the default toolFormat (Rql) the other tools take RQL values as arguments and return compact text; Json keeps ReflectorNet's JSON schemas and results. Screenshots return a PNG image. All editor tools are available immediately after the addon is enabled — no extra configuration required. The runtime-errors, console and reflection families serve the running game (the in-game runtime, see Runtime usage); runtime-errors is OFF by default — opt in with builder.WithRuntimeErrorCapture() (see Capturing in-game runtime errors).

Family

Tools

What it does

ping

ping

Lightweight readiness probe — echoes a message back, or returns pong. Verifies the end-to-end MCP path (editor → SignalR → tool dispatch). System tool — reachable over the server's /api/system-tools/ HTTP surface, not advertised to AI agents in tools/list.

rql

rql-query, rql-mutate, rql-schema

Query and change the live editor with RQL: nodes, root, selection, scenes, files, folders, classes, console, editor, project, settings, inputActions, autoloads, groups, layers, tools (every MCP tool, hidden ones included) sources; rql-schema "<extension>" prints an installed extension's guide; godot.find / godot.group / godot.resources / godot.resource / godot.callable; node calls AddChild / InstantiateScene / Duplicate / Reparent / MoveChild / Free / Connect / Disconnect / AddToGroup / RemoveFromGroup; project settings, input map, autoloads, layer names, TileSets, Themes, ShaderMaterial parameters and MeshLibraries; editor calls NewScene / OpenScene / SaveScene / CreateResource / MoveResource / DeleteResource / CreateFolder / DeleteFolder / Reimport. All-or-nothing and undoable. See docs/rql.md and the godot-rql skill.

script

script-read, script-create, script-update, script-delete, script-attach-to-node, script-validate, script-build, script-execute

CRUD on C# (.cs) and GDScript (.gd) files, attaching a script to a node, validating GDScript, building the project's C# (the result arrives after Godot's assembly reload), and running C# in the editor (Code-Exec).

screenshot

screenshot-viewport, screenshot-camera, screenshot-isolated

Capture the editor viewport, a specific camera, or an isolated node render, returned as a PNG image the LLM can inspect.

console

console-get-logs, console-clear-logs

In a running game: read and clear the log collector (GD.Print/GD.PushWarning/GD.PushError). In the editor, the RQL console source.

reflection

reflection-method-find, reflection-method-call

In a running game: find and call C# methods (static/instance, public/private) across every loaded assembly via ReflectorNet. In the editor, RQL call / CallMethod and rql-schema.

skills

godot-skill-create, godot-skill-generate

Author a new MCP tool as a C# file in the project, and regenerate the SKILL.md files from the tools the editor currently has registered. System tools — reachable over the server's /api/system-tools/ HTTP surface (like ping), not advertised to AI agents in tools/list.

session

session-close

Save and quit an editor started by godot-cli exec / open --headless (godot-cli close --owned-only). System tool, and only for that CLI session (its lease nonce).

runtime-errors

runtime-errors-get, runtime-errors-clear

Poll errors raised inside the running game (NOT the editor) — GDScript runtime errors, push_error/push_warning, shader errors, and C# unhandled / unobserved-Task exceptions, with multi-frame GDScript backtraces on Godot 4.5+. OFF by default — opt in with builder.WithRuntimeErrorCapture().

ping

  • ping — Lightweight readiness probe; echoes a message back, or returns pong. A System tool: call it over /api/system-tools/ping (or godot-cli run-system-tool ping); it is not listed in tools/list.

rql (editor)

  • rql-query — Read-only RQL query, compact rows: nodes | where class == "OmniLight3D" | select name props["light_energy"].

  • rql-mutate — RQL query ending with set / call: nodes | where name starts-with "Enemy" | set Visible = false, root | call AddChild("Node3D", "Props"), nodes | where name == "Button" | call Connect("pressed", godot.find("UI"), "_on_pressed"), project | call AddInputAction("jump", ["Key:Space"]), editor | call SaveScene(). All-or-nothing, undoable (Ctrl+Z), dry_run previews; scene edits mark the scene unsaved, edited resource files and project.godot are saved.

  • rql-schema — The sources, functions and node classes in the edited scene; with a type name its members, methods and Godot properties.

script

  • script-read — Read a .cs / .gd script file.

  • script-create — Create a new script file.

  • script-update — Update an existing script file's contents.

  • script-delete — Delete a script file.

  • script-attach-to-node — Attach a script to a node.

  • script-validate — Validate GDScript (.gd) files and return structured parse/compile diagnostics.

  • script-build — Build the project's C# (dotnet build, Debug) so scripts written with script-create / script-update load. The build runs in the background; when it succeeds Godot reloads the C# assembly, the plugin reconnects and the result (the compiler warnings, or the compiler errors) is delivered after the reload through the request's requestId. See C# assembly reloads.

  • script-execute — Compile and run C# in the editor (Roslyn, via Code-Exec): plain statements with the last expression as the result (own usings, local classes, await), or a class with a static method. The game's compiled types are visible; include adds helper .cs files from <project>/.ai-game-dev/snippets. Returns the value plus what the code printed (Console; GD.Print / push_warning / push_error when the project builds with Godot.NET.Sdk 4.5+). From a terminal: godot-cli exec '1 + 2'.

screenshot

  • screenshot-viewport — Capture the editor viewport as a PNG.

  • screenshot-camera — Capture from a specific camera.

  • screenshot-isolated — Render a node in isolation from a chosen angle.

console (in-game runtime; in the editor use the RQL console source)

  • console-get-logs — Read the plugin's collected logs (with filtering). This includes the plugin's connection lifecycle diagnostics (connect/disconnect, drain-timeout, config save/load, skill-gen, dev-control, dispatcher, and runtime-capture warnings), which route through the same capture sink as its framework logs.

  • console-clear-logs — Clear the collected log cache.

reflection (in-game runtime; in the editor use RQL call / CallMethod and rql-schema)

  • reflection-method-find — Find C# methods (including private) across every loaded assembly.

  • reflection-method-call — Call any C# method with input parameters and get the result.

session (system tool — served on /api/system-tools/, not advertised to AI agents)

  • session-close — Save and quit an editor started by godot-cli (exec, open --headless): open scenes that have a file and the resources they changed. Needs that session's nonce from .ai-game-dev/session.json, so it never closes a user's editor; godot-cli close --owned-only calls it.

runtime-errors (in-game; OFF by default — enable with builder.WithRuntimeErrorCapture())

  • runtime-errors-get — Read captured in-game runtime errors (oldest-first, newest-kept page); poll only new errors via sinceSequence. Returns available:false when capture was never enabled, so an empty list is never mistaken for health.

  • runtime-errors-clear — Clear the captured in-game runtime-error buffer (a no-op when capture is not enabled); the monotonic sequence counter is preserved.

skills (system tools — served on /api/system-tools/, not advertised to AI agents)

  • godot-skill-create — Write a new C# (.cs) MCP tool file into the project. The tool becomes callable once the project is rebuilt (Godot builds C# out-of-band).

  • godot-skill-generate — Regenerate every SKILL.md from the tools currently registered in the editor, into the selected AI agent's skills folder.

Related MCP server: Unreal-MCP

Install Additional Skills and Tools

Install extensions when you need more or create your own tools. Each extension is an RQL module: it adds no MCP tools of its own, it extends the core rql-query / rql-mutate / rql-schema tools and the godot-rql skill.

godot-cli install-extension com.IvanMurzak.Godot.MCP.CSG ./MyGodotProject
godot-cli build ./MyGodotProject

Extension

Description

Requires

AI Animation

AnimationPlayer libraries, animations, tracks and keys

AI Beehave

Beehave addon: list behaviour trees and their nodes

Beehave addon

AI CSG

Constructive solid geometry: validated primitive dimensions

AI Dialogic

Dialogic addon: list and create timelines and characters, append timeline text

Dialogic addon, Godot 4.4+

AI GridMap

GridMap 3D tile maps: validated settings, cell rows and cell editing

AI Navigation

2D and 3D navigation: agents, regions, links and region baking

AI Particles

GPU particles in 2D and 3D: validated emitter settings and restart

AI PhantomCamera

Phantom Camera addon: list virtual cameras and hosts, set follow and look-at targets

Phantom Camera addon, Godot 4.4+

AI Terrain3D

Terrain3D addon: terrain source, region size changes and material creation

Terrain3D addon, Godot 4.4+

AI Tilemap

TileMapLayer: used-cell rows and cell editing

The installer adds only the extension package; install a required third-party addon into the project yourself.

AI Game Developer — Godot MCP

Requirements

  • Godot 4.3+ — the C# / .NET (mono) edition. The addon csproj pins Godot.NET.Sdk/4.3.0 as its minimum floor; newer 4.x editors (4.4, 4.5) work.

  • .NET 8 SDK (net8.0).

IMPORTANT

Godot-MCP requires themono (C#/.NET) build of Godot — the standard (GDScript-only) build cannot compile the addon.

NOTE

C# scripts in a game run from the editor need <EnableDynamicLoading>true</EnableDynamicLoading>. If a game started from the editor (Play) fails to create every C# script ("Cannot instantiate C# script because the associated class could not be found", after a TypeLoadException ... Could not resolve type '<an EditorPlugin class>'), the project's .csproj lacks that property: without it the build does not copy the NuGet package assemblies (Godot-MCP's packages, and the Debug configuration's GodotSharpEditor reference) into .godot/mono/temp/bin/Debug/. Godot's own C# project template always has it, and godot-cli install-plugin / create-project set it. Add it to the project's <PropertyGroup> (next to <TargetFramework>) and rebuild.

Installation

There are two things to install: the addon (the plugin files) and the two NuGet packages the addon's C# depends on. Godot compiles every .cs under your project into one assembly, so your project's .csproj must declare the same NuGet references the addon needs — otherwise the addon's C# will not compile.

Step 1: Add the addon

Pick one of the following ways to get the addons/godot_mcp/ folder into your Godot C# project.

Fully automated (recommended for terminal workflows): godot-cli install-plugin ./MyGodotProject does all of Step 1 and Step 2 in one command — it downloads addons/godot_mcp/ from the matching GitHub release, adds the two NuGet packages and the extension-catalog <EmbeddedResource> to your .csproj, and enables the plugin in project.godot, idempotently. Use --source <path>/addons/godot_mcp to install from a local copy offline. The manual Options A–C below remain for in-editor (Asset Library) and hand-managed installs.

The easiest path: install directly from inside the editor.

  1. Open the AssetLib tab at the top of the Godot editor.

  2. Search for Godot-MCP and open the asset.

  3. Click Download, then Install — Godot unpacks the addon into your project's res://addons/godot_mcp/.

The Asset Library entry is published per release and always points at a tagged version, so an in-editor install gives you a known-good snapshot of the addon. (See note below if the entry is not visible yet.)

Option B — GitHub Release zip

Grab the latest godot-mcp-addon-<version>.zip from the Releases page and extract it into your project's root — the archive already contains addons/godot_mcp/..., so the files land at res://addons/godot_mcp/.

Option C — copy from source

Copy the addons/godot_mcp/ folder from this repository (or your clone) into your project's addons/ directory by hand.


After the files are in place (Options A–C), enable the plugin: Project → Project Settings → Plugins → Godot-MCP → Enable. (If you used the fully-automated godot-cli install-plugin above, the plugin is already enabled and the NuGet packages + extension-catalog embed are already added — skip straight to Step 3.) On a successful load the editor Output panel prints:

[Godot-MCP] plugin loaded

Asset Library availability. The in-editor AssetLib entry (Option A) appears after the maintainer's first submission is approved by the Godot Asset Library moderators. Until then, use Option B (GitHub Release zip) or Option C.

Step 2: Add the NuGet packages + the extension catalog embed

Add the six PackageReferences and the extension-catalog <EmbeddedResource> to your project's .csproj (use these exact pinned versions — they must match the addon's Godot-MCP.csproj), and keep <EnableDynamicLoading>true</EnableDynamicLoading> in its <PropertyGroup> (Godot's C# template has it; without it the package assemblies are not copied next to the game assembly):

<ItemGroup>
  <PackageReference Include="com.IvanMurzak.ReflectorNet" Version="5.5.1" />
  <PackageReference Include="com.IvanMurzak.McpPlugin"   Version="8.19.0" />
</ItemGroup>

<!-- Editor-only: exported games (ExportDebug / ExportRelease) ship no RQL and no Roslyn. -->
<ItemGroup>
  <PackageReference Include="com.IvanMurzak.Rql"         Version="0.3.0" Condition="'$(Configuration)' != 'ExportDebug' and '$(Configuration)' != 'ExportRelease'" />
  <PackageReference Include="com.IvanMurzak.CodeExec"        Version="0.4.0" Condition="'$(Configuration)' != 'ExportDebug' and '$(Configuration)' != 'ExportRelease'" />
  <PackageReference Include="com.IvanMurzak.CodeExec.Roslyn" Version="0.4.0" Condition="'$(Configuration)' != 'ExportDebug' and '$(Configuration)' != 'ExportRelease'" />
  <PackageReference Include="Microsoft.CodeAnalysis.CSharp"  Version="4.12.0" Condition="'$(Configuration)' != 'ExportDebug' and '$(Configuration)' != 'ExportRelease'" />
</ItemGroup>

<!-- Embed the extension catalog so the Extensions panel populates (else it is EMPTY). -->
<ItemGroup>
  <EmbeddedResource Include="addons/godot_mcp/extensions.catalog.json" LogicalName="Godot-MCP.extensions.catalog.json" />
</ItemGroup>

Package

Version

Role

com.IvanMurzak.ReflectorNet

5.5.1

Reflection / serialization core

com.IvanMurzak.McpPlugin

8.19.0

MCP plugin client (transitively pulls McpPlugin.Common + ReflectorNet; carries the shared AgentConfig module)

com.IvanMurzak.Rql

0.3.0

RQL, the editor tools' data layer (licensed separately under PolyForm Shield — see License)

com.IvanMurzak.CodeExec

0.4.0

Code-Exec runtime behind script-execute (PolyForm Shield)

com.IvanMurzak.CodeExec.Roslyn

0.4.0

Code-Exec's Roslyn adapter, shipped as source (PolyForm Shield)

Microsoft.CodeAnalysis.CSharp

4.12.0

Roslyn, the C# compiler script-execute runs (its dependencies match .NET 8)

The four editor-only packages carry a Condition that leaves them out of Godot's export builds: only editor code (#if TOOLS) uses them, so your exported game ships neither RQL nor Roslyn (godot-cli install-plugin writes the same condition; see docs/ARCHITECTURE.md).

The <EmbeddedResource> is as required as the NuGet pins: the addon's pure-managed extension registry reads the catalog at editor runtime via GetManifestResourceStream (no res:// / filesystem fallback), and because the addon ships as source its own <EmbeddedResource> does not carry into your project — so without this line your Extensions panel is empty. The LogicalName must be exactly Godot-MCP.extensions.catalog.json so the resource resolves identically to the addon's own assembly.

Run dotnet restore so the packages land in your NuGet cache, then build. No manual DLL copying is required — at editor runtime the addon's assembly resolver locates the DLLs in your NuGet global-packages folder by reading the build's *.deps.json. (If you prefer self-contained output, set <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> so the DLLs are copied beside your project assembly instead.)

Step 3: Install an AI agent

Choose a single AI agent you prefer — you don't need to install all of them. This is your main chat window to communicate with the LLM.

Write the agent's MCP-client config with godot-cli setup-mcp <agent> ./MyGodotProject. By default it points the client at the project-pinned cloud URL <host>/mcp/p/<pin>, so an agent session launched in this project folder routes to this project's editor even when your account has several editors connected; pass --no-pin for the bare <host>/mcp URL. In Cloud mode the config carries this project's project key (Authorization: Bearer agd_pk_…) — a non-expiring credential bound to this project only, created with your machine sign-in; without a sign-in (or with --oauth) the config is URL-only and the agent signs in with its own OAuth. The editor's Configure button writes the same config. See the CLI documentation for the full list of supported agents.

AI Game Developer — Godot MCP

Connect

The plugin connects to an MCP server in one of two modes. The mode and its URL / token can be set in the serialized config or overridden at process start with environment variables (handy for CI, headless runs, and local dev). All variable names are the Godot analog of Unity-MCP's UNITY_MCP_*. The active mode always recomputes from the environment, so a process-level override wins over the serialized config without editing any file.

Cloud mode (default) — ai-game.dev

In Cloud mode the plugin connects to the hosted backend at https://ai-game.dev (the /mcp hub path is appended automatically). This is the default connectionMode.

Sign in once with godot-cli login. Cloud authentication uses the OAuth 2.1 device-authorization flow (RFC 8628): godot-cli login prints a short user code, opens your browser, and — on approval — saves a cloud credential to the shared machine store (~/.ai-game-dev/credentials.json) that the editor plugin auto-adopts, so godot-cli open connects with no token to copy or paste. No personal access token (PAT) is minted. GODOT_MCP_TOKEN (below) remains available as a manual override for CI / headless runs.

Environment variable

Purpose

Default

GODOT_MCP_CONNECTION_MODE

Force the mode: Cloud or Custom (case-insensitive).

Cloud

GODOT_MCP_CLOUD_URL

Override the cloud base URL. A trailing /mcp is stripped if present; a non-http(s) value falls back to the default.

https://ai-game.dev

GODOT_MCP_CREDENTIALS_DIR

Absolute credential-store directory for isolated development/testing. Also accepted in the project's .env; process environment wins. Omit for the regular machine store.

Platform user profile's .ai-game-dev

GODOT_MCP_TOKEN

Bearer token, routed to the active mode's token. Surrounding quotes are trimmed.

(none)

Custom mode — your own server

In Custom mode the plugin connects to a server URL you supply (a local dev server, a self-hosted instance, etc.).

Environment variable

Purpose

Default

GODOT_MCP_CONNECTION_MODE

Set to Custom to select this mode.

Cloud

GODOT_MCP_HOST

The custom server URL. Must be an absolute http(s) URL or it falls back to the default.

http://localhost:8080

GODOT_MCP_TOKEN

Bearer token (only needed if the server requires authorization).

(none)

With authorization oauth, your signed-in ai-game.dev account token is sent only to a localhost server (localhost, 127.0.0.0/8, ::1). For any other host set GODOT_MCP_TOKEN; without it the connection fails with Refusing to send your ai-game.dev account token to <host>; set GODOT_MCP_TOKEN or use a localhost server.

Example — boot the editor pointed at a local server:

export GODOT_MCP_CONNECTION_MODE=Custom
export GODOT_MCP_HOST=http://localhost:5300
# export GODOT_MCP_TOKEN=...   # only if the server enforces auth

The godot-cli open command forwards these env vars for you via --mode, --url, --cloud-url, and --token flags.

AI Game Developer — Godot MCP

Godot MCP Server setup

In Cloud mode you don't run a server at all — the plugin talks to ai-game.dev. If you want to host the server yourself (local dev, CI, or your own cloud), you have two options: let the addon download and run the matched server binary for you (recommended), or run it manually (advanced).

The server itself is the shared, engine-agnostic GameDev-MCP-Server — one server binary (gamedev-mcp-server) serving Unity-MCP, Godot-MCP, and Unreal-MCP. It is released from its own repo on its own version line; this addon pins the server version it consumes (the ServerVersion constant in addons/godot_mcp/Runtime/Connection/GodotMcpServerView.cs).

Local server — let the addon download & run it for you

In Custom mode the plugin can host its own MCP server — you don't have to build or launch anything by hand. Open the addon dock's Server card while Custom mode is selected and use the Local server row:

  • Start Server — downloads the server build for the pinned server version, caches it, launches it, and the plugin connects to it. Stop Server terminates it (it is also stopped automatically when you close the editor).

  • The download is the per-platform release asset gamedev-mcp-server-<rid>.zip — pulled over HTTPS from github.com only, from the GameDev-MCP-Server release tagged v<ServerVersion>, so the asset URL is: https://github.com/IvanMurzak/GameDev-MCP-Server/releases/download/v<ServerVersion>/gamedev-mcp-server-<rid>.zip. The <rid> (platform runtime identifier — e.g. win-x64, osx-arm64, linux-x64) is resolved automatically for your machine; all seven published RIDs are supported (win-x64/x86/arm64, linux-x64/arm64, osx-x64/arm64).

  • The binary is cached under your project's .godot/mcp-server/<rid>/ folder (gitignored) and re-used on later launches; it is only re-downloaded when the pinned server version changes (an exact version match, so the editor plugin and the server it talks to never drift). The server is launched on the port from your Server URL (default http://localhost:8080), over the streamableHttp transport, with plugin-timeout=10000 (the connection timeout) and tool-timeout=300000. A server built on McpPlugin 8.11.0+ sends a tool call to the editor once and lets it run up to 5 minutes (script-execute stops a snippet itself when its timeout argument runs out: 30 s by default, at most 4 minutes, the tool timeout minus a minute; an endless loop returns a Timeout error and runaway recursion a "Recursion too deep" error, the editor keeps running). Older servers ignore tool-timeout: they re-send a call every 10 s and give up after about 110 s; the addon runs each re-sent call only once.

Version pinning & security. The download URL is derived solely from the addon's pinned ServerVersion constant and your platform RID — there is no arbitrary-URL binary execution. The addon version and the server version are decoupled: bumping the consumed server is an explicit addon change (a new ServerVersion), and the pinned v<ServerVersion> release must already exist on GameDev-MCP-Server before an addon release that pins it. If the release asset can't be fetched (you're offline), the addon logs a warning and the local server simply doesn't start — fall back to the manual run below, or use Cloud mode. The download is skipped entirely under CI (the CI / GITHUB_ACTIONS environment), where no local server is hosted.

This mirrors Unity-MCP's self-hosted server flow: the editor plugin manages the pinned server binary for you instead of requiring a manual build.

Run the server manually (advanced)

To run the server as a standalone / cloud process, download a GameDev-MCP-Server release binary (or use the aigamedeveloper/mcp-server Docker image). Both transports are supported: streamableHttp (HTTP) and stdio.

# HTTP transport on port 8080
./gamedev-mcp-server --client-transport streamableHttp --port 8080

# stdio transport — for local MCP clients that launch the server directly
./gamedev-mcp-server --client-transport stdio

Then point the plugin at it in Custom mode (GODOT_MCP_HOST=http://localhost:8080).

Choosing a transport: use stdio when the MCP client launches the server binary directly (local use — the most common setup); use streamableHttp when running the server as a standalone process or in the cloud and connecting over HTTP.

See the GameDev-MCP-Server README for the full argument / environment-variable table, the Docker image, and the cross-platform build matrix.

AI Game Developer — Godot MCP

Customize Tools

Godot-MCP supports custom MCP Tool development directly in your project code. A tool family is a partial class decorated [AiToolType]; each tool method is decorated [AiTool("tool-name", …)] with a [Description] on the method and on each parameter to help the LLM understand it.

Any Godot API call (Node, Resource, EditorInterface, …) must run on the editor main thread — marshal it through MainThread.Instance.Run(...) (ReflectorNet's MainThread is backed by the Godot main-thread dispatcher on plugin boot). Never touch engine objects off-thread.

[AiToolType]
public partial class Tool_MyFeature
{
    [AiTool("my-custom-task", Title = "Do a custom task")]
    [Description("Explain to the LLM what this does and when to call it.")]
    public string CustomTask
    (
        [Description("Explain to the LLM what this parameter is.")]
        string inputData
    )
    {
        // ... work that does not touch the Godot API can run on this background thread ...

        return MainThread.Instance.Run(() =>
        {
            // ... touch EditorInterface / Node / Resource here, on the main thread ...
            return "[Success] Operation completed.";
        });
    }
}

Return a structured data model (ReflectorNet-serialized) or void for side-effect-only ops — never ad-hoc string formatting for parseable output. Use string? optional = null parameters (nullable + default) to mark them as optional for the LLM.

C# assembly reloads

Godot reloads the project's C# assembly about half a second after the built DLL changes (script-build, the editor's Build button, or a dotnet build in a terminal), whatever the plugin is doing. The plugin is part of that assembly, so it is torn down, rebuilt and reconnects (a few seconds). A tool call still running at that moment would keep the old assembly alive and Godot's unload would fail, so the plugin gates tool calls around a reload:

  • once the rebuilt DLL is on disk, new tool calls are refused with a retryable error ("Godot is about to reload the project's C# assembly … Retry in a few seconds"); calls already running finish first, and Godot's reload waits for them (at most 10 s);

  • when the unload starts, the plugin stops receiving calls, refuses main-thread work still queued for a call instead of running it, and waits for those calls to finish (a call caught there is reported by the server as lost with the connection: retry it too);

  • after the reload the plugin reconnects by itself, and script-build delivers its result.

Known issue: crash or hang on reload with .NET 10

On .NET 10, the editor can crash or hang shortly after a C# assembly reload. This is a .NET runtime bug, dotnet/runtime#135441: when an idle thread-pool thread exits after a reload, the runtime can free a handle in the wrong collectible load context and corrupt a static of the newly loaded assemblies (the addon's dependencies use thread-static fields, which trigger it). It is fixed on the runtime's main branch (dotnet/runtime#134983); the .NET 10 servicing fix is dotnet/runtime#136481.

Until that servicing release ships, keep idle thread-pool threads alive by setting this environment variable for the editor process:

DOTNET_ThreadPool_ThreadsToKeepAlive=-1

Editors started by godot-cli (open, open --headless, exec) get it automatically. If you open the editor yourself, set it system-wide (Windows: System Properties → Environment Variables, or setx DOTNET_ThreadPool_ThreadsToKeepAlive -1; macOS / Linux: export it in the shell or launcher that starts Godot) and restart the editor. Remove it once your .NET 10 runtime includes the fix.

AI Game Developer — Godot MCP

Runtime usage (in-game)

Everything above runs the MCP connection inside the Godot editor (the [Tool] EditorPlugin boots it for you). Godot-MCP can also run inside a running / exported game build (debug or release) — the Godot analog of Unity-MCP's runtime mode. This lets an LLM read and drive your live game state: imagine a Chess game whose bot logic you outsource to an LLM by exposing a couple of tools.

Two things make runtime mode different from editor mode, and both are deliberate:

  • It never auto-connects. The editor plugin connects on boot; a game build does not. You write the opt-in code and decide when (if ever) to call Connect().

  • There are no tools, prompts, or resources by default — strictly manual. The runtime ships zero MCP tools, prompts, and resources. You register every [AiToolType] tool, [AiPromptType] prompt, and [AiResourceType] resource yourself, in your own code — and each kind is independently optional (register prompts without any tools, or vice versa). (The addon's editor tool families are gated by #if TOOLS and don't even compile into a game build, so they can never leak in.)

The entry point is GodotMcpRuntime.Initialize(...) (namespace com.IvanMurzak.Godot.MCP.Runtime). Write it once — e.g. from a Godot autoload's _Ready() so a SceneTree exists:

using System.Reflection;
using com.IvanMurzak.Godot.MCP.Connection;   // GodotMcpConnectionMode
using com.IvanMurzak.Godot.MCP.Runtime;      // GodotMcpRuntime
using McpServerConsts = com.IvanMurzak.McpPlugin.Common.Consts.MCP.Server;   // AuthOption (none/oauth/token)
using Godot;

public partial class GameMcp : Node
{
    private GodotMcpRuntimeHandle? _mcp;

    public override async void _Ready()
    {
        // 1) Build the connection (default OFF — nothing connects yet).
        _mcp = GodotMcpRuntime.Initialize(builder =>
        {
            builder.WithConfig(config =>
            {
                config.ConnectionMode = GodotMcpConnectionMode.Custom;         // your own server
                config.Host           = "http://localhost:8080";               // prefer loopback
                config.AuthOption     = McpServerConsts.AuthOption.token;      // offline bearer-token auth
                config.Token          = "your-secret-token";
            });

            // 2) Opt YOUR tools / prompts / resources in. Zero of each by default — this is the only way
            //    they get registered, and each kind is independently optional.
            builder.WithToolsFromAssembly(Assembly.GetExecutingAssembly());     // [AiToolType] classes
            builder.WithPromptsFromAssembly(Assembly.GetExecutingAssembly());   // [AiPromptType] classes
            builder.WithResourcesFromAssembly(Assembly.GetExecutingAssembly()); // [AiResourceType] classes
            //   …or register specific families:
            //   builder.WithTools(typeof(GameMcpTools));
            //   builder.WithPrompts(typeof(GameMcpPrompts));
            //   builder.WithResources(typeof(GameMcpResources));
        }).Build();

        // 3) Connect — explicit, the security-required opt-in. Retries in the background while
        //    KeepConnected is true (the default).
        await _mcp.Connect();
    }

    public override async void _ExitTree()
    {
        // 4) Disconnect on shutdown (or whenever you want to stop exposing tools).
        if (_mcp is not null)
            await _mcp.Disconnect();
    }
}

Builder surface (all fluent / chainable):

Call

What it does

GodotMcpRuntime.Initialize(configure)

Begin configuring; returns a GodotMcpRuntimeBuilder. configure may be null for the zero-tool, env-configured default.

builder.WithConfig(Action<GodotMcpConfig>)

Set Host / Token / ConnectionMode / AuthOption in code. Multiple calls compose in order.

builder.WithToolsFromAssembly(Assembly)

Register every [AiToolType] class in an assembly (usually Assembly.GetExecutingAssembly()).

builder.WithTools(params Type[])

Register specific [AiToolType] classes when a whole-assembly scan is too broad.

builder.WithPromptsFromAssembly(Assembly)

Register every [AiPromptType] class in an assembly. Independent of tools/resources.

builder.WithPrompts(params Type[])

Register specific [AiPromptType] classes.

builder.WithResourcesFromAssembly(Assembly)

Register every [AiResourceType] class in an assembly. Independent of tools/prompts.

builder.WithResources(params Type[])

Register specific [AiResourceType] classes.

builder.WithoutMainThreadDispatcher()

Skip the automatic main-thread-dispatcher bootstrap (only if you install your own autoload dispatcher).

builder.ConnectWhenRunFromEditor()

Let handle.Connect() connect in a game started from the Godot editor (Play) too. OFF by default: see "Games run from the editor" below.

builder.WithRuntimeErrorCapture()

Capture errors raised in the running game (GDScript runtime errors, push_error/push_warning, shader errors via the Godot 4.5+ engine hook; C# unhandled / unobserved-Task exceptions with full stack traces) and register the runtime-errors-* tool so an agent can poll them. OFF by default. See "Capturing in-game runtime errors" below.

builder.WithScriptExecute()

Register script-execute in the game: the agent's C# is compiled with Roslyn and run on the game's main thread. OFF by default, needs the csproj opt-in, arbitrary code execution. See "Running C# in the game" below.

builder.Build()

Finalize; returns a default-OFF GodotMcpRuntimeHandle.

handle.Connect() / handle.Disconnect()

Open / close the connection. handle.Dispose() tears it down on shutdown.

Initialize().Build() also guarantees a main-thread dispatcher Node in the running SceneTree (so tool handlers can marshal Godot API calls onto the engine main thread), unless you opt out with WithoutMainThreadDispatcher(). Call it once a SceneTree is live (e.g. from an autoload _Ready).

Games run from the editor

A game started from the Godot editor (Play, F5) inherits the editor's environment, GODOT_MCP_HOST and GODOT_MCP_TOKEN included, so its in-game runtime would join the server the editor is connected to. By default handle.Connect() therefore does nothing in such a game and returns false (it logs why); the editor stays the one plugin the agent talks to. Exported games and games started outside the editor connect as before. To connect a game run from the editor too, opt in with builder.ConnectWhenRunFromEditor() or the process environment variable GODOT_MCP_RUNTIME_FROM_EDITOR=1. A GameDev-MCP-Server with plugin roles then keeps both connections (the game connects with the runtime role, the editor with the editor role) and routes tool calls to the editor while it is connected. The editor plugin sets GODOT_MCP_EDITOR_PID in its process, so every process it starts inherits it; it counts only while that editor runs, and the plugin clears it when it unloads.

Capturing in-game runtime errors

In editor mode, console-get-logs and script-validate surface the plugin's own logs and GDScript parse errors. But errors raised inside a running game — a GDScript runtime error (a null dereference, a bad index), a push_error/push_warning, a shader error, or a C# unhandled exception — are not visible to an agent through those editor tools. Without this, an agent can launch the game, poll for logs, see silence, and wrongly conclude the game is healthy. This is the gap that blocks an unattended "keep fixing until no errors" loop for real gameplay/runtime bugs.

Opt in with WithRuntimeErrorCapture():

_mcp = GodotMcpRuntime.Initialize(builder =>
{
    builder.WithConfig(cfg => { /* host / token … */ });
    builder.WithRuntimeErrorCapture();   // capture in-game runtime errors + expose the runtime-errors-* tool
}).Build();

await _mcp.Connect();

That single call installs three best-effort capture channels and registers the runtime-errors-* tool:

  1. Engine error stream (Godot 4.5+) — registers a Godot.Logger via OS.AddLogger, so GDScript runtime errors, push_error/push_warning, and shader errors raised in the running game are captured with their origin (file / line / function).

  2. C# unhandled exceptions — AppDomain.CurrentDomain.UnhandledException, with the full managed stack trace.

  3. C# unobserved Task exceptions — TaskScheduler.UnobservedTaskException, with the full managed stack trace. (It only observes for logging — it does not call SetObserved(), so your game's own escalation behavior is unchanged.)

An MCP client polls the captured errors with the runtime-errors-get tool (and clears the buffer with runtime-errors-clear):

Tool

What it returns / does

runtime-errors-get

A bounded, newest-kept list of { sequence, message, type, source, file, line, function, stackTrace, frames, timestamp }. Pass the previous result's highestSequence as sinceSequence to poll only new errors — the "did anything break since I last looked?" loop. Returns available:false when capture was never enabled (so an empty list is never mistaken for health).

runtime-errors-clear

Clears the captured buffer (the monotonic sequence counter is preserved, so a pre-clear sinceSequence poll still behaves).

Stack-trace fidelity (read this). The two error sources differ in depth:

  • Engine errors (source: Engine) carry the error's origin — file:line and the originating function — plus the message and a type (Error / Warning / Script / Shader). On Godot 4.5+, a GDScript runtime error also carries the deep multi-frame call stack: frames is the ordered (innermost-first) backtrace — each { function, file, line } — and stackTrace is the engine's formatted rendering of it. On Godot < 4.5 (or a release build without call-stack tracking) frames is null and stackTrace is null (origin only). The frames are materialized off the engine's non-thread-safe ScriptBacktrace inside the logger callback on the originating thread — only plain managed values ever cross into the collector, never a live engine object.

  • C# faults (source: UnhandledException / UnobservedTaskException) carry the full managed stack trace (inner exceptions inlined) in stackTrace, plus the CLR exception type name in type. (frames is null — the managed stack lives in the stackTrace string.)

Graceful degradation. On Godot < 4.5 there is no OS.AddLogger managed hook, so the engine channel is silently unavailable — the C# exception channels still work, and runtime-errors-get still functions (it just won't see GDScript runtime errors). And like the rest of runtime mode, capture is strictly opt-in — without WithRuntimeErrorCapture() nothing is hooked and there is no behavior change. Disposing the handle (handle.Dispose()) uninstalls the hooks.

⚠️ Security — information disclosure. Captured errors forward the full message and (for C# faults) the full managed stack trace to the connected agent through runtime-errors-get. Those strings can embed sensitive runtime data — absolute filesystem paths, machine/user names, query strings, or a secret/token that happened to appear in an exception message or argument. That is the intended diagnostic value, but it widens what is exposed over the connection. Enable WithRuntimeErrorCapture() only on a trusted connection: a loopback host (http://localhost:… / 127.0.0.1) with AuthOption = Consts.MCP.Server.AuthOption.token and a real token — never an unauthenticated public interface in a release build. See Security and docs/runtime-security.md.

Running C# in the game

script-execute normally runs in the editor. A game can offer it too: the agent sends C#, the running game compiles it with Roslyn and runs it on its main thread, with the game's own types, autoloads and scene tree (((SceneTree)Engine.GetMainLoop()).Root). There is no editor there, so no EditorInterface.

Godot's export builds leave Roslyn out, so the game's .csproj opts in:

<PropertyGroup>
  <GodotMcpCodeExecInGame>true</GodotMcpCodeExecInGame>
  <DefineConstants>$(DefineConstants);GODOT_MCP_CODE_EXEC_IN_GAME</DefineConstants>
</PropertyGroup>

(godot-cli install-plugin of this release or later writes the Code-Exec / Roslyn PackageReferences with a Condition that honours GodotMcpCodeExecInGame; an older one writes them editor-only, and the opted-in export then fails to compile until you re-run a current install-plugin. RQL is never shipped.) Then register the tool:

_mcp = GodotMcpRuntime.Initialize(builder =>
{
    builder.WithConfig(cfg => { /* loopback host + token */ });
    builder.WithScriptExecute();
}).Build();

Without the opt-in, WithScriptExecute() logs a warning and registers nothing, so the same code builds either way. A game started from the editor always has it (and there a WithToolsFromAssembly scan of the game assembly also brings the addon's editor tools, see docs/runtime-security.md).

⚠️ Security — arbitrary code execution. Anyone who can call tools on this connection can run any code in the game process. Use it in debug/dev builds only, on a loopback host with a token, and never ship it enabled in a public release build. Desktop CoreCLR builds only: NativeAOT and iOS cannot run code emitted at run time; mobile is untested. See docs/runtime-security.md.

Sample: a live game-state tool

A runtime tool is written exactly like an editor tool — a partial class decorated [AiToolType], each method decorated [AiTool("tool-name", …)] with a [Description] on the method and each parameter. Any Godot API call (Node, SceneTree, …) must run on the engine main thread — marshal it through MainThread.Instance.Run(...) (ReflectorNet's MainThread, backed by the dispatcher Initialize() bootstrapped for you).

This Godot analog of Unity-MCP's "Chess bot" sample exposes the live running SceneTree to the LLM — a pure-managed game-ping round-trip plus a game-scene-tree-summary that reads real Node state:

using System.ComponentModel;
using com.IvanMurzak.McpPlugin;              // [AiToolType], [AiTool]
using com.IvanMurzak.McpPlugin.Common;       // ToolTarget
using com.IvanMurzak.ReflectorNet.Utils;     // MainThread
using Godot;

// Target = ToolTarget.Runtime: a game-only tool. A game run from the editor compiles the same assembly, but only the
// game's connection registers it; while the editor is connected too, the server sends its calls to the game.
[AiToolType(Target = ToolTarget.Runtime)]
public partial class GameMcpTools
{
    [AiTool("game-ping", Title = "Game Ping", ReadOnlyHint = true, IdempotentHint = true)]
    [Description("Runtime readiness probe. Echoes 'message' back, or returns 'pong-from-game' when omitted.")]
    public string GamePing(
        [Description("Optional message to echo back. When null/empty, returns 'pong-from-game'.")]
        string? message = null)
    {
        return string.IsNullOrEmpty(message) ? "pong-from-game" : message;
    }

    [AiTool("game-scene-tree-summary", Title = "Game Scene-Tree Summary", ReadOnlyHint = true)]
    [Description("Summary of the LIVE running game's SceneTree (current scene + root child node names).")]
    public SceneTreeSummary GameSceneTreeSummary()
    {
        // Touch the live SceneTree on the engine main thread — MainThread.Instance was installed by
        // GodotMcpRuntime.Initialize(...). Touching Node APIs off the main thread would fault.
        return MainThread.Instance.Run(() =>
        {
            var summary = new SceneTreeSummary();
            if (Engine.GetMainLoop() is not SceneTree tree || tree.Root == null)
            {
                summary.CurrentSceneName = "<no-scene-tree>";
                return summary;
            }

            summary.CurrentSceneName = tree.CurrentScene?.Name ?? "<none>";
            summary.RootChildCount   = tree.Root.GetChildCount();
            foreach (var child in tree.Root.GetChildren())
                summary.RootChildNames.Add(child.Name);
            return summary;
        });
    }
}

// Structured result (ReflectorNet-serialized — never ad-hoc string formatting for parseable output).
public sealed class SceneTreeSummary
{
    public string CurrentSceneName { get; set; } = string.Empty;
    public int RootChildCount { get; set; }
    public System.Collections.Generic.List<string> RootChildNames { get; set; } = new();
}

Register it from the Initialize(...) block above (WithToolsFromAssembly(Assembly.GetExecutingAssembly()) picks it up automatically), connect, and the LLM can now call game-ping / game-scene-tree-summary against your live game. Real example outsourcing bot logic: a chess-do-turn tool that calls into your game controller on the main thread, plus a chess-get-board tool returning a structured board model.

Same [AiToolType]/[AiTool]/MainThread.Instance.Run(...) contract as the editor Customize Tools section — the only differences are that in a game build you register the tools and you call Connect(), and that game tools are marked [AiToolType(Target = ToolTarget.Runtime)] (or per tool [AiTool("x", Target = ToolTarget.Runtime)]) so the editor does not register them and the agent reaches the game even while the editor is connected. ToolTarget.Editor marks an editor-only tool; the default, Both, registers it everywhere (the editor answers it while connected).

Sample: a prompt and a resource

Tools are not the only thing you can expose — MCP also has prompts (reusable instruction templates an LLM can request by name) and resources (addressable, read-only content the LLM can fetch by URI). They register exactly like tools: a partial class decorated [AiPromptType] / [AiResourceType], with each member decorated [AiPrompt(...)] / [AiResource(...)] and a [Description]. Register your prompt and resource classes the same way you register tools — WithPromptsFromAssembly(...) / WithResourcesFromAssembly(...) (or the by-type WithPrompts(...) / WithResources(...)) from the Initialize(...) block above. Each kind is independently optional — a game can expose prompts and/or resources with no tools at all.

using System.ComponentModel;
using com.IvanMurzak.McpPlugin;               // [AiPromptType], [AiPrompt], [AiResourceType], [AiResource]
using com.IvanMurzak.McpPlugin.Common.Model;  // Role, ResponseResourceContent
using com.IvanMurzak.ReflectorNet.Utils;      // MainThread
using Godot;

// A PROMPT — a named, reusable instruction the LLM can request. Returns the prompt text; Role marks who
// the message is from. Set Enabled = false to ship a prompt registered-but-off until you flip it on.
[AiPromptType]
public partial class GameMcpPrompts
{
    [AiPrompt(Name = "explain-game-state", Role = Role.User)]
    [Description("Ask the assistant to summarize the current game state for the player.")]
    public string ExplainGameState()
    {
        return "Read the live SceneTree via the game tools and explain the current game state in one paragraph.";
    }
}

// A RESOURCE — addressable read-only content fetched by URI. Route is the URI template; the method
// returns ResponseResourceContent[]. Any Godot API access marshals onto the main thread, exactly like a tool.
[AiResourceType]
public partial class GameMcpResources
{
    [AiResource(
        Name = "Live SceneTree node names",
        Route = "game://scene-tree/nodes",
        MimeType = "application/json",
        Description = "The root child node names of the live running game's SceneTree.")]
    public ResponseResourceContent[] SceneTreeNodes(string uri)
    {
        return MainThread.Instance.Run(() =>
        {
            var names = new System.Collections.Generic.List<string>();
            if (Engine.GetMainLoop() is SceneTree tree && tree.Root != null)
            {
                foreach (var child in tree.Root.GetChildren())
                    names.Add(child.Name);
            }

            var json = System.Text.Json.JsonSerializer.Serialize(names);
            return new[] { ResponseResourceContent.CreateText(uri, json, "application/json") };
        });
    }
}

Same independently-optional, manual-registration contract as tools. The [AiPrompt]/[AiResource] attribute names, Role enum, and ResponseResourceContent helper all come from the reused com.IvanMurzak.McpPlugin package the editor path already uses — nothing Godot-specific to learn.

Where the server URL and token come from

A game build never auto-loads the editor's saved config (that's an editor-only convenience). You supply host/token one of two ways — and they compose, with env winning over code at resolution time:

  1. In code — builder.WithConfig(c => { c.Host = …; c.Token = …; }), as above.

  2. Out-of-band — GODOT_MCP_* process environment variables or a project .env file (read live by GodotMcpConfig, so a build can be reconfigured without recompiling):

    Environment variable

    Values

    Description

    GODOT_MCP_CONNECTION_MODE

    Cloud / Custom

    Connection mode (a loopback host implies Custom).

    GODOT_MCP_CLOUD_URL

    URL

    Override the Cloud base URL (default https://ai-game.dev).

    GODOT_MCP_HOST

    URL

    Custom-mode server host (default http://localhost:8080).

    GODOT_MCP_AUTH_OPTION

    none / oauth / token

    Custom-mode auth: anonymous / account (oauth) / offline bearer (token).

    GODOT_MCP_TOKEN

    string

    The bearer token (routed to Cloud or Custom by the active mode).

    GODOT_MCP_LOG_LEVEL

    Trace…None

    Log-verbosity threshold.

    GODOT_MCP_SERVER_PATH

    absolute path

    Editor-only (dev / CI). Launch this existing gamedev-mcp-server(.exe) instead of the pinned release: skips the download and the version match, and disables orphaned-server cleanup while active. A value naming no existing file is ignored.

    GODOT_MCP_SERVER_PATH is resolved by the editor plugin's server manager, not by GodotMcpConfig, so it affects only which locally-hosted server binary the editor starts, and it has no effect in a game build. It is read once per plugin assembly load and cached — a C# hot-reload re-reads it, but toggling the plugin off and on does not. It uses the same process-env → project .env precedence as the rows above. Orphan cleanup is disabled while it is set because that cleanup claims every server process running from the same directory, which an override binary is expected to share with other projects and tools. A value that names no existing file is ignored with a warning in the editor log. On Linux/macOS the file must already be executable — the chmod applied to a downloaded release is not applied to an override binary.

    export GODOT_MCP_CONNECTION_MODE=Custom
    export GODOT_MCP_HOST=http://localhost:8080
    export GODOT_MCP_AUTH_OPTION=token
    export GODOT_MCP_TOKEN=your-secret-token
  • Cloud mode (GodotMcpConnectionMode.Cloud) connects to https://ai-game.dev (override with GODOT_MCP_CLOUD_URL).

  • Custom mode (GodotMcpConnectionMode.Custom) connects to your own server — a local dev server, self-hosted, or a loopback address. This is the recommended mode for a shipped game (see below).

Security: opt-in only, default OFF

Exposing an MCP server inside a shipped game opens a remote-control surface: anything your registered tools can do, a connected MCP client can drive. Godot-MCP's runtime is built so this can only happen deliberately:

  • Opt-in only / default OFF. Building a handle does not connect. Nothing happens until your code calls Connect(). There is no auto-connect path in a game build.

  • Zero tools by default. A runtime with no WithTools… call registers nothing. The attack surface is exactly the set of tools you chose to register — no more.

  • No persisted-config auto-load. A game build never silently reads a saved config file; host/token come only from your code or GODOT_MCP_* env / .env.

  • Prefer loopback + a required token. For local tooling, bind to a loopback host (http://localhost:… / 127.0.0.1) and set AuthOption = Consts.MCP.Server.AuthOption.token with a real Token. Avoid exposing the connection on a public interface in a release build unless you have explicitly designed and secured that surface.

  • Runtime-error capture forwards sensitive data. WithRuntimeErrorCapture() is OFF by default. When enabled, captured messages and stack traces are sent verbatim to the connected agent via runtime-errors-get and may contain absolute paths, machine/user names, or a secret that appeared in an exception — so enable it only on a trusted loopback + token connection (full note under Capturing in-game runtime errors).

  • In-game script-execute is arbitrary code execution. WithScriptExecute() is OFF by default and needs a csproj opt-in; enable it only in dev builds on a loopback + token connection (Running C# in the game).

Editor-side security notes (accepted posture)

The points above are about a game build. Two editor-side surfaces are documented here for completeness; both are by design today:

  • Editor token storage is plaintext at rest. When you connect the editor plugin (Cloud device-auth or a Custom-mode token), the plugin persists your connection config — including the bearer token (token) and Cloud token (cloudToken) — as plaintext JSON in user://godot-mcp-config.json (resolved per Godot's user:// data directory). It is not encrypted or stored in an OS keystore. The trust assumption is the local user account: anyone with read access to your user data directory can read the token. Treat that file as a secret — don't commit it, sync it, or share it. To rotate, clear the saved token in the dock (or delete the file) and reconnect. (Process-env / .env overrides via GODOT_MCP_TOKEN always shadow the persisted value and are not written back to this file.)

  • The dev-control bridge is unauthenticated but gated OFF. A development-only inject/control HTTP bridge exists for driving the editor dock in tests. It is unauthenticated, but its security boundary is threefold: it is editor-only (#if TOOLS, never compiled into a game), binds 127.0.0.1 only, and starts only when GODOT_MCP_DEV_CONTROL=1 — a shipped addon (and any editor session without the env var) never listens. That env gate is load-bearing and enforced by a unit test + a boot-time assertion, so it can never silently ship enabled.

A short standalone copy of this contract lives in docs/runtime-security.md.

AI Game Developer — Godot MCP

How Godot MCP Architecture Works

Godot-MCP is a bridge between LLMs and the Godot editor. It exposes and explains Godot's tools to the LLM, which then understands the interface and uses the tools according to your requests.

On editor load, the [Tool] EditorPlugin (GodotMcpPlugin) boots the plugin: it installs a main-thread dispatcher, builds a ReflectorNet Reflector with Godot type converters, and opens a SignalR connection to an MCP server over the reused com.IvanMurzak.McpPlugin client. The AI tools it registers are then callable by any MCP-aware AI agent.

What is MCP

MCP — Model Context Protocol. In a few words, it is USB Type-C for AI, specifically for LLMs (Large Language Models). It teaches the LLM how to use external features — such as the Godot Engine in this case, or even your own custom C# method. Official documentation.

What is an AI agent

It is an application with a chat window. It may have smart agents to operate better, and embedded advanced MCP Tools. A well-built MCP client is 50% of the AI success in executing a task — which is why it is important to choose a good one.

What is the MCP Server

It is the bridge between the MCP Client and "something else" — in this case the Godot editor. In Cloud mode this is the hosted ai-game.dev backend; in Custom mode it is the shared GameDev-MCP-Server host you run yourself (or let the addon download and run for you).

What is an MCP Tool

An MCP Tool is a function the LLM can call to interact with Godot. These tools are the bridge between natural-language requests and actual Godot operations. When you ask the AI to "create a node" or "open a scene," it uses MCP Tools to execute the action. Tools have typed, described parameters; return structured results; and are thread-aware (main-thread for Godot API calls, background-thread for heavy processing).

Each tool, prompt and resource has a Visible switch in the dock's Tools / Prompts / Resources windows. It only decides whether MCP clients see the item listed (tools/list), which keeps their context small; it is not an access control: a hidden tool still runs when a client calls it by name. Settings saved by an older version (the switch was called Enabled) are kept.

AI Game Developer — Godot MCP

Building & contributing

Godot.NET.Sdk is a NuGet SDK, so no Godot binary is required to compile or unit-test:

dotnet restore Godot-MCP.sln
dotnet build  Godot-MCP.sln --configuration Debug --no-restore   # 0 errors required (CI gate)
dotnet test   Godot-MCP.Tests/Godot-MCP.Tests.csproj --configuration Debug --no-build

A Godot 4.3+ editor is only needed for live behavioral verification of the engine-driving tools. See AGENTS.md for the build/test commands, the editor-runtime assembly-load notes, conventions, and the headless testbed smoke.

Contributions are highly appreciated. Please give this project a star 🌟 if you find it useful!

  1. 👉 Fork the project

  2. Clone the fork and open it in a Godot 4.3+ (mono) editor

  3. Implement new things, commit, and push to GitHub

  4. Create a Pull Request targeting the original Godot-MCP repository, main branch.

License

Apache-2.0 © Ivan Murzak

The addon depends on the NuGet package com.IvanMurzak.Rql (RQL), which is licensed separately under the PolyForm Shield License 1.0.0 — free to use, including in commercial products, except to provide a product that competes with the licensor's products.

Extension modules

Extensions live in extensions/ with independent NuGet versions and releases. Each one is an RQL module: it adds no MCP tools, but adapters, sources and an agent guide to the rql-query / rql-mutate / rql-schema tools and the godot-rql skill (see docs/rql.md). See the monorepo guide for one-editor live tests, isolated consumers, releases and adding a module.

Available Tools

14 tools
rql-mutateRQL / MutateA
Destructive

RQL (Reflection Query Language, reference: https://ai-game.dev/docs/rql). Change the live Godot editor with an RQL query whose last stage is set or call. All-or-nothing: every target and value is checked before anything is written; a failure changes nothing. Undoable in the editor (Ctrl+Z). Scene edits mark the scene unsaved (save with editor | call SaveScene()); edited resource files (res://….tres) are saved to disk automatically. dry_run=true returns the receipt ($ref, member, old, new) without changing anything. The receipt leaves out assignments that found the value already set; they are counted ("N unchanged assignment(s) not listed"). Do exactly the change requested; a read-only member fails with readonly-member instead of being approximated. Destructive cleanup over what a query finds (Free, DeleteResource, DeleteFolder on matches, not on paths the user named) takes two turns: list what matches with rql-query, show the list to the user and end your turn; delete only after the user confirms in a later message. When you map a misspelt or approximate name to an existing node, resource or member, tell the user which one you used. set path = expr, ... assign members (paths may go through structs: set Position.Y = Position.Y + 1); values see each target: $ and $index (its position in the stream entering set / call) call Method(args) invoke a method the tools expose Examples: nodes | where name == "Player" | set Position = "(0, 1, 0)" move a node nodes | where class == "OmniLight3D" | set props["light_energy"] = props["light_energy"] * 2, props["light_color"] = "#ffcc88" Godot properties by their snake_case name nodes | where name starts-with "Enemy" | set Visible = false hide every enemy nodes | where name starts-with "Enemy" | sort name | set Position.X = $index * 2 spread the matches along X godot.resource("res://materials/red.tres") | set props["albedo_color"] = "(1, 0, 0, 1)" edit a resource file (saved to disk) root | call AddChild("Node3D", "Props") new child node of a class; the receipt's result column holds its $ref range(0, 100) | call editor.CreateNode("MeshInstance3D", {name: "Cube" + $, parent: "Props", position: [($ % 10) * 2, 0, floor($ / 10) * 2], mesh: {class: "BoxMesh"}}) bulk create: a 10 x 10 grid of cubes in one call and one undo step (one receipt row per node with its $ref; a long receipt lists the first 50 and counts the rest) [["CoinA", 0], ["CoinB", 3]] | call editor.InstantiateScene("res://scenes/enemy.tscn", {name: $[0], parent: "Props", position: [$[1], 1, 0]}) one scene instance per list entry nodes | where name starts-with "Cube" | set Scale = Scale * 1.5 vectors and colors compute component-wise nodes | where name starts-with "Cube" | set Position = [Position.X, 0, Position.Z] * 2 numeric lists compute like vectors ([x, 0, z] * k); vector functions: length, normalize, distance, dot, lerp nodes | where name starts-with "Cube" | sort name | set Position = [10 * cos(2 * pi() * $index / $count), 0, 10 * sin(2 * pi() * $index / $count)] evenly on a circle of radius 10 ($count = the matches; sin cos atan2 pi deg rad) nodes | where name == "Props" | call InstantiateScene("res://scenes/enemy.tscn") instance a scene as a child nodes | where name == "Crate" | call Duplicate() copy next to it (returns the copy) nodes | where name == "Floor" | set Mesh = {$type: "PlaneMesh", Size: "(20, 20)"} a new embedded resource (mesh, shape, material, environment …) of a concrete class, with its members; a script class_name works too nodes | where name == "Crate" | call Reparent(godot.find("Props")) reparent (keeps the global transform) nodes | where name == "Crate" | call MoveChild(0) reorder among its siblings (-1 = last) nodes | where name == "Old" | call Free() remove a node (undoable, like Delete in the Scene dock) editor | set selection = [godot.find("Player")] change the editor selection editor | call SaveScene() write the edited scene to its .tscn editor | call CreateResource("res://materials/blue.tres", "StandardMaterial3D") new resource file (a Godot class or a script class_name) editor | call MoveResource("res://materials/blue.tres", "res://materials/old/blue.tres") move / rename a file (its .import / .uid sidecars move with it) editor | call DeleteResource("res://materials/old/blue.tres") delete a file and its sidecars editor | call NewScene("res://scenes/new.tscn", "Node2D", "Main") create, save and open a scene (path, root class, root name) editor | call OpenScene("res://scenes/level.tscn") open a scene file editor | call Reimport(["res://materials/red.tres"]) re-import files (no argument: full filesystem scan) editor | call ClearConsole() clear the captured editor log nodes | where name starts-with "Enemy" | call AddToGroup("enemies") add nodes to a group (saved with the scene; set groups = [...] replaces them) nodes | where name == "Enemy1" | call Connect("tree_exited", godot.find("Player"), "_on_enemy_died") connect a signal to a method of the target's script (saved with the scene) nodes | where name == "Enemy1" | get connections | where method == "on_enemy_died" | call Disconnect() remove a connection project | set settings["display/window/size/viewport_width"] = 1280 any project setting by its path (saved to project.godot) project | call AddInputAction("jump", ["Key:Space", "JoypadButton:A"]) new input action with its events inputActions | where name == "jump" | call AddEvent("Key:W") add an event to an input action (RemoveEvent, set events = [...], set deadzone) project | call AddAutoload("Game", "res://scenes/game.gd") register an autoload singleton godot.resource("res://tiles/terrain.tres") | call AddAtlasSource("res://tiles/terrain.png") add a texture to a TileSet (returns the source id) godot.resource("res://tiles/terrain.tres") | get sources | get atlas | call CreateTilesFromTexture() create tiles in the non-transparent grid cells godot.resource("res://ui/theme.tres") | set colors["Button/font_color"] = "#ffcc00", constants["HBoxContainer/separation"] = 8 edit Theme items layers | where kind == "3d_physics" and index == 2 | set name = "enemies" name a layer (saved to project.godot) nodes | where name == "Wall" | set layerNames["collision_mask"] = [1, "enemies"] a layer mask by layer names (numbers for unnamed layers) godot.resource("res://materials/water.tres") | set params["speed"] = 2, params["tint"] = "#3388ff" shader uniforms of a ShaderMaterial (null or call ClearParam(name) reverts one) godot.resource("res://meshes/tiles.tres") | call CreateItem("Floor", "res://meshes/floor.tres") add a MeshLibrary item (set its shapes, navigationMesh … on the returned row) nodes | where class == "OmniLight3D" | set name = name.replace("Lamp", "Light") bulk rename (replace, replace-regex, substring, pad …) files | where path == "res://tiles/terrain.png" | set importSettings["mipmaps/generate"] = true change an import option (re-imports the file; a .wav file loops with the import option edit/loop_mode = 2; on an AudioStreamWAV resource set LoopMode = "Forward", its loop_mode 2 is PingPong) Editor state: editor | set isPlaying = true / false runs / stops the main scene; editor | call PlayScene("res://…") runs one scene; what the game prints and its errors: rql-query gameLog. editor | call Undo() / Redo() steps the editor's undo history like Ctrl+Z (returns the action; rql-query editor | select undoHistory lists the scene's and the global history); editor | call ReloadScene() reloads the edited scene from its file; NewScene(path, "res://base.tscn") makes an inherited scene. Baking: a VoxelGI node bakes global illumination with call CallMethod("bake") (set its Size around the level first); LightmapGI has no scriptable bake in Godot (only the editor toolbar's Bake Lightmaps), so say so instead of reporting a bake. Bulk create: range(start, end) or a list literal streams items, and call editor.CreateNode(class, {…}) / editor.InstantiateScene(path, {…}) runs once per item ($ and $index are the item's); the options are name, parent (a node, its path or ref("Node3D#…"); the scene root by default) and Godot properties of the new node (position, rotation_degrees, scale, mesh, visible …; local, not global*). Every item is checked before the first node is made; all of them are one undo step. By name, the options stay one object: call editor.CreateNode({className: "Node3D", options: {name: "Pivot"}}). Object literals build values and resources (meshes, shapes, materials …, script and GDExtension Resource classes too), never nodes (AddChild / CreateNode create those; pass an existing node by godot.find("Name"), path or ref): set Position = {x: 1, y: 2, z: 3}; set Mesh = {$type: "BoxMesh", Size: [1, 2, 1]} builds a new resource of a concrete class. Map entries streamed by get write through their map: … | get colors | where Key starts-with "Button/" | set Value = "#ffffff". Up to 200 targets per call; for an intended bulk change of more objects pass max_targets = the match count (at most 1000): still one all-or-nothing, undoable call. Nodes inside a scene instance take property changes (saved as overrides; the instance gets Editable Children) but are removed, renamed or moved in their own scene.

Query part: the RQL of rql-query (full guide and the sources in its description); godot.resources("Texture2D"), godot.resource(path), godot.find("Name"), ref("Node3D#1234"). Stages: where, select, sort, first N, get , recurse children. Operators: == != < > and or not, contains, starts-with, $ is Node3D, cond ? a : b; conditions are booleans (where Mesh != null, has(Mesh)). Paths a.b.c, list[0], props["key"]. Vectors/colors as "(1, 2, 3)" or "#ff8800"; enums as strings.

NameRequiredDescriptionDefault
queryYesRQL query ending with a set or call stage, e.g. nodes | where name == "Player" | set Visible = false
dry_runNotrue = preview the receipt without changing anything
max_targetsNoMost objects the set / call stage may change (default 200, at most 1000). Raise it to the match count for an intended bulk change of more objects: the call stays all-or-nothing and one undo step.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare destructive/not-idempotent; the description adds the operational traits that matter: all-or-nothing validation before any write, editor undo (Ctrl+Z), scene edits left unsaved vs. .tres files auto-saved, receipt contents ($ref, member, old, new), omitted unchanged assignments being counted, and the 200/1000 target cap. It even documents non-obvious limits (nodes inside scene instances can take property overrides but can't be removed or reparented) and tool-specific caveats like LightmapGI having no scriptable bake.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The critical rules are correctly front-loaded before the example list, but the text is very large, with ~30 example lines plus trailing prose blocks on Editor state, Baking, Bulk create, and Object literals that sprawl rather than being grouped. Much of the example material overlaps the RQL guide it explicitly points to in rql-query's description, so a meaningful share of the length is duplicative rather than earning its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, and the description compensates by describing the receipt shape, per-row $ref/result columns, truncation at 50 rows with a counted remainder, and failure modes. For a high-complexity mutation tool spanning nodes, resources, project settings, and editor state, an agent has everything needed to call it correctly and interpret what comes back.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real meaning beyond the schema: dry_run returns the receipt without writes and omits unchanged assignments with a count, max_targets defaults to 200 and can be raised to the match count (max 1000) for an intended bulk change that stays one undo step, and query must terminate in set or call. The extras are behavioral rather than syntax-level, which keeps it short of a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: "Change the live Godot editor with an RQL query whose last stage is set or call." It also implicitly separates itself from the sibling read tool by naming rql-query for listing and gameLog reads, so an agent can distinguish mutate from query without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to guidance and exclusions: read-only members fail with readonly-member rather than being approximated, destructive cleanup over query matches requires a two-turn confirm flow via rql-query, and dry_run=true previews first. It also instructs reporting name-to-object mappings and warns against doing more than the requested change.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

rql-queryRQL / QueryA
Read-onlyIdempotent

RQL (Reflection Query Language, reference: https://ai-game.dev/docs/rql). Run a read-only RQL query against the live Godot editor and return compact rows. One precise query beats many exploratory calls. Changes need rql-mutate.

RQL: source | stage | stage ... Sources: nodes (every node of the edited scene, depth-first, root included), root (the edited scene root), selection, scenes (open scenes), files (every res:// file the editor knows; on request size (bytes), importedSize, deps, missingDeps, usedBy (scenes / resources that load it) and importSettings["option"]), folders (path fileCount folderCount empty), classes (Godot classes, ClassDB), console (captured editor log entries), gameLog (what the game run from the editor printed: Sequence LogType Message StackTrace), editor (editor state, undoHistory / redoHistory, and operations), project (project settings: settings["section/key"], name, mainScene; input map, autoload and global group calls), settings (every project setting: name section value type changed), inputActions (name events deadzone builtIn), autoloads (name path singleton order), groups (name nodes count global), layers (kind index name: the project's layer names), tools (every MCP tool of this editor, hidden ones included: name title visible description; a hidden tool is not offered to this client: to use one, ask the user to show it in the AI Game Developer dock). Resources: godot.resources("StandardMaterial3D") or godot.resources("Texture2D", "res://art"), godot.resource("res://materials/red.tres"). ref("Node3D#1234") resolves a $ref; godot.find("Player") or godot.find("Level/Player") a node by unique name or path below the scene root; godot.group("enemies") the nodes of a group; godot.callable("Player", "_on_hit") a Callable. Stages: where filter (a boolean condition: where Mesh != null, where has(Mesh), where children.count() > 0) select a b.c alias=(expr) project columns (wrap computed expressions in parentheses) sort [desc], ... stable sort first N | skip N | count get replace each item by a member (lists are flattened) recurse children [max-depth=N] [include-self=false] walk the scene tree (input items included by default) distinct [expr] | sum expr | min expr | max expr | avg expr group-by expr rows key|count; group-by a, b (or kind=class) gives one column per key, then count; group-by class | select key, count, avgY = items.avg(Position.Y) Expressions: == != < <= > >= and or not + - * / %; contains, starts-with, ends-with (case-insensitive); matches "regex"; x in [a, b]; x is null; x is not null; $ is Node3D (includes subclasses); has(member). Conditional: cond ? a : b, e.g. select name kind=($ is MeshInstance3D ? "mesh" : "other"). Text: replace(s, from, to) (case-insensitive), replace-regex(s, pattern, repl) ($1 = group 1), substring(s, start[, length]), split(s, sep), trim(s), pad(v, width[, fill]) — also as methods: name.replace("Old", "New"), name.substring(0, 4). Literals: numbers, "strings", true/false/null, lists [a, b], objects {x: 1, y: 2}; groups == ["enemies"] compares a list. Paths: a.b.c, list[0], map["key"]; .member on a list maps over its elements. List methods: .any(pred) .all(pred) .count([pred]) .sum(e) .min(e) .max(e) .first([pred]); inside them bare names refer to the element, falling back to the outer item. Meta: $ (current item), $ref (stable id such as Node3D#1234), $type, $path, $index, $depth. Enums compare to strings; == on strings is case-sensitive. Godot: every node has name, path (below the scene root, "." = root), class (the Godot class, e.g. "OmniLight3D"), parent, owner, children (list), childCount, index, script (res:// path), groups (list) and id; signals, connections (persistent: signal target method flags), incoming and overrides (how a scene instance differs from its scene file: node property value template) are read on request (select connections, get overrides). Members are the C# properties (Position, Rotation, Scale, Visible, Text …), matched case-insensitively, so position works too; Rotation is in radians, RotationDegrees in degrees. Every Godot property, including GDScript script variables (in the editor: @export ones, every one of a @tool script) and GDExtension properties, is in props["snake_name"] (e.g. props["light_energy"], props["speed"] on a scripted node); a Godot array lists the values it holds (get props["items"] | where props["price"] > 10). Vectors / colors print as (x, y, z); write "(1, 2, 3)", [1, 2, 3] or {x: 1, y: 2, z: 3}; colors also "#ff8800". Vector2/3/4, their integer variants (whole results only) and Color compute component-wise (set Scale = Scale * 1.5, Position + [0, 1, 0]); Quaternion, Basis and transforms do not. Lists of numbers compute too ([Position.X, 0, Position.Z] * 2), and length(v), normalize(v), distance(a, b), dot(a, b), lerp(a, b, t) take vectors or lists (distance(Position, godot.find("Player").Position) < 5). Units: meters in 3D, pixels in 2D. A resource path "res://…" works wherever a resource is expected, a node path or name wherever a node is expected. Freed nodes read as null. Input events are text: "Key:Space", "Key:Ctrl+S", "PhysicalKey:W", "Mouse:Left", "JoypadButton:A", "JoypadMotion:LeftX-". A TileSet has sources (id kind atlas texture tileCount), physicsLayers, terrainSets, customDataLayers; an atlas source has tiles (coords size data). A Theme has maps keyed "ControlType/item": colors, constants, fonts, fontSizes, icons, styleboxes. A ShaderMaterial has uniforms (name type default value overridden) and params["uniform"] (typed by the shader; null reverts to the default); a MeshLibrary has items (id name mesh meshTransform shapes navigationMesh preview). Layer masks by name: layerNames["collision_mask"] on any object with layer properties is a list of layer names (a number for an unnamed layer). Callables are "Player::_on_hit" (target::method); RIDs are read-only "RID(…)"; a Projection is 16 numbers. Navigate with paths or get; select only projects output columns. Results come back as compact rows (header line, then col|col). Long results are truncated — refine with where/first or use count. Unknown members produce warnings with suggestions (in rql-mutate a misspelt member that leaves no target is an error); rql-schema(type) lists a type's members and Godot properties.

Language rules:

  • Conditions are booleans. where, and / or / not, ?: and the predicates of .any() .all() .count() .first() take a comparison or has(), never a bare value: where material != null (or has(material)), where count > 0, where name != "", where len(tags) > 0. A number, string, object or list there fails with type-mismatch; null counts as false.

  • cond ? a : b (the same as if(cond, a, b)) picks a value and evaluates only the taken branch: set y = (y < 0 ? 0 : y). Inside select, wrap computed values in parentheses: select kind = (intensity > 2 ? "bright" : "dim").

  • Lists compare by value, in order: tags == ["a", "b"], x in [[1, 2], [3, 4]]; distinct and group-by merge equal lists.

  • group-by gives rows with key (or one column per key), count and the group's items: group-by kind | select key, count, avg = items.avg(weight); also items.sum(e), items.min(e), items.max(e), items.count(pred), items.first().name. Group by the item's type with $type (group-by $type); a bare type is a member that most items do not have.

  • Receipts list only real changes: a set row whose value stayed the same is left out and counted as unchanged, so no rows with N unchanged means the values already matched.

  • call Method(args) runs the method of each item (the items must be objects). call source.Method(args) runs the method of that one source object once per stream item, with $, $index, $path and $depth in the arguments being the item's; every call counts against the target limit, and every argument is checked before the first call (a dry run checks without calling).

  • Bulk create: range(start, end[, step]) streams numbers (end excluded), and a list literal streams its elements, so one call creates many objects: range(0, 150) | call editor.CreateNode("Node3D", {name: "Tile" + $, position: [($ % 15) * 2, 0, floor($ / 15) * 2]}) builds a 15-wide grid, [["Lamp", [0, 2, 0]], ["Sun", [6, 4, 0]]] | call editor.CreateNode("OmniLight3D", {name: $[0], position: $[1]}) one object per entry. The receipt has one row per call with the $ref of the created object.

  • Vectors and colors the host marks as component-wise compute component by component: set Scale = Scale * 1.5, Position + [0, 1, 0], Position - Offset, -Velocity, v / 2. Adding a number to a vector is a type-mismatch (add [x, y, z] instead); quaternions and other types without that mark stay type-mismatch. Lists of numbers are vectors too: [x, 0, z] * k, [1, 2] + [3, 4]. Vector functions work on both: length(v) (or v.length(), magnitude), normalize(v), distance(a, b), dot(a, b), lerp(a, b, t); len(x) stays the size of a string or list.

  • Trigonometry is in radians: sin, cos, tan, asin, acos, atan, atan2(y, x), pi(), deg(x), rad(x); trigonometric results are rounded (sin(pi()) is 0). In a set value or call argument $count is the number of items entering the stage ($index runs 0 to $count - 1), so set position = [5 * cos(2 * pi() * $index / $count), 0, 5 * sin(2 * pi() * $index / $count)] lays the items out on a circle of radius 5.

  • A missing map entry reads as null: props["k"] == null and props["k"] is null are true without the entry, and has(props["k"]) tests whether it exists. A misspelt member is not null: it stays unknown and is reported.

  • Arguments by name: call Method({name: value, ...}) with one object literal that no overload takes as a whole binds each key to the parameter of that name; parameters left out keep their defaults, and an unknown name fails listing the parameters: range(0, 10) | call editor.CreateNode({className: "Node3D", options: {name: "Tile" + $, position: [$ * 2, 0, 0]}}). Engine objects (scene objects, nodes, actors) are never built from an object literal: pass an existing one, e.g. ref("Type#id"). Value-like assets and owned subobjects that the host marks are built from a typed literal: set mesh = {$type: "TypeName", size: 2}.

Examples: nodes | where name contains "Enemy" | select $ref name class path nodes | where $ is Node3D and Position.Y > 0 | select name Position nodes | where class == "OmniLight3D" | select name props["light_energy"] props["light_color"] root | recurse children | where childCount == 0 | select path class godot.resources("StandardMaterial3D") | select path props["albedo_color"] files | where ext == "tscn" | select path scenes | select name path active classes | where parent == "Light3D" | select name console | where logType == "Error" | sort sequence desc | first 5 | select message editor | select isPlaying editedScene selection godot.group("enemies") | select name path nodes | where name == "Player" | get incoming | select source signal method settings | where section == "display" and changed | select name value inputActions | where builtIn == false | select name events autoloads | select name path singleton godot.resources("TileSet") | get sources | select id kind texture tileCount godot.resource("res://materials/water.tres") | get uniforms | select name type default value overridden nodes | where class == "StaticBody3D" | select name layerNames["collision_layer"] layerNames["collision_mask"] layers | where kind == "3d_physics" and name != "" | select index name godot.resources("MeshLibrary") | get items | select id name mesh nodes | group-by class, scripted=(script is not null) nodes | where $ is Node3D | group-by class | select key, count, avgY = items.avg(Position.Y) nodes | where $ is Node3D | sort distance(Position, [0, 0, 0]) | first 3 | select name dist = (distance(Position, [0, 0, 0])) nodes | where $ is Node3D | select name kind=($ is MeshInstance3D ? "mesh" : $ is Light3D ? "light" : "other") files | where type contains "Material" and usedBy.count() == 0 | select path files | where missingDeps.count() > 0 | select path missingDeps files | where not (path starts-with "res://addons/") | sort size desc | first 5 | select path size importedSize nodes | where SceneFilePath != "" and overrides.count() > 0 | select name overrides editor | select undoHistory redoHistory

NameRequiredDescriptionDefault
queryYesRQL query, e.g. nodes | where $ is Node3D | select $ref name Position

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish the read-only, idempotent, non-destructive profile, but the description adds substantial behavior: compact row output format, truncation of long results, refinement via where/first/count, warning-and-suggestion behavior for unknown members, and call-limit receipts.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is organized into labeled sections and examples, but it is extremely long and reads like a full language reference rather than a tool description. The linked RQL documentation is mentioned but much of its content is duplicated inline, making the definition excessively verbose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex DSL-based query tool with no output schema, the description is exceptionally complete: it covers return format, truncation, error behavior, source types, operators, and many worked examples. An agent has enough information to construct and interpret queries correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the only parameter is the query string, but the description goes far beyond the schema by documenting sources, stages, expression syntax, paths, meta fields, type-specific members, and language rules. This is necessary semantic detail for constructing a valid query.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence states a specific verb and resource: 'Run a read-only RQL query against the live Godot editor and return compact rows.' It also distinguishes the tool from its sibling by noting 'Changes need rql-mutate.'

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly advises 'One precise query beats many exploratory calls,' routes mutations to rql-mutate, and points to rql-schema(type) for member discovery. This gives clear when-to-use and when-to-use-something-else guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

rql-schemaRQL / SchemaA
Read-onlyIdempotent

RQL (Reflection Query Language, reference: https://ai-game.dev/docs/rql). Without a type: RQL sources, functions, the installed extensions (Particles, Tilemap …), and the node classes present in the edited scene (with counts), plus the Node members. With an extension name: its guide with examples. With a type name (e.g. "Node3D", "OmniLight3D", "StandardMaterial3D", "Vector3", "editor"): that type's members (name type; ro = read-only), methods, and its Godot properties (props["name"]). Use it before querying an unfamiliar class.

NameRequiredDescriptionDefault
typeNoOptional type name, e.g. "Node3D", "OmniLight3D", "StandardMaterial3D", "Vector3", "editor"

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish readOnlyHint/idempotent/closed-world, and the description goes beyond them by describing what content comes back per mode, including node class counts and the 'ro = read-only' member flag plus the props["name"] convention. It does not discuss output size, truncation, or cost of introspecting a large scene, which would be a fuller disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The paragraph is dense and front-loaded, leading with the acronym and its reference link before the mode breakdown, and every clause maps to a distinct behavior. The parenthetical reference URL and a couple of nested asides make it slightly heavier than necessary, but there is no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With only one optional parameter and no output schema, the description carries the burden of explaining return content, and it does so for each mode including the shape of member listings (name type; ro markers; props["name"]). Nothing essential for correct invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline would be 3, but the description substantially enriches the single `type` parameter by explaining the three semantic branches (omitted, extension name, type name) and giving concrete example values. That is meaning the schema alone does not convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states precisely what the tool returns and enumerates three distinct modes keyed by the optional `type` argument: no type gives sources/functions/extensions/scene node classes plus Node members, an extension name gives that extension's guide, and a type name gives members, methods, and Godot properties. This is far more specific than the title 'RQL / Schema' and distinguishes it from siblings like rql-query and rql-mutate.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit usage guidance in 'Use it before querying an unfamiliar class,' which tells the agent to call this as a discovery step ahead of querying. It does not name a concrete alternative (e.g. rql-query) or state when not to use it, so it falls short of the top score.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

screenshot-cameraScreenshot / CameraA
Read-onlyIdempotent

Capture a screenshot from a specific Camera3D (or Camera2D) in the edited scene and return it as a PNG image for direct LLM inspection. Identify the camera with 'nodeRef' (instanceId preferred, else a scene-tree path). The camera's own world is rendered into a temporary off-screen SubViewport at the requested 'width'x'height', so the editor's live selection/camera is left untouched. Output edges are capped to keep the response transportable. NOTE: rendering needs a real GPU — a Godot launched with '--headless' yields an empty image, surfaced here as a structured error rather than a blank PNG.

Argument types — give these arguments as RQL values (as in rql-mutate): {key: value, …}, [a, b], "text", 1.5, true, ref("Node3D#12") from a query result, godot.find("Name"); leave out keys you do not need: NodeRef — a node: ref("Node3D#12") from a query, godot.find("Name"), an instance id, or its path below the scene root as text ("Level/Player")

NameRequiredDescriptionDefault
widthNoOutput width in pixels. Default 1920. Must be > 0 and within the dimension cap.
heightNoOutput height in pixels. Default 1080. Must be > 0 and within the dimension cap.
nodeRefYesReference to the camera Node (Camera3D or Camera2D) to capture from (instanceId preferred, else scene-tree path). RQL: NodeRef

TDQS

A4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), yet the description still adds real behavioral value: rendering happens in a temporary off-screen SubViewport so the editor's live selection/camera is untouched, output edges are capped for transportability, and a --headless launch yields a structured error rather than a blank PNG. That headless failure mode is exactly the hidden behavior an agent needs before calling.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The lead sentences are front-loaded and dense with useful information. The trailing 'Argument types — give these arguments as RQL values' block is generic shared boilerplate that partly duplicates the schema, adding bulk without adding tool-specific meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 3-parameter read tool with no output schema, the description covers the return type (PNG), the rendering isolation, the size limiting, and the headless error case. The remaining gap is that the dimension cap is referenced but never quantified, and sibling disambiguation is absent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema baseline is a 3. The description goes beyond it by spelling out the accepted NodeRef forms (ref("Node3D#12"), godot.find("Name"), instance id, or a path below the scene root) and by stating that unneeded keys may be omitted. It does not clarify the actual dimension cap value for width/height.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: capture a screenshot from a specific Camera3D (or Camera2D) in the edited scene and return a PNG. The nodeRef requirement and off-screen SubViewport rendering are concrete. It stops short of naming the siblings screenshot-viewport / screenshot-isolated, which are the obvious alternatives an agent must choose between, so differentiation is only implicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by the framing (render a specific camera's world without disturbing the editor), but there is no explicit when-to-use/when-not statement and no routing to screenshot-viewport or screenshot-isolated. The agent must infer the choice from names alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

screenshot-isolatedScreenshot / Isolated NodeA
Read-onlyIdempotent

Render a target Node3D in ISOLATION (only the target is visible, in its own world) from a chosen camera angle, framed automatically to its bounding box, lit by a default directional light, and return the result as a PNG image for direct LLM inspection. Identify the target with 'nodeRef'. Choose the view with 'cameraView' (Front/Back/Left/Right/Top/Bottom). Pick a 'background' (SolidColor or Transparent) and, for SolidColor, a hex 'backgroundColor'. The target is rendered via a temporary off-screen copy, so the edited scene is never mutated. Output is a square 'resolution'x'resolution' PNG, capped to keep the response transportable. NOTE: rendering needs a real GPU — a Godot launched with '--headless' yields an empty image, surfaced here as a structured error rather than a blank PNG.

Argument types — give these arguments as RQL values (as in rql-mutate): {key: value, …}, [a, b], "text", 1.5, true, ref("Node3D#12") from a query result, godot.find("Name"); leave out keys you do not need: NodeRef — a node: ref("Node3D#12") from a query, godot.find("Name"), an instance id, or its path below the scene root as text ("Level/Player")

NameRequiredDescriptionDefault
nodeRefYesReference to the target Node3D to render in isolation (instanceId preferred, else path). RQL: NodeRef
paddingNoFraming multiplier around the object. 1.0 = tight fit, 1.5 = 50% extra space. Default: 1.2.
backgroundNoBackground mode for the render: SolidColor (default) or Transparent.
cameraViewNoCamera angle relative to the target's bounding box. Default: Front.
resolutionNoOutput image resolution in pixels (width = height). Default: 512.
fieldOfViewNoCamera vertical field of view in degrees. Default: 60.
farClipPlaneNoFar clip plane distance. Default: 4000.
nearClipPlaneNoNear clip plane distance. Default: 0.05.
backgroundColorNoHex background color (e.g. '#404040'). Used only when background is SolidColor.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover readOnly/idempotent/non-destructive, but the description adds substantive behavior: rendering uses a temporary off-screen copy so the edited scene is never mutated, output is capped for transportability, and missing GPU support surfaces as a structured error rather than a blank PNG. Those are exactly the traits an agent cannot derive from the annotation flags.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core sentence is front-loaded and dense with useful facts, and the GPU caveat is correctly placed at the end. The trailing 'Argument types — give these arguments as RQL values' block is boilerplate that duplicates framework-wide conventions rather than this tool's specifics.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 9-parameter tool with no output schema, the description still covers the return value (square resolution x resolution PNG, capped), the non-mutation guarantee, and the failure mode. Nothing an agent needs to decide whether to call it or to interpret the result is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description earns above that by explaining that nodeRef accepts ref() from a query, godot.find(), an instance id, or a scene-relative path, and that backgroundColor only applies when background is SolidColor. It stays silent on padding, fieldOfView, and the clip planes, leaving those entirely to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Render a target Node3D in ISOLATION') plus scope constraints ('only the target is visible, in its own world') and the output form (PNG). The isolation scope is what separates it from screenshot-camera and screenshot-viewport without the agent needing to open either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear conditions of use — pick a node, pick a view, pick a background — and an explicit environment prerequisite (needs a real GPU; '--headless' fails). It never names screenshot-camera or screenshot-viewport, so the choice between the three screenshot tools remains an inference from the word 'isolation' rather than an explicit routing rule.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

screenshot-viewportScreenshot / Editor ViewportA
Read-onlyIdempotent

Capture the Godot editor's main viewport (the 3D or 2D editing surface) and return it as a PNG image for direct LLM inspection. Set 'mode' to '3d' (default) for the 3D editor viewport or '2d' for the 2D editor viewport. The longest output edge is capped to keep the response transportable; oversized captures are scaled down (aspect preserved). NOTE: the viewport must have a live GPU render — a Godot launched with '--headless' has no rendering device and yields an empty image, which surfaces here as a structured error rather than a blank PNG.

NameRequiredDescriptionDefault
modeNoWhich editor viewport to capture: '3d' (the 3D editor surface, default) or '2d' (the 2D editor surface). Case-insensitive.

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent and non-destructive, so the description is free to add genuinely new behavior: the PNG return format, the longest-edge downscaling with aspect preserved, and the headless/--headless failure mode surfacing as a structured error instead of a blank PNG. That last point is exactly the kind of non-obvious operational constraint annotations cannot express.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences: purpose first, then mode selection, then output scaling and the headless caveat. Every sentence contributes distinct information with no repetition of the title or name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-optional-param read tool with no output schema, the description still covers what is returned (PNG), how it is sized, and how failure manifests. Nothing an agent needs in order to call it correctly and interpret the result is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the mode enum, default and case-insensitivity are already documented in the schema. The description restates them faithfully but adds no syntax or edge-case detail beyond it; baseline 3 is appropriate when the schema carries the parameter burden.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Capture') and resource ('Godot editor's main viewport'), and clarifies it is the 3D or 2D editing surface as opposed to the camera or isolated views implied by the sibling tools. An agent can distinguish this from screenshot-camera and screenshot-isolated without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear selection guidance between '3d' (default) and '2d', and states the precondition that the editor must have a live GPU render. It does not explicitly route the agent to the sibling screenshot tools when the camera or an isolated view is wanted, which is the one remaining gap.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

script-attach-to-nodeScript / Attach To NodeA
DestructiveIdempotent

Attach a script (C# '.cs' or GDScript '.gd') to a Node in the currently edited Godot scene — the Godot analog of adding a MonoBehaviour to a GameObject. Identify the target with 'nodeRef' (instanceId preferred, else scene-tree path) and the script with 'scriptPath' (a res:// script file that must exist). Pass an empty/whitespace/null 'scriptPath' to DETACH (clear) the node's script. The script resource is loaded and set via Node.SetScript; the scene is marked unsaved (persist with rql-mutate 'editor | call SaveScene()'). Returns the script's structured ScriptInfo on attach, or an identity record with a 'detached' status on clear.

Argument types — give these arguments as RQL values (as in rql-mutate): {key: value, …}, [a, b], "text", 1.5, true, ref("Node3D#12") from a query result, godot.find("Name"); leave out keys you do not need: NodeRef — a node: ref("Node3D#12") from a query, godot.find("Name"), an instance id, or its path below the scene root as text ("Level/Player")

NameRequiredDescriptionDefault
nodeRefYesReference to the target Node (instanceId preferred, else scene-tree path). RQL: NodeRef
scriptPathNores:// path of the script to attach, ending in '.cs' or '.gd'. Pass empty/whitespace/null to DETACH the node's current script.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations: it discloses the underlying mechanism (script resource loaded and set via Node.SetScript), the side effect that the scene is marked unsaved, the follow-up call needed to persist, and the destructive clear-on-empty behavior that explains destructiveHint=true. It also describes the return payload (structured ScriptInfo on attach, an identity record with 'detached' status on clear), which matters since there is no output schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Dense but front-loaded: the core action, argument contract, and side effects come before the RQL value-format digression, and every sentence carries information. The only waste is the NodeRef definition appearing twice (in the 'Argument types' block and again as the trailing 'NodeRef — a node:' line).

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description supplies the return shape for both attach and detach, and for a two-parameter mutation it covers side effects, persistence, and argument forms. Nothing an agent needs in order to call it correctly and interpret the result is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real meaning: the accepted forms of nodeRef (ref("Node3D#12") from a query, godot.find("Name"), raw instance id, or scene-root-relative path) with a stated preference order. It also reinforces the extension constraint on scriptPath and the detach sentinel. The redundant restatement of NodeRef at the end of the description slightly dilutes the added value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Attach a script ... to a Node') and bounds the scope to 'the currently edited Godot scene'. It further disambiguates from siblings by explaining the tool also performs DETACH when scriptPath is empty, and grounds the concept with 'the Godot analog of adding a MonoBehaviour to a GameObject'. An agent can distinguish this from script-create/script-update/script-delete without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear operating context: attach in the currently edited scene, scriptPath must point to an existing res:// file, and persisting requires a separate rql-mutate 'editor | call SaveScene()'. It also defines the detach path and its precondition. It stops short of explicitly saying 'use script-create first if the file does not exist' or naming script-update as the alternative for changing script contents, so alternatives are only implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

script-buildScript / BuildA
DestructiveIdempotent

Build the project's C# (dotnet build, Debug configuration like the editor's builds) so C# scripts written with 'script-create' / 'script-update' load: new [GlobalClass] types, changed node scripts. The build runs in the background; when it succeeds Godot reloads the C# assembly and the plugin reconnects, and the result (the compiler warnings, or the compiler errors) is delivered after that reload through the request's requestId. Tool calls made while the reload is pending are refused with a retryable error: retry them a few seconds later.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds substantial behavior beyond the annotations: the build runs in the background, a successful build triggers an assembly reload and plugin reconnect, results (warnings or errors) arrive asynchronously after the reload via requestId, and concurrent calls are rejected with a retryable error. It does not address why destructiveHint=true (what on disk may be overwritten) or expected duration, so it is not fully exhaustive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core action and its purpose are front-loaded in the first sentence, and the async/retry details follow in logical order. The second sentence is long and densely packed (reload, reconnect, requestId delivery), costing some readability but no wasted content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description correctly explains the return path (compiler warnings/errors delivered after the reload via requestId) and the async/retry contract, which is what an agent needs to call it safely. It omits build scope details (whole solution vs project) and expected build duration, so it is strong but not fully complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Zero input parameters, so the baseline is 4 and there is little for the description to clarify. It mentions a 'requestId' that does not exist in the schema, which is mildly confusing but does not affect invocation since no arguments are accepted.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource (build the project's C# via dotnet build, Debug configuration) and ties the effect directly to sibling tools script-create/script-update by explaining what the build makes loadable ([GlobalClass] types, changed node scripts). An agent can distinguish this from script-validate or script-execute without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear when-to-use guidance: call after writing/updating scripts so they load in the editor. It also explains the failure mode to expect (calls during the pending reload are refused with a retryable error, retry in a few seconds). It does not compare against alternatives such as script-validate, so it stops short of explicit when-not-to-use routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

script-createScript / CreateA
Idempotent

Create a NEW Godot script file (C# '.cs' or GDScript '.gd') under res:// with the provided source. Fails if a file already exists at the path — use 'script-update' to overwrite. GDScript content is syntax-validated before write (invalid '.gd' is rejected and nothing is written); C# is accepted as-is (no in-editor compiler) and the post-write build settle surfaces compile errors. Missing parent directories are created. For a '.cs' file the editor filesystem reimports and BOUNDED-settles before returning (then 'script-build' builds the project and loads the new type); a '.gd' file only needs a filesystem update. Returns a structured ScriptInfo.

NameRequiredDescriptionDefault
contentYesSource code for the new script file.
scriptPathYesres:// path for the new script, ending in '.cs' (C#) or '.gd' (GDScript), e.g. 'res://scripts/Enemy.cs' or 'res://scripts/player.gd'.

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description goes well past the annotations: GDScript is syntax-validated and rejected with nothing written, C# is accepted unvalidated, missing parent directories are created, and .cs writes trigger a bounded filesystem reimport before returning. These are exactly the failure-mode and side-effect facts an agent needs and none of them are derivable from readOnlyHint/destructiveHint/idempotentHint.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The creation constraint and the script-update redirect are front-loaded, and each subsequent sentence covers a distinct behavior (validation, C#, parent dirs, settle, return). It is dense and the middle sentences are long, but no sentence is redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter creation tool with no output schema, the description covers failure conditions, validation, side effects, and the next step in the workflow. The only gap is that 'Returns a structured ScriptInfo' does not hint at its fields, which would matter since no output schema exists.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description earns more by explaining that the path's extension ('cs' vs 'gd') changes validation, settle, and follow-up behavior. It does not discuss content encoding or any edge cases of content, but the schema already carries the type and required constraints.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource ('Create a NEW Godot script file ... with the provided source') and scopes it to res:// paths and two file extensions. It also explicitly distinguishes itself from script-update, so an agent can route between them without inspecting schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It says precisely when not to use it ('Fails if a file already exists at the path — use script-update to overwrite') and points at script-build as the follow-up for C#. It stops short of naming conditions in which script-create should be preferred over other creation paths, but the exclusion and alternative are explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

script-deleteScript / DeleteA
DestructiveIdempotent

Delete a Godot script file (C# '.cs' or GDScript '.gd') from the res:// filesystem. Fails if no file exists at the path. The script's identity (path, language) is captured BEFORE deletion and returned for confirmation. The file is removed with DirAccess, along with its Godot '.uid' sidecar when present, then the editor filesystem is rescanned and (for a '.cs' file) BOUNDED-settles. WARNING: nodes/resources referencing the deleted script will break — verify with 'script-read' first. A loaded copy is detached from the path, so an open scene still using it embeds it on its next save. Returns a structured ScriptInfo.

NameRequiredDescriptionDefault
scriptPathYesres:// path of the script file to delete, ending in '.cs' or '.gd'.

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already flag destructive/non-readOnly, but the description goes well beyond them: failure when no file exists, identity captured before deletion, the '.uid' sidecar removal, a filesystem rescan, and a bounded settle for '.cs'. It also discloses a non-obvious side effect — a loaded copy is detached so an open scene embeds it on its next save — which no annotation conveys.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the action and resource in the first sentence, then layers failure mode, mechanics, and the breakage warning in a logical order. It is dense but every clause carries operational information; only the 'BOUNDED-settles' phrasing is slightly opaque jargon.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description does state what comes back ('structured ScriptInfo' with path and language captured pre-deletion). It omits the concrete shape of that return object and any error payload detail, but for a one-parameter destructive tool the operational picture is essentially complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the single parameter already documents the res:// path format and '.cs'/'.gd' extension requirement. The description adds only the implied must-exist constraint, so the baseline 3 applies with little extra meaning contributed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Delete a Godot script file') plus the scope ('from the res:// filesystem') and accepted file types ('.cs' or '.gd'). It is unmistakable against siblings like script-update or script-read, which mutate or inspect rather than remove.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit precondition before use: 'verify with script-read first' because nodes/resources referencing the deleted script will break. That names a sibling and the condition selecting it. It does not, however, contrast against other mutation siblings (script-update, script-create) or state when deletion is inappropriate beyond the breakage warning.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

script-executeScript / ExecuteA
Destructive

Compiles and runs C# in the Godot editor: plain statements with the last expression as the result (own usings, local classes, await allowed), or a declared class 'className' with static method 'methodName'. Returns the result and the printed output (Console; GD.Print on Godot 4.5+). Guarded: an endless loop is stopped when its time budget ('timeout', default 30 s) runs out, and runaway recursion is stopped, with an error, and the process keeps running. Threads and tasks the code starts (new Thread, Task.Run, ThreadPool) are stopped at their next loop iteration or call once the run ends (a warning counts them); timers, async void code after its first await and work blocked in a wait are not, so join or await what you start.

Argument types — give these arguments as RQL values (as in rql-mutate): {key: value, …}, [a, b], "text", 1.5, true, ref("Node3D#12") from a query result, godot.find("Name"); leave out keys you do not need: SerializedMemberList — named values {count: 3, label: "x", target: ref("Node3D#12")}: each is passed as a parameter of the value's type (a node, string, int, float, bool)

NameRequiredDescriptionDefault
usingsNoExtra namespaces for snippet mode, e.g. ["MyGame.Gameplay"].
includeNoHelper .cs files or folders (recursive) compiled with the code, relative to <project>/.ai-game-dev/snippets.
timeoutNoTime budget of the run in seconds (default 30, at most 240; 0 = default). When it runs out the code is stopped at its next loop iteration or call and the call fails with Timeout; give long-running work a larger value.
classNameNoFull mode: the class whose static method runs (namespace-qualified or not).
csharpCodeYesC# code. Snippet: 'var root = EditorInterface.Singleton.GetEditedSceneRoot();\nroot.GetChildCount()' (System, LINQ and Godot are imported; the last expression is the result, so no ';' after it). Full: 'using Godot;\npublic class Script { public static int Main() { return 42; } }' (full code brings its own usings).
methodNameNoFull mode: the static method to run.
parametersNoValues for the snippet's / method's parameters by name, e.g. {node: ref("Node3D#12"), n: 6}. Leave empty if there are none. RQL: SerializedMemberList
isMethodBodyNoForce snippet mode even when the code declares 'className'.

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only say destructive/open-world/non-idempotent; the description goes far beyond by disclosing exactly what is guarded (endless loops against a time budget, runaway recursion), that the process survives failures, and precisely which spawned concurrency (Thread, Task.Run, ThreadPool) is cleaned up versus which is not (timers, async void after first await, blocked waits). It also states the failure mode ('fails with Timeout') and that leftover threads produce a warning, which is unusually honest behavioral disclosure for an execution tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is front-loaded with the core purpose and modes, and nearly every sentence carries operational information (timeouts, thread cleanup, RQL encoding). The second paragraph is a dense wall of text with an unfinished sentence ('(a node, string, int, float, bool)'), and the timeout default is restated from the schema, so it is not fully tight.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an 8-parameter, no-output-schema execution tool this covers the essentials: what is returned (result plus Console/GD.Print output), both compilation modes, parameter passing syntax, resource limits, and concurrency cleanup caveats. An agent has enough to invoke it correctly and to anticipate failure modes without further documentation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds meaning the schema does not: the RQL value grammar ({key: value}, [a, b], "text", ref("Node3D#12"), godot.find("Name")) and the rule that each named value is passed as a parameter of that value's type. That materially helps an agent construct 'parameters' correctly. Minor deduction because the argument-types explanation trails off with an incomplete parenthetical and repeats the timeout default already in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first clause gives a precise verb and resource: 'Compiles and runs C# in the Godot editor', then distinguishes its two operating modes (snippet with implicit usings vs. a declared class with a static method). It is immediately clear what the tool does and how it differs in effect from a build/validate tool, though no sibling (script-build, script-validate, rql-query) is named explicitly to route the agent.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives real in-tool selection guidance: snippet vs. full class, when to set className/methodName, how isMethodBody overrides mode detection, and 'leave out keys you do not need' for parameters. It also explains when to raise 'timeout'. What it lacks is any statement of when to choose this over siblings like script-build or rql-query, so it stops short of explicit alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

script-readScript / ReadA
Read-onlyIdempotent

Read a Godot script file (C# '.cs' or GDScript '.gd') under res:// and return its content + metadata (res:// path, language, line count). Supports an optional 1-based 'lineFrom'/'lineTo' slice for partial reads (indices are clamped — out-of-range values read at-most the whole file). Pair with 'script-update'/'script-create' to write back. The file is read with Godot's FileAccess so it works on any res:// path (imported or not). Returns a structured ScriptInfo.

NameRequiredDescriptionDefault
lineToNo1-based inclusive end line of the slice (default -1 = end of file). Clamped into range.
lineFromNo1-based start line of the slice (default 1). Clamped into range.
scriptPathYesres:// path of the script to read, e.g. 'res://scripts/player.gd' or 'res://scripts/Enemy.cs'. Must end in '.cs' or '.gd'.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, non-destructive, and closed-world, so the safety profile is covered. The description still earns credit by adding behavior beyond them: the read goes through Godot's FileAccess so it works on imported and non-imported res:// paths, and out-of-range slice values are clamped to at-most the whole file rather than erroring.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose in the first sentence, then slice behavior, then the write-back pairing, then the FileAccess caveat — a sensible order. A few stacked parentheticals add density, but no sentence is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, and the description fully compensates by enumerating the returned fields (res:// path, language, line count, content) and naming the ScriptInfo shape. Combined with slice clamping and path validation notes, an agent has everything needed to call it correctly on the first try.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description adds meaning: '1-based' indexing and the clamping policy ('out-of-range values read at-most the whole file') clarify edge behavior that the per-property 'Clamped into range' notes only gesture at. It stops short of 5 because the slice semantics are largely restated from the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Read a Godot script file') along with the accepted extensions (.cs/.gd), the path namespace (res://), and the return payload (content + path, language, line count). This is clearly distinguishable from the file-lifecycle siblings script-update/script-create, which it explicitly cites as the write-side counterparts.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives real usage context: read here, then pair with script-update/script-create to write back, and the optional slice for partial reads. It lacks an explicit exclusion (e.g., when to prefer script-validate or rql-query over reading raw content), so it stops just short of full when/when-not routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

script-updateScript / UpdateA
DestructiveIdempotent

Overwrite an EXISTING Godot script file (C# '.cs' or GDScript '.gd') under res:// with the provided source. Fails if no file exists at the path — use 'script-create' to create one. GDScript content is syntax-validated before write (invalid '.gd' is rejected and the existing file is left untouched); C# is accepted as-is (no in-editor compiler) and the post-write build settle surfaces compile errors. For a '.cs' file the editor filesystem reimports and BOUNDED-settles before returning (then 'script-build' loads the changed type); a '.gd' file only needs a filesystem update so the editor re-parses it. Use 'script-read' to inspect the current content first. Returns a structured ScriptInfo.

NameRequiredDescriptionDefault
contentYesNew source code to write (replaces the file's entire content).
scriptPathYesres:// path of the existing script to overwrite, ending in '.cs' or '.gd'.

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations: it discloses that GDScript is syntax-validated and rejected without touching the existing file, C# is written as-is with no in-editor compile, that a bounded reimport/settle occurs for .cs, and that compile errors surface post-write. Failure behavior and side effects are described in concrete terms consistent with destructiveHint=true.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the operation and scoping, then layers validation and follow-up behavior; nearly every clause carries information. It is dense and somewhat parenthetical ('BOUNDED-settles', capitalized emphasis), which costs a little readability but not correctness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 2-parameter mutation with no output schema, it covers failure modes, validation, and follow-up steps thoroughly. The one gap is the return value: it only says "Returns a structured ScriptInfo" without any indication of what that structure contains, and there is no output schema to fall back on.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so both parameters are already documented (full-content replacement, res:// path ending in .cs or .gd). The description mostly restates those constraints rather than adding new syntax or format guidance, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

"Overwrite an EXISTING Godot script file ... under res:// with the provided source" gives a specific verb, resource, and scope, plus the accepted extensions. It explicitly names the sibling tools script-create, script-read, and script-build, so an agent can distinguish the intended tool without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

States the preconditions (a file must already exist at the path), tells what to do otherwise (use 'script-create'), recommends inspecting first with 'script-read', and names the follow-up tool 'script-build' for C# types. Explicit when/when-not plus alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

script-validateScript / ValidateA
Read-onlyIdempotent

Validate GDScript ('.gd') files and return STRUCTURED parse/compile diagnostics — the on-demand 'is the project error-free?' query. Closes the gap where the agent gets NO feedback on GDScript parse errors and assumes the game runs fine. Inputs:

  • 'scriptPath' (optional): a single res:// '.gd' path to validate. Omit to scan every '.gd' under res:// (capped at FullScanFileCap). Result (ScriptDiagnosticsResult): 'ok' (true when no errors), 'diagnostics' [{ path, line, message, severity }], counts, a 'truncated' flag (true when a full scan hit the file cap, so 'ok' is NOT a whole-project all-clear), and a 'fidelity' note:

  • Godot 4.5+: 'Precise' — exact line + message captured via the engine Logger hook.

  • Godot < 4.5: 'Coarse' — per-file pass/fail with the engine error code (no line/message text). GDScript warnings (unused variables, shadowed names, … as enabled in Project Settings → Debug → GDScript) come back with severity Warning on Godot 4.5+ for scripts without errors (res://addons/ skipped like the editor does); they do not make 'ok' false. C# ('.cs') files are NOT validated here (Godot has no in-editor C# compiler to reach cheaply); their compile errors surface through the project build. Pair with the RQL 'console' source (rql-query 'console | where logType == "Error"'), which on Godot 4.5+ also captures engine GDScript parse errors passively.

NameRequiredDescriptionDefault
scriptPathNoOptional single res:// '.gd' script path to validate, e.g. 'res://scripts/player.gd'. Omit (null/empty) to scan every '.gd' file under res://.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, yet the description still adds substantial behavior: full-scan file capping with a 'truncated' flag that invalidates a whole-project all-clear, version-dependent fidelity (Precise vs Coarse diagnostics on Godot <4.5), warning severity that does not flip 'ok', and res://addons/ skipping. These are exactly the caveats an agent could otherwise misread as success signals.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the verb and result shape, then cleanly sectioned into Inputs and Result. It is long and occasionally editorial ('Closes the gap where the agent gets NO feedback', the GOOD-word caps), but nearly every clause carries operational information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, and the description compensates by enumerating the result fields (ok, diagnostics shape, counts, truncated, fidelity) and their semantics. Version-gated behavior, warning handling, and the C# gap are all covered — nothing an agent needs to interpret a clean or truncated result is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and its own description already explains omit-to-scan-all, so the baseline is 3. The description adds real context beyond the schema: the omission path is bounded by FullScanFileCap, and that cap interacts with the 'truncated' result flag, which the schema does not mention.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Validate GDScript .gd files') plus the exact return shape (structured parse/compile diagnostics). It explicitly excludes C# ('.cs') files, which separates it from the sibling script-build, and the framing as the 'is the project error-free?' query distinguishes it from script-execute and script-read.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use ('on-demand is the project error-free check') and when-not-to (C# files, which surface through the project build). It also names a complementary path — pairing with rql-query on the 'console' source — so the agent knows how to cover what this tool misses.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 14 tool updatesv0.35.1
    • Addedrql-mutate
    • Addedrql-query
    • Addedrql-schema
    • Addedscreenshot-camera
    • Addedscreenshot-isolated
    • Addedscreenshot-viewport
    • Addedscript-attach-to-node
    • Addedscript-build
    • Addedscript-create
    • Addedscript-delete
    • Addedscript-execute
    • Addedscript-read
    • Addedscript-update
    • Addedscript-validate

TDQS

A4.4/5.0

Scored across 14 tools

Disambiguation4/5

The set splits cleanly along purpose lines: rql-query reads, rql-mutate writes, rql-schema documents, screenshot-* capture images, script-* manage files. Minor overlaps remain — rql-mutate with dry_run=true is effectively a read that duplicates rql-query territory, and screenshot-viewport vs screenshot-camera both return rendered imagery — but descriptions make the boundaries clear enough that an agent should pick correctly.

Naming Consistency4/5

All names are kebab-case with a resource prefix (script-, rql-, screenshot-), which is predictable. The suffix convention drifts, however: script-* uses verbs (create/read/update/delete/validate/build/execute), rql-* uses verbs too but in noun-verb order, while screenshot-* uses target nouns (camera/isolated/viewport), and script-attach-to-node is an outlier in length and shape.

Tool Count5/5

14 tools is comfortably in the well-scoped range, and each covers a distinct capability (query, mutate, schema lookup, three capture modes, script CRUD plus build/validate/execute/attach). Nothing looks like filler; the segmentation of script operations mirrors familiar file-tool conventions.

Completeness5/5

The domain is live Godot editor automation, and the surface covers it end to end: inspection via RQL sources (nodes, files, project settings, console, game log), mutation of scenes, resources, settings, input actions and autoloads via rql-mutate, plus script authoring, validation, build, execution and three screenshot modes. Only marginal gaps exist (e.g. detach is folded into script-attach-to-node with an empty path rather than being its own operation).

Maintenance

ActivityActive
ResponsivenessUsually handled

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Open-source MCP server connecting AI agents (Claude, Cursor, GitHub Copilot, Gemini, and more) to Unreal Engine 5.7, editor and runtime (C++ plugin + .NET sidecar). Additionally you can use AI Game Dev app (https://ai-game.dev) to generate images, video, audio, 3D, rig, animation and much more.
    57
    Apache 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    A security-first MCP server and Godot editor addon enabling AI agents to observe and control Godot games through bounded, permission-gated tools for debugging, input automation, and project editing.
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    The most advanced MCP server for Godot Editor that lets AI assistants operate directly inside your running Godot project for scene creation, script generation, UI authoring, and more.
    49
    MIT