esc
navigate openSearch by Pagefind

Scripting and CI

The CLI's automation contract in bash and PowerShell: exit codes, JSON and TSV output, discovery, plus a complete CI smoke test.

The CLI is built to live inside scripts: results go to stdout, errors go to stderr as the JSON-RPC code and message, and exit codes follow one contract: 0 success, 1 command or connection error, 2 usage error. Everything on this page is composition on top of that.

Single commands are identical in every shell. godot-mcp project info pastes into bash, PowerShell, or cmd unchanged. The tabs below appear where pipelines diverge; PowerShell examples use its native object pipeline (ConvertFrom-Json) rather than transliterated bash.

Machine output

Piped output is pretty-printed JSON. The CLI detects the pipe and never emits color or table layout there, so a script has nothing to strip. (On a terminal, results render as tables and key/value boxes instead; --format json pins exact JSON anywhere.) --format tsv (global flags precede the group) re-renders a result for text tools: an object becomes key<TAB>value rows, an array of objects becomes a header row plus one row per element, and nested values stay as compact JSON. --format ndjson emits one compact JSON value per line when the result is a top-level array, and the whole result on a single line otherwise (most commands wrap arrays in an object, so expect one line there). Unlike TSV, nesting survives intact. The GODOT_MCP_FORMAT environment variable pins a format for a whole shell or CI job; the flag always wins, and an unrecognized value warns and falls back to the default.

Terminal window
godot-mcp engine commands --group node | jq -r '.methods[]'
godot-mcp --format tsv project info | awk -F'\t' '$1 == "main_scene" { print $2 }'
Terminal window
(godot-mcp engine commands --group node | ConvertFrom-Json).methods
# PowerShell parses JSON natively, so TSV is rarely needed:
(godot-mcp project info | ConvertFrom-Json).main_scene

Drive every node of a type

Queries compose with actions. batch find-nodes-by-type returns paths that feed straight back into node set:

Terminal window
godot-mcp batch find-nodes-by-type --type Label \
| jq -r '.matches[].path' \
| while read -r p; do
godot-mcp node set --node-path "$p" --property visible --value false
done
Terminal window
(godot-mcp batch find-nodes-by-type --type Label | ConvertFrom-Json).matches.path |
ForEach-Object { godot-mcp node set --node-path $_ --property visible --value false }

Preflight, then launch

godot-mcp doctor checks the environment (godot binary, project, addon install state, port config, dotnet) and exits 1 only on a real failure. “No editor running yet” is a warning, so it is safe to run before you have launched anything. godot-mcp status gives the machine-readable liveness verdict (running / starting / crashed / closed), and the launch policy follows from it: never start a second editor when one is running. godot-mcp status --all scans the editor auto range (9080-9095, plus any pinned port) and the game range (9200-9215), then lists every live instance with its port, project, version, and pid. That is the multi-instance view for when two editors are open at once. godot-mcp launch applies that policy for you: it starts one editor for the project, waits until it answers, and returns non-zero if it never does.

The cold half: import, check, test, export, run

Five subcommands spawn the engine directly instead of talking to an editor, which is what a CI job needs before an editor exists. Each writes the engine’s output to <project>/.godot/godot-mcp-<name>.log and returns the exact argv it ran, so a failing step is reproducible by hand.

  • godot-mcp import builds the .godot/ import cache a fresh checkout has none of. Run it first. It refuses while an editor is running on the project, because both own that cache; the live-editor form is import reimport --path res://....
  • godot-mcp check . --jobs 4 parses every .gd under the paths you name with --check-only and exits 1 with file:line per failure. Directories are walked recursively, skipping .godot/ and addons/ unless the path you name is itself inside addons.
  • godot-mcp test runs the pure-logic tests in res://test/ cold, with no editor and no scene tree. A test file is any script that extends RefCounted, every test_* method is a test, and the method takes one argument: a harness carrying t.ok, t.eq, t.ne and t.fail. It exits 1 on a failed assertion, on a script error raised inside a test, and on a file that does not parse.
  • godot-mcp export "<preset>" runs the headless export, writes to the preset’s own export_path unless --out says otherwise, and reports the file produced, its size, and the engine’s parsed errors. An export that exits 0 having written nothing exits non-zero here, which is what missing export templates look like.
  • godot-mcp run [scene] --headless starts the game standalone with no editor at all. With the godot_mcp/runtime/direct_server project setting on, it waits for the game’s own channel and reports the port to drive with godot-mcp --game.

export, import and test are addon group names as well. A command name after any of them (export list-presets, export info, import reimport, test report) still reaches the running editor.

A complete CI smoke test

#!/usr/bin/env bash
set -euo pipefail
godot-mcp doctor --project . --json > /dev/null # environment sanity: exit 1 on real problems
godot-mcp import # build .godot/ on a fresh checkout
godot-mcp check . --jobs 4 # parse every .gd; exit 1 on any failure
godot-mcp test # run res://test/; exit 1 on any failure
godot-mcp launch --headless # one editor, and never a second
EDITOR_PID=$(godot-mcp status | jq -r .pid)
godot-mcp project info > /dev/null # the editor answers
godot-mcp scene play --mode main
godot-mcp runtime eval --code 'emit(get_tree().current_scene.name)' \
| jq -e '.output[0] == "Main"' # assert against the running game
godot-mcp scene stop
kill $EDITOR_PID # export reads the files on disk, so close first
godot-mcp export "Windows Desktop" # exit 1 if nothing landed
Terminal window
$ErrorActionPreference = "Stop"
godot-mcp doctor --project . --json | Out-Null # environment sanity: exit 1 on real problems
if ($LASTEXITCODE) { exit 1 }
godot-mcp import | Out-Null # build .godot/ on a fresh checkout
if ($LASTEXITCODE) { exit 1 }
godot-mcp check . --jobs 4 | Out-Null # parse every .gd
if ($LASTEXITCODE) { exit 1 }
godot-mcp test | Out-Null # run res://test/
if ($LASTEXITCODE) { exit 1 }
godot-mcp launch --headless | Out-Null # one editor, and never a second
if ($LASTEXITCODE) { exit 1 }
$editorPid = (godot-mcp status | ConvertFrom-Json).pid
godot-mcp project info | Out-Null # the editor answers
if ($LASTEXITCODE) { exit 1 }
godot-mcp scene play --mode main | Out-Null
$name = (godot-mcp runtime eval --code "emit(get_tree().current_scene.name)" |
ConvertFrom-Json).output[0]
if ($name -ne "Main") { exit 1 } # assert against the running game
godot-mcp scene stop | Out-Null
Stop-Process $editorPid # export reads the files on disk, so close first
godot-mcp export "Windows Desktop" | Out-Null # exit 1 if nothing landed
if ($LASTEXITCODE) { exit 1 }

Every step exits non-zero on failure, so set -e (bash) or the explicit $LASTEXITCODE checks (PowerShell) turn any broken link into a red build.

The catalog is data

engine commands returns the whole tool surface as JSON: the method list, a group map, and per-command param docs (attached for --group, or --docs for the full catalog). Generators become trivial: bindings, wrappers, forms, docs that cannot drift.

Terminal window
# every node command with its required flags
godot-mcp engine commands --group node \
| jq -r '.docs | to_entries[] | .key + " " + ([.value.params[]? | select(.required) | "--" + .name] | join(" "))'
Terminal window
# every node command with its required flags
$cat = godot-mcp engine commands --group node | ConvertFrom-Json
$cat.docs.PSObject.Properties | ForEach-Object {
$_.Name + " " + ((@($_.Value.params) | Where-Object required | ForEach-Object { "--" + $_.name }) -join " ")
}

Standalone game rigs

With the direct-to-player channel enabled, a QA rig needs no editor at all: run a debug build, then godot-mcp --game runtime tree, --game runtime eval …, and --game input … drive it directly. See Playtest loop.

Built with the help of godot-mcp. MIT licensed.