Agent-first CLI and MCP server for the Godot engine, with structured output for AI agents
Project description
godot-agent (gda)
The agent-first CLI + MCP server for the Godot Engine. Give your AI coding agent structured, machine-readable control of Godot — create scenes, edit nodes & scripts, and export builds headlessly, then drive a running game live: runtime tree, input, screenshots, performance.
AI agents are great at writing GDScript and terrible at seeing what happened. gda
closes that loop: your agent issues one operation and gets back a single clean JSON
result it can act on — never engine logs it has to scrape. It works two ways:
- Headless — one-shot and stateless, zero setup. No editor plugin, no daemon, nothing to install in your project. Create and edit scenes, nodes, scripts, resources, shaders and themes; analyze the project; export builds.
- Live — drive a running game through a background daemon for everything only a live engine can do: read the runtime scene tree, get/set runtime properties, simulate input, capture screenshots, and sample performance.
gda ships as three pieces: gda (the CLI), gda-mcp (an MCP server that
exposes the same operations as tools, generated from gda's own schemas), and
gda-daemon (the per-project process behind live operations). See
How it works.
gdais pre-1.0: every command works end-to-end today, but the CLI surface may still change before 1.0.
Why gda?
- 🤖 Structured output, built for agents. Every command emits exactly one JSON
object on stdout (
--json); engine banners, warnings andprint()go to stderr. Your agent parses one result, not a wall of logs. - 📐 Typed & self-describing. Every command's input and output are typed models that
also back a machine-readable
--schema(a JSON-Schema contract), so an agent can discover and validate the whole surface programmatically instead of guessing. - 🔀 CLI and MCP, fully interchangeable. Run
gda …in a terminal or CI, or call the same operation as an MCP tool from your agent —gda-mcpis generated from the CLI's own schemas, so the two surfaces stay identical, command for command. Use whichever fits the moment, with no feature gap between them. - 🧩 Godot-native commands. Grouped by Godot object (
gda scene create,gda node add,gda game set) with a tiny, consistent verb vocabulary — zero learning curve if you already know Godot. - ⚡ Headless by default, live when you need it. Headless operations need no daemon and no editor — just a Godot binary. Live operations add real-time control of a running game over a Unix-domain-socket daemon, addressed by the same CLI grammar.
- 🛡️ Fails loudly, never silently. A missing or hung engine is bounded by a timeout
and mapped to a stable non-zero exit code plus a structured
{"error": {…}}envelope — so a shell or agent can branch on the failure category without parsing prose.
Installation
Install the CLI from PyPI onto your PATH:
uv tool install gda # or: pipx install gda
gda --help
Other ways to install (pip, from source)
Into an existing environment:
pip install gda
From source (for development or unreleased changes):
git clone https://github.com/aigengame/godot-agent.git
cd godot-agent
uv sync # create the environment + install dependencies
uv run gda --help
Use it as an MCP server
gda ships a stdio MCP server behind a [mcp] extra,
so any MCP agent (Claude Code, Codex, Cursor, …) can drive Godot. Try it with no install:
uvx --from "gda[mcp]" gda-mcp
gda-mcp picks your Godot project from GDA_PROJECT if set, otherwise the client's
workspace roots, otherwise the working directory. An explicitly set
GDA_PROJECT that is not a valid project is reported as an error, not silently replaced.
Register with Claude Code / Codex / Cursor
Claude Code — project scope, .mcp.json at the repo root (auto-detects the project via roots):
{
"mcpServers": {
"gda-mcp": {
"command": "uvx",
"args": ["--from", "gda[mcp]", "gda-mcp"]
}
}
}
User scope (every project) — the CLI, which writes ~/.claude.json:
claude mcp add --scope user gda-mcp -- uvx --from "gda[mcp]" gda-mcp
Codex — project scope, .codex/config.toml at the repo root (the project must be trusted):
[mcp_servers.gda-mcp]
command = "uvx"
args = ["--from", "gda[mcp]", "gda-mcp"]
[mcp_servers.gda-mcp.env]
GDA_PROJECT = "/absolute/path/to/your/godot/project"
Cursor — project scope, .cursor/mcp.json at the repo root (${workspaceFolder}
tracks the open project):
{
"mcpServers": {
"gda-mcp": {
"type": "stdio",
"command": "uvx",
"args": ["--from", "gda[mcp]", "gda-mcp"],
"env": {
"GDA_PROJECT": "${workspaceFolder}",
"PATH": "/opt/homebrew/bin:/usr/local/bin:${userHome}/.local/bin:${env:PATH}"
}
}
}
}
GUI-launched clients (Cursor, Claude Desktop) start with a minimal
PATH, so a bareuvxmay not resolve — the Cursor snippet repairs it inenv; ifuvxis still not found (or for Claude Desktop), use an absolute path (which uvx) forcommand. Full recipes — user vs project scope, Claude Desktop, per-agent project pinning — are in the registration recipes.
Quick start
Point gda at your Godot binary, then ask the engine its version:
export GDA_GODOT="/path/to/Godot" # or pass --godot to any command
gda info --json
# {"major":4,"minor":6,"patch":3,"status":"stable","string":"4.6.3-stable (official)",…}
stdout is always clean JSON you can pipe; all engine and script diagnostics go to stderr:
gda info --json | jq .major # → 4
Build a scene headlessly and read it back as a structured tree (nodes are addressed by their path relative to the scene root):
gda scene create game/main.tscn --root-type Node2D --json
gda node add game/main.tscn --type Sprite2D --name Hero --json
gda node set game/main.tscn --node Hero --property position --value 10,20 --json
gda scene get game/main.tscn --json
# {"path":"game/main.tscn","root":{"name":"main","type":"Node2D","children":[{"name":"Hero",…}]}}
Now drive a running game live — game/ must be a Godot project (a directory with
project.godot); macOS/Linux, Godot 4.6+:
gda daemon start --project game # start the daemon (installs the harness)
gda game tree --project game --json # the runtime scene tree, after _ready
gda perf monitors --project game --json # live engine counters: fps, memory, node count
gda daemon stop --project game
(gda screen capture works live too, but needs a windowed session — start the daemon
with gda daemon start --windowed.)
How it works
gda is three components serving operations in two modes:
| Component | Role |
|---|---|
gda |
The agent-facing CLI — exposes Godot with structured --json output. |
gda-mcp |
An MCP server exposing the same operations as tools, from --schema. |
gda-daemon |
A per-project process supervising a running game for live operations. |
- Headless operations need no running engine —
gdaruns them one-shot with nothing to install (create a scene, edit a script, export, analyze). - Live operations require a running game —
gda-daemonlaunches it, injects an inert in-game harness, and brokers requests over a Unix domain socket (runtime tree, input, screenshots, performance, diagnostics).
Platform & version support:
| Mode | Godot | Platforms |
|---|---|---|
| Headless | 4.4+ | macOS · Linux · Windows¹ |
Live (via gda-daemon) |
4.6+ | macOS · Linux² |
¹ Headless is cross-platform by design (one-shot processes, no platform-specific dependency) — Windows keeps the full headless surface, though CI does not exercise it yet. ² Live operations use Unix domain sockets, so Windows is not supported yet.
Command reference
gda commands are grouped by Godot domain object and use a small, consistent verb
vocabulary, so the same verb means the same thing in every group:
| Verb | Meaning |
|---|---|
create / delete |
Make / remove a standalone entity (scene, script, resource). |
add / remove |
Add / remove a sub-entity within a container (node → scene). |
get / list |
Read one entity / enumerate many. |
set |
Mutate a property. |
| domain verbs | play, run, export, import, … kept with their natural meaning. |
Every command supports --json and --schema — except gda schema itself, which emits
the aggregate manifest as JSON directly. Commands that read or mutate a res:// path
resolve a project context. Run gda <group> <command> --help for full
flags — gda --help is the authoritative list of what is installed.
Meta — about gda / the engine itself
| Command | What it does |
|---|---|
gda info |
Report the Godot engine version info. |
gda schema |
Emit the whole command surface as one machine-readable JSON manifest. |
Headless commands — Godot 4.4+, all platforms
scene — scene files (.tscn)
| Command | What it does |
|---|---|
scene create |
Create a new .tscn with the given root node type. |
scene get |
Read a scene and report its structured node tree. |
scene list |
Enumerate the .tscn scenes in the resolved project. |
scene get-exports |
List the @export properties a scene's nodes' scripts declare. |
scene delete |
Delete a scene file and report what was removed. |
node — nodes within a scene file
| Command | What it does |
|---|---|
node add |
Add a node under a parent (built-in type or class_name script). |
node get |
Read a node's properties (by node path) as typed JSON. |
node list |
List a scene's node tree with each node's path relative to the root. |
node set |
Set a node property, coercing the value to its declared Godot type. |
node remove |
Remove a node (and its subtree) by node path. |
node duplicate |
Duplicate a node (and its subtree) under its parent. |
node move |
Reparent a node (and its subtree) under a new parent. |
node connect-signal |
Wire a source node's signal to a target node's method. |
node disconnect-signal |
Unwire an existing signal→method connection. |
script — GDScript files (.gd)
| Command | What it does |
|---|---|
script create |
Create a new .gd script from a template or verbatim --content. |
script get |
Read a script's source plus its class_name / extends metadata. |
script list |
Enumerate the .gd scripts in the resolved project. |
script set |
Edit a script via search-replace, line-range, or full overwrite. |
script delete |
Delete a script file and report what was removed. |
script attach |
Attach a .gd script to a node (by node path) in a scene. |
script validate |
Syntax/compile-check a .gd script. |
project — the project as a whole (settings, autoloads, static analysis)
| Command | What it does |
|---|---|
project info |
Report project metadata (name, main scene, viewport, engine version). |
project get |
Read a single project setting by section/key as typed JSON. |
project set |
Set a project setting, coercing the value to its declared type. |
project add-autoload |
Register an autoload singleton (name → script/scene). |
project remove-autoload |
Unregister an autoload singleton by name. |
project find-references |
Find every project file that references a given resource. |
project dependencies |
Map each scene/resource to the resources it depends on. |
project find-unused-resources |
Find resource files that nothing references. |
project statistics |
Report the project's file/line counts, autoloads, and more. |
resource — resource files (.tres)
| Command | What it does |
|---|---|
resource create |
Create a new .tres resource of the given type. |
resource get |
Read a .tres resource's properties as typed JSON. |
resource set |
Set a .tres property, coercing the value to its declared type. |
resource delete |
Delete a .tres resource file and report what was removed. |
resource uid |
Resolve a resource UID ↔ its res:// path in both directions. |
export — export presets and artifacts
| Command | What it does |
|---|---|
export list |
Enumerate the project's export presets (name, platform, …). |
export get |
Report one preset's details plus export-template install status. |
export run |
Export a named preset (release / debug / pack) to a destination. |
shader — shader files (.gdshader)
| Command | What it does |
|---|---|
shader create |
Create a new .gdshader from a template or verbatim --content. |
shader get |
Read a shader's source plus its shader_type. |
shader set |
Edit a .gdshader via search-replace, line-range, or full overwrite. |
theme — theme resources (.tres)
| Command | What it does |
|---|---|
theme create |
Create a new, loadable .tres Theme resource (no-clobber). |
Live commands — via gda-daemon; Godot 4.6+, macOS/Linux
daemon — the live runtime lifecycle
| Command | What it does |
|---|---|
daemon start |
Start the per-project daemon and install the harness; the engine session launches on the first live op (--windowed for screen capture). |
daemon stop |
Stop the project's daemon and any running engine session. |
daemon status |
Report the daemon's state (running, windowed mode, session). |
daemon uninstall |
Remove the in-game gda harness autoload from the project. |
game — the running game's runtime scene graph
| Command | What it does |
|---|---|
game tree |
Read the running game's runtime scene tree (after _ready). |
game get |
Read a runtime node's live properties by node path. |
game set |
Set a runtime node property on the running game. |
diag — runtime diagnostics
| Command | What it does |
|---|---|
diag errors |
Tail the running game's runtime errors (categorized). |
diag log |
Tail the running game's raw output log (print + stderr). |
perf — performance monitoring
| Command | What it does |
|---|---|
perf monitors |
Snapshot the engine's performance counters (fps, memory, nodes, …). |
perf monitor |
Sample a node property or signal over a frame window (timeline). |
input — input simulation
| Command | What it does |
|---|---|
input key |
Inject a key event (with modifiers). |
input mouse-click |
Inject a mouse click at (x, y). |
input mouse-move |
Inject mouse motion to (x, y). |
input action |
Press/release a mapped input action. |
input sequence |
Inject a multi-frame event timeline. |
screen — viewport capture
| Command | What it does |
|---|---|
screen capture |
Capture one viewport frame to a PNG. |
screen frames |
Capture an N-frame PNG sequence. |
Global flags
| Flag | Description |
|---|---|
--json |
Emit the result as a single JSON object on stdout. Without it, commands print a concise human-readable rendering. |
--schema |
Emit the command's input/output JSON Schema contract (no Godot spawned). |
--godot |
Path to the Godot binary (overrides $GDA_GODOT and the default). |
--project |
Godot project directory for res:// resolution (overrides $GDA_PROJECT; defaults to the current directory if it is a project). Domain commands only. Resolving a project runs that project's code — see Project code execution. |
--help |
Show usage for gda or any command. |
Configuration
gda finds the Godot binary from the --godot <path> flag, otherwise the
GDA_GODOT environment variable — set one of these so gda can locate your engine.
Domain commands resolve a Godot project (so res:// paths and a scene's inter-resource
references resolve deterministically) in this order:
- The
--project <dir>flag. - The
GDA_PROJECTenvironment variable. - The current directory, when it is a Godot project (contains
project.godot).
A named directory must be a project, or gda reports it as an error. When none resolves,
gda runs projectless — only filesystem paths (absolute or cwd-relative) resolve, not
res://.
Project code execution — what runs when you point at a project
Resolving a project so res:// paths work runs Godot against that project, and Godot runs
some of the project's own code as part of that. Concretely:
- Autoloads run on every
--projectoperation. When a project is resolved, the engine constructs the project's autoload singletons at startup — before the command's own work runs — so their_init(and_ready) execute on every operation, including read-only ones likescene getandnode list. Without a resolved project, no autoloads are registered, so they do not run. - Commands that instantiate a scene execute that scene's attached scripts' constructors.
A command that needs a live node tree — every mutating command (
node add,node set,node remove, …), andnode get(which reports runtime property defaults the stored data does not carry) — loads and instantiates the scene, which constructs each node and runs the_initof any script attached to a node in it. Commands that only read the stored scene data (scene get,scene list,node list) walk it without instantiating, so they do not run those scripts.
gda treats the target project as trusted, so this is by design — see
ADR-0009 for the trust model.
Under the hood — the structured-output contract & exit codes
Headless Godot interleaves its banner, warnings, and print() output into stdout. gda
solves this with a sentinel contract
(ADR-0002):
-
The GDScript payload emits exactly one result, wrapped in unique sentinels on stdout:
<<<GDA:RESULT>>>{ …json… }<<<GDA:END>>> -
It routes all of its own diagnostics to stderr; stdout carries nothing but the contract.
-
gdaextracts and parses only the bytes between the sentinels, ignoring the surrounding engine noise, and surfaces stderr for inspection.
This is what makes gda's output safe to consume programmatically, and it generalizes to
the per-message protocol the daemon uses for live operations.
Exit codes (the CLI ABI). A failed gda run exits with a small, stable code so a shell
or agent can branch on the failure category without parsing the JSON error:
| Exit code | Category | When |
|---|---|---|
0 |
— | Success. |
127 |
environment |
The Godot binary could not be launched (shell convention: not found). |
124 |
environment |
Godot launched but did not return before the runner timeout (shell convention: timed out). |
3 |
version |
The detected Godot version is below the supported minimum. |
4 |
operation |
The engine ran but the operation failed — a registered operation error, an engine crash, or an unstructured non-zero exit. |
5 |
parse |
The process claimed success but violated the structured-output contract. |
These values are the public ABI; their authoritative source is
src/gda/exit_codes.py. The {"error": {category, code, …}}
envelope carries a finer code within each category (e.g. path_not_found,
already_exists, node_not_found all sit under operation / exit 4). The full
registry lives in
ADR-0002's GdaError.code table.
Development
uv sync # set up the environment
uv run pytest # run the full suite (includes e2e tests against a real Godot)
uv run pytest -m "not e2e" # unit tests only (no Godot binary required)
uv run pytest -m e2e # only the end-to-end tests (needs Godot 4.4+ on this machine)
The e2e tier runs by default with uv run pytest, and fails loudly — naming the
resolved path and how to fix it — if no Godot binary is found there, rather than skipping.
Deselect the whole tier with -m "not e2e" (CI's per-PR job uses exactly this).
src/gda/
cli.py # CLI entrypoint (Typer): all command groups, --json / --schema
surface.py # walks the live Typer tree → the `gda schema` manifest
headless.py # the per-command descriptor (one HeadlessCommand per command)
binary.py # Godot binary resolution (flag > $GDA_GODOT > default)
runner.py # the one-shot headless spawn seam (Protocol + subprocess impl)
live_runner.py # the live-operation client that talks to gda-daemon
models.py # typed I/O models (Pydantic) backing --json and --schema
errors.py / error_codes.py / exit_codes.py # failure classification + the CLI ABI
render.py # human-readable (non-JSON) rendering
ops/operations.gd # the headless GDScript payload, dispatched by operation name
daemon/ # gda-daemon: server, session supervision, IPC protocol, discovery
harness/ # the inert in-game `gda` autoload injected into a live session
mcp/ # gda-mcp: the schema → MCP-tool server
tests/ # unit + e2e tests against a real engine (shared fixtures in conftest.py)
docs/adr/ # architecture decision records
CONTEXT.md # the project's shared domain language
gda has two external boundaries, each behind a seam fast tests inject through: spawning a
one-shot headless process (runner.py) and talking to a running game via the daemon
(live_runner.py). The e2e suite drives a real engine across both.
Contributing
Contributions are welcome. Read CONTEXT.md to align with the project's
shared language, and review the relevant ADRs for the area you're touching.
Issues and PRDs live as GitHub issues.
Commits follow the Conventional Commits specification.
Working with an AI coding agent? This project is built to be agent-navigable —
AGENTS.mdis the entry point for coding agents, wiring in the project's rules, domain docs, and skills.
License
Released under the MIT License. Copyright (c) 2026 aigengame.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gda-0.1.36.tar.gz.
File metadata
- Download URL: gda-0.1.36.tar.gz
- Upload date:
- Size: 192.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1aca087415243ee470d1c8534ab605d7f0793f647d7f7d695df3d8f81df5547a
|
|
| MD5 |
0d603fb08d5984edf229c06121ee21b7
|
|
| BLAKE2b-256 |
540841d4e443efae7e85e5a4661a0b34ad20122508cfaccd317f86826525695b
|
Provenance
The following attestation bundles were made for gda-0.1.36.tar.gz:
Publisher:
release.yml on aigengame/godot-agent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gda-0.1.36.tar.gz -
Subject digest:
1aca087415243ee470d1c8534ab605d7f0793f647d7f7d695df3d8f81df5547a - Sigstore transparency entry: 1927522710
- Sigstore integration time:
-
Permalink:
aigengame/godot-agent@1b68eed24fad4b7b669a25fb3024ea0f82e1b9af -
Branch / Tag:
refs/heads/main - Owner: https://github.com/aigengame
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1b68eed24fad4b7b669a25fb3024ea0f82e1b9af -
Trigger Event:
push
-
Statement type:
File details
Details for the file gda-0.1.36-py3-none-any.whl.
File metadata
- Download URL: gda-0.1.36-py3-none-any.whl
- Upload date:
- Size: 209.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2cf2b33f7425d3a1278bed4f6125f336b76be3c5d8b34e1f6b009542bdf1556d
|
|
| MD5 |
1c94bfee898fd77620797b85100cd014
|
|
| BLAKE2b-256 |
24e8f7e3c54af4cd06f445238c2cf4f819bbc0d1328d442fc1f9cf133f46006a
|
Provenance
The following attestation bundles were made for gda-0.1.36-py3-none-any.whl:
Publisher:
release.yml on aigengame/godot-agent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gda-0.1.36-py3-none-any.whl -
Subject digest:
2cf2b33f7425d3a1278bed4f6125f336b76be3c5d8b34e1f6b009542bdf1556d - Sigstore transparency entry: 1927522940
- Sigstore integration time:
-
Permalink:
aigengame/godot-agent@1b68eed24fad4b7b669a25fb3024ea0f82e1b9af -
Branch / Tag:
refs/heads/main - Owner: https://github.com/aigengame
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1b68eed24fad4b7b669a25fb3024ea0f82e1b9af -
Trigger Event:
push
-
Statement type: