Environment variables
All knobs are optional; the defaults serve a local single-user setup.
Server
| Variable | Default | Meaning |
|---|---|---|
GODOT_MCP_TRANSPORT | stdio | stdio or http |
GODOT_MCP_HTTP_HOST | 127.0.0.1 | HTTP bind host |
GODOT_MCP_HTTP_PORT | 9090 | HTTP bind port (/mcp is FastMCP's mount path) |
GODOT_MCP_AUTH_TOKEN | unset | Bearer token; required for non-loopback HTTP binds (fail-fast, #226) |
GODOT_MCP_HTTP_HOST_ORIGIN_PROTECTION | auto | HTTP Host/Origin (DNS-rebinding) guard (#636): auto validates the Host header + browser Origin for a loopback-bound server (foreign Host → 421, foreign Origin → 403); false opts out; true/strict validates every request |
GODOT_MCP_BRIDGE_URL | ws://127.0.0.1:9080 | bridge listen (server) / connect (addon) URL |
GODOT_MCP_BRIDGE_TOKEN | unset | opt-in shared secret for the bridge handshake (#538) — when set, the addon must send the matching token as its first message or the peer is refused at the handshake; never logged |
GODOT_MCP_PROJECT_DIR | unset | explicit project dir; else the connected editor's project is used |
GODOT_MCP_DEFAULT_TOOLSETS | unset (= inspection) | seed the initial enabled toolsets: all or a comma-separated list (e.g. scene_edit,runtime); unknown names are logged and ignored |
GODOT_MCP_GODOT_BIN | godot on PATH | the Godot binary the server shells out to for headless runs and parse/compile checks (distinct from the addon's editor; also settable as GODOT_BIN on CI) |
GODOT_MCP_LOG_LEVEL | INFO | structured stderr logging level (JSON per line) |
GODOT_MCP_APPROVAL_WEBHOOK | unset | human-in-the-loop approval endpoint for destructive tools (#153); unset → auto-approve |
GODOT_MCP_APPROVAL_TIMEOUT | 30.0 | per-approval-request timeout, seconds |
GODOT_MCP_APPROVAL_FAIL_OPEN | false | on an unreachable/slow webhook: false denies (fail closed, #638), true approves |
GODOT_MCP_CHECK_UPDATES | unset (= off) | opt-in update check (#629): when set (1/true/yes) the server may query the release feed so godot_check_for_updates can report a newer release; unset = no network |
Addon (read in the editor's environment)
| Variable | Default | Meaning |
|---|---|---|
GODOT_MCP_BRIDGE_URL | ws://127.0.0.1:9080 | where the addon dials the server (same var as the server) |
GODOT_MCP_BRIDGE_TOKEN | unset | the same opt-in secret as the server — the addon authenticates with it as its first message after connecting; never logged |
GODOT_MCP_AUTO_REFRESH | unset (= off) | opt-in timer-based filesystem auto-refresh (#561): 1/true/yes enables a periodic EditorFileSystem.scan() so external edits (agents, git, other tools) show up without editor focus; the dock checkbox is the runtime override |
GODOT_MCP_AUTO_REFRESH_INTERVAL | 10 | seconds between auto-refresh scans (floor 2s; only read when GODOT_MCP_AUTO_REFRESH is on) |
GODOT_BIN | godot on PATH | where a CI workflow points the server's binary lookup (e2e.yml Actions var) — the server itself reads GODOT_MCP_GODOT_BIN, then godot/Godot on PATH, then the macOS app bundle |
Security model
| Mode | Bind | Auth | Why |
|---|---|---|---|
stdio (default) | N/A (local subprocess) | none | only the spawning client can talk to it |
http on loopback | 127.0.0.1 | none | only local processes reach it |
http non-loopback | 0.0.0.0 / external IP | GODOT_MCP_AUTH_TOKEN required | the server refuses to start otherwise (#226) — without a token, anyone on the network could write scripts, run the game, or export builds through your editor |
# Generate + set a token
TOKEN=$(openssl rand -hex 32)
GODOT_MCP_TRANSPORT=http GODOT_MCP_HTTP_HOST=0.0.0.0 GODOT_MCP_AUTH_TOKEN=$TOKEN \
uv run godot-editor-mcpClients pass the same token (HTTP Authorization: Bearer … header).
Bridge handshake auth (#538)
The editor↔server WebSocket bridge is localhost-only; the bridge token adds a shared secret for shared/self-hosted runners and remote-editor setups. It is opt-in on each side — unset means no auth (zero-config local dev, unchanged path). Both sides read GODOT_MCP_BRIDGE_TOKEN.
| Server token | Addon token | Behavior |
|---|---|---|
| unset | unset | today's path — no auth exchange (byte-identical) |
| set | set, matching | peer authenticated, everything serves |
| set | set, mismatched | structured refusal at the handshake; the addon drops into its reconnect/backoff loop and the dock log shows the refusal reason each attempt until the tokens agree |
| set | unset | refused at the handshake (the addon never authenticates) — same visible reconnect loop |
| unset | set | the server consumes the auth envelope as a control message and serves anyway (forward-compatible: a newer addon against an older server) |
A refusal happens at the handshake — never as per-command errors, never as a silent drop. Tokens are compared in constant time and are never logged on either side.
# Shared/self-hosted runner: pin the same token on both sides
TOKEN=$(openssl rand -hex 32)
GODOT_MCP_BRIDGE_TOKEN=$TOKEN uv run godot-editor-mcp # server
GODOT_MCP_BRIDGE_TOKEN=$TOKEN godot --editor --path ./game # the editor's envListener hardening (#636)
Browsers do not apply the same-origin policy to WebSockets, and DNS rebinding can make a page the user visits resolve to 127.0.0.1. Both listeners are hardened by default:
- Bridge WebSocket: the listener accepts only Origin-less handshakes (
origins=[None]) — the Godot addon'sWebSocketPeersends noOrigin, so a browser page is rejected at the handshake (403) before it can reach the bridge. A bad-or-missing-token peer is also refused before it can displace the authenticated editor (#635). - HTTP transport: FastMCP's
HostOriginGuardMiddlewareis enabled by default (GODOT_MCP_HTTP_HOST_ORIGIN_PROTECTION=auto), so a non-loopbackHostheader is refused (421) and a foreign browserOriginis refused (403) before any code-executing tool (write_script,run_and_capture,export_project) is reachable. Opt out withGODOT_MCP_HTTP_HOST_ORIGIN_PROTECTION=false.
Neither guard replaces the auth token — a non-loopback HTTP bind still requires GODOT_MCP_AUTH_TOKEN.
Logging
Logs are one JSON object per line on stderr (stdout is the stdio protocol channel). Every log line carries the command id where applicable; errors include exception info. No secrets or full file contents are logged.