Skip to content

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Godot Agents Devcontainer for Indie Developers

A devcontainer setup that runs Claude Code and OpenCode with two Godot MCP servers for AI-assisted Godot game development.

Ideal for indie or solo game developers, which simply would like solid tooling without wanting to run an entire game studio using AI.

What's Included

  • Claude Code CLI running with --dangerously-skip-permissions inside a sandboxed container
  • OpenCode — Alternative AI coding agent with multi-provider support (OpenAI, Anthropic, Google, local models, etc.)
  • godot-mcp — Full Godot editor integration (21 tools, 86 actions): scene and node editing, resource and animation authoring, plus a full playtesting loop — run the game, inject input, screenshot it, step the game clock, and read live runtime state
  • minimal-godot-mcp — LSP-based diagnostics (4 tools): GDScript error checking, workspace scanning, console output
  • auto_reload addon — reloads the open scene and its scripts within ~1s of an external change, so files written from inside the container show up in the editor without a manual reload (vendored from GoPeak, MIT)
  • blender-mcp — Drives a Blender session running on your host (22 tools): scene and object inspection, viewport screenshots, arbitrary Python against the scene, and asset sourcing from Poly Haven, Sketchfab, Hyper3D Rodin and Hunyuan3D. Blender stays on the host — only the small Python client is in the container
  • Godot headless CLI — Run scenes, export projects, execute GDScript, and validate projects from the command line (godot --headless)
  • In-container rendering (optional, off by default) — OpenGL via Xvfb and Mesa, so Godot can draw real frames for screenshots and videos without a host display. See Optional: rendering in the container
  • Asset generation tools — ImageMagick, FFmpeg, Python/Pillow, trimesh, gltf-transform, obj2gltf, fbx2gltf
  • Headless Blender (optional, off by default) — The official Blender LTS build for scripted modelling and glTF export (blender --background --python), see Optional: headless Blender
  • Audio tools (optional, off by default) — Procedural sound effects (numpy/scipy, pedalboard, pyo, jsfxr) and MIDI music rendering (FluidSynth with two General MIDI soundfonts), see Optional: audio tools
  • Headroom (optional, off by default) — Local compression proxy wrapping Claude Code and OpenCode, reducing token usage without sending anything off-machine (usage beacon disabled)

What's NOT included

  • Godot specific skills - This is very subjective and each developer may have different preferences when it comes to skills. The setup will source your skills and global Claude setup based on an environment variable (see 2. Configure environment in the Setup guide). Personal recommendation for a good comprehensive Godot skill: Godot skill for Claude Code
  • Blender with a GUI: The Blender MCP talks to a Blender you run on the host, so interactive modelling happens there. See Set up Blender. For scripted, windowless work, an optional headless Blender can be built into the image (Optional: headless Blender)

Prerequisites

  • Docker (20.10+) installed and running
    • macOS / Windows: Docker Desktop (recommended)
    • Windows (WSL2): Docker Desktop with WSL2 backend, or Docker Engine inside WSL2
    • Linux: Docker Engine or Docker Desktop
  • Node.js (18+) on the host (for npm scripts and devcontainer CLI)
  • socat (Linux only) — bridges Godot's and Blender's localhost ports to the Docker network. Not needed on macOS or Windows where Docker Desktop handles this natively. Install with sudo apt-get install socat
  • Godot 4.5+ editor installed on the host
  • Blender 3.0+ on the host — optional, only for the Blender MCP. Everything else works without it

Setup

1. Clone and install

git clone <this-repo>
cd godot-agents-devcontainer
npm install

2. Configure environment

cp .env.example .env

Edit .env and set the required variables:

# Required: absolute path to your Godot project
GODOT_PROJECT_PATH=/home/you/projects/my-godot-game

# Your Claude Code user config directory (skills, CLAUDE.md, etc.)
# Default: $HOME/.claude (standard Claude Code setup)
CLAUDE_USER_CONFIG_DIR=$HOME/.claude

# Optional: Godot headless CLI version (default: 4.7.2)
# GODOT_VERSION=4.7.2

# Optional: in-container rendering (on/off, default off) and which graphics
# API `godot-render` uses. See "Optional: rendering in the container" below.
# RENDER_OPENGL=off
# RENDER_DEFAULT=opengl
# RENDER_DEVICE=/dev/dri

# Optional: long-lived Claude Code token so the container stays logged in
# (only needed if you also use the same Anthropic account elsewhere).
# See "Staying logged in across days" below.
# CLAUDE_CODE_OAUTH_TOKEN=

CLAUDE_USER_CONFIG_DIR should point to a directory containing any of:

  • skills/ — custom skills (shared by Claude Code and OpenCode)
  • CLAUDE.md — global instructions for Claude Code (also read by OpenCode as fallback)
  • AGENTS.md — global instructions for OpenCode (takes precedence over CLAUDE.md)

If you manage your Claude config in a separate repo (e.g., with CLAUDE.md as a symlink to another file), point this to that directory. Symlinks within the directory resolve correctly inside the container.

3. Build and start the devcontainer

npm run build
npm run up

4. Log in to Claude Code

On first use, log in inside the container:

npm run claude

Claude Code will prompt you to authenticate. Credentials are stored in a Docker volume and persist across container restarts.

Staying logged in across days (optional long-lived token)

Interactive claude login is all you need if this container is the only place you use that Anthropic account. But if you also run Claude Code on your host (or another machine) under the same account, the container tends to get logged out — usually on the first session of the day. This is not a bug in this setup: Anthropic uses rotating, single-use refresh tokens, so when your host refreshes the account's token it invalidates the copy stored in the container's volume.

The fix is a long-lived token that is independent of that rotation. It's optional — leave CLAUDE_CODE_OAUTH_TOKEN unset and everything works as before.

  1. Requirements: an active Claude subscription (Pro, Max, or Team). This token is created from your Claude subscription account via the CLI below — it is not a console.anthropic.com API key (those bill against the pay-as-you-go API, not your subscription).
  2. On a machine that has a browser and Claude Code installed (e.g. your host, not the headless container), run:
    claude setup-token
    Complete the browser sign-in. It prints a token and the line export CLAUDE_CODE_OAUTH_TOKEN=<token>. Copy the <token> value.
  3. Add it to .env (which is git-ignored — never commit it):
    CLAUDE_CODE_OAUTH_TOKEN=<token>
  4. Recreate the container so the token is injected:
    npm run up
    From now on npm run claude authenticates with the token automatically — no claude login needed, and it survives shutdowns and multi-day gaps.

Notes:

  • Where it lives. npm run up writes the token to ~/.claude/.oauth-token in the container (mode 600), not into the container's environment: devcontainer exec passes that environment on the docker exec command line, where ps would show it.
  • Inference-only. Long-lived tokens are scoped to inference, which covers normal coding.
  • Expiry. The token is long-lived (about a year), not infinite — regenerate with claude setup-token when it eventually expires.
  • If the token is set, it overrides any interactive login. To go back to claude login, blank out CLAUDE_CODE_OAUTH_TOKEN in .env and run npm run up again.
  • Still see a login screen after setting the token? A leftover ~/.claude/.credentials.json from a previous interactive login can make the TUI prompt even though the token authenticates fine. npm run up clears it automatically when the token is set; to fix it immediately, delete that file once (npm run shell → rm ~/.claude/.credentials.json).

5. Set up Godot for MCP integration

Install the godot-mcp addon

The addon connects on port 6550 by default — override with GODOT_WS_PORT in .env if you need a different port (for example, running multiple instances in parallel; see Running multiple instances in parallel).

The addon is installed into your Godot project's addons/ directory automatically every time the container starts, from the version pinned in the image. This keeps the addon and the MCP server in lockstep — a mismatched addon does not error, it silently drops whole tools. Re-running is a no-op when the versions already match, and it will never downgrade a newer addon.

To install it manually (for example when running without the devcontainer):

npm run install-godot-addon

Enable the addon in Godot

  1. Open your Godot project in the editor
  2. Go to Project > Project Settings > Plugins
  3. Enable the godot-mcp plugin

After the addon is upgraded to a new version, re-enable the plugin and restart the editor so the new commands register.

Enable auto-reload (recommended)

In the same Plugins list, also enable Godot MCP Auto Reload. It polls once a second and reloads the open scene and its attached scripts when they change on disk, so edits an agent makes from inside the container appear in the editor without you doing anything.

Its scope is narrower than the name suggests: it watches only the currently edited scene and the scripts attached to nodes in it. For anything else, the agent should call godot_scene reload explicitly. Details and provenance in .devcontainer/addons/README.md.

Enable the LSP server (for minimal-godot-mcp)

  1. In Godot, go to Editor > Editor Settings > Network > Language Server
  2. Ensure the language server is enabled
  3. Note the port (default: 6005 — override with GODOT_LSP_PORT if changed)

Enable the Debug Adapter (optional)

Only needed for get_console_output.

  1. In Godot, go to Editor > Editor Settings > Network > Debug Adapter
  2. Ensure the debug adapter is enabled
  3. Note the port (default: 6006 — override with GODOT_DAP_PORT if changed)

(Optional) Set up Blender for blender-mcp

Skip this if you don't need Blender. The rest of the setup works without it.

The MCP server in the container talks to an addon running inside Blender on your host. The addon is vendored in this repo so it stays version-matched to the pinned blender-mcp package — installing it from GitHub instead risks the two drifting apart and failing silently.

  1. In Blender, go to Edit > Preferences > Add-ons
  2. On Blender 4.2+, use the dropdown at the top-right and choose Install legacy Add-on (upstream's docs say "Install from file", which predates the Extensions platform and no longer matches the UI)
  3. Select .devcontainer/blender/addon.py from this repo
  4. Tick Interface: Blender MCP to enable it
  5. In the 3D viewport press N, open the BlenderMCP tab, and click Connect to MCP server — if you overrode BLENDER_PORT in .env, set the matching port in that same tab before connecting

Step 5 is needed after every Blender launch — the socket server does not autostart. Blender must be running with a GUI; the addon refuses to start under blender --background because its commands are dispatched on the UI thread.

The optional asset integrations (Poly Haven, Sketchfab, Hyper3D Rodin, Hunyuan3D) are toggled in that same sidebar tab. Poly Haven is free; the others need API keys entered there.

Security note. execute_blender_code runs arbitrary Python on your host, outside the container's isolation and with your user's privileges. That is the point of the tool, but it means the "Docker provides filesystem and process isolation" property below does not hold for this path. It is a broader exposure than godot_exec (full Python rather than GDScript). If that is not a trade you want, leave the addon disabled — the MCP server simply fails to connect and nothing else is affected.

6. Start developing

With Godot running on the host, start the container and launch your preferred AI coding agent:

npm run up
npm run claude    # or: npm run opencode

On Linux, a port bridge starts automatically with the container, relaying Godot's localhost-bound ports to the Docker network. On macOS and Windows, Docker Desktop handles this natively — no bridge needed. Both Claude Code and OpenCode have access to all MCP tools on all platforms. You can verify with the /mcp command inside Claude Code.

When done:

npm run down

Running multiple instances in parallel

You can work on several Godot games at once, each in its own devcontainer instance. Each clone of this repo already gets its own container name, image tag, and named volumes automatically — the devcontainer CLI derives them from the clone's own path — so no repo changes are needed for that part. What you do need to handle yourself is port collisions, since several MCP/bridge ports default to the same values across every clone.

  1. Clone this repo again, once per project:

    git clone <this-repo> ../my-other-game-devcontainer
    cd ../my-other-game-devcontainer
    npm install
  2. Give each clone distinct ports. Copy .env.example to .env, set GODOT_PROJECT_PATH to that project, and set all four port variables to values not used by any other clone running on the same host. For example:

    Clone GODOT_WS_PORT GODOT_LSP_PORT GODOT_DAP_PORT BLENDER_PORT
    1st (defaults) 6550 6005 6006 9876
    2nd 6551 6015 6016 9877
    3rd 6552 6025 6026 9878

    Leaving any of these at the shared default while another instance is running will collide — this is the one failure mode nothing in this repo can catch for you, so double check .env in each clone before starting it.

  3. Match the port on the Godot side, per project. The godot-mcp addon's WebSocket port is a per-project Godot setting, stored in that game's own project.godot — not something this repo controls. Set it to match GODOT_WS_PORT either through the addon's MCP panel at the bottom of the Godot editor, or by editing project.godot directly:

    [godot_mcp]
    port_override_enabled=true
    port_override=6551
  4. LSP/DAP only need attention if you keep two Godot editor windows open at the same time. Godot's Language Server and Debug Adapter ports (Editor Settings > Network) are a single global setting per editor install, not per-project — if you only ever have one Godot editor open (even switching between projects), this never matters, the one running instance just uses the default port. It only becomes a real conflict when two editor processes are open simultaneously: both would try to bind the same default port, and the second one to start simply fails to enable its LSP/DAP for that session (logged in its own Output panel, not a crash, and it won't connect to the other project's server either — the port bind just fails cleanly). Enable Editor Settings > Network > Language Server (and Debug Adapter, if used) once — that toggle is global and only needs setting up the first time — then override the port per-launch with Godot's own CLI flags instead of touching that global setting:

    godot -e --path /path/to/second/project --lsp-port 6015 --dap-port 6016

    matching that clone's GODOT_LSP_PORT/GODOT_DAP_PORT.

  5. Blender: if you're also running a second Blender session, set its BlenderMCP panel port to match BLENDER_PORT before clicking "Connect to MCP server".

  6. Start each instance from its own clone directory:

    npm run build && npm run up && npm run claude   # or: npm run opencode

    Verify each with npm run bridge:doctor — it reports that clone's own ports and only touches that clone's own container and bridge relay.

Available Commands

Command Description
npm run build Build the container image
npm run up Start the container (auto-starts port bridge on Linux; skipped on macOS/Windows)
npm run down Stop and remove the container (auto-stops port bridge on Linux)
npm run shell Open a shell inside the container
npm run bridge:start Manually start host-side port bridge (Linux only; no-op on macOS/Windows)
npm run bridge:stop Manually stop the port bridge (Linux only)
npm run bridge:status Show whether host-side bridge relays are listening
npm run bridge:doctor End-to-end health check: Godot ports, host bridge, container-side relay, addon version
npm run doctor:render Render a test frame under Xvfb and report which device drew it (needs RENDER_OPENGL=on)
npm run claude Launch Claude Code with --dangerously-skip-permissions
npm run claude:resume Resume a previous Claude Code session
npm run claude:prompt -- "prompt" Run a one-shot prompt
npm run opencode Launch OpenCode TUI
npm run opencode:prompt -- "prompt" Run a one-shot prompt with OpenCode
npm run install-godot-addon Install godot-mcp addon into the Godot project

Note: npm up is a built-in npm alias for npm update. Always use npm run up (with run) to start the container.

How It Works

macOS / Windows (Docker Desktop)

Host Machine                          Container
+------------------+                  +--------------------+
| Godot 4.5+       |  host.docker.    | Claude Code CLI    |
|  127.0.0.1:6550  |  internal        |   godot-mcp        |
|  127.0.0.1:6005  | <--------------> |   minimal-godot-   |
|  127.0.0.1:6006  |  (native)        |     mcp            |
+------------------+                  |   blender-mcp      |
| Blender 3.0+     |                  |                    |
|  127.0.0.1:9876  | <--------------> |                    |
+------------------+                  +--------------------+
                                      | /workspace (bind)  |
                                      |   = Godot project  |
                                      +--------------------+
                                      | ~/.claude (volume)  |
                                      |   + skills/ (link)  |
                                      |   + CLAUDE.md (link) |
                                      +--------------------+

Docker Desktop resolves host.docker.internal to the host and can reach localhost-bound ports natively. No bridge needed.

Ports shown are defaults — all four are overridable via .env (see Running multiple instances in parallel).

Linux (Docker Engine)

Host Machine                          Container
+------------------+                  +--------------------+
| Godot 4.5+       |                  | Claude Code CLI    |
|  127.0.0.1:6550  |   bridge.sh      |   godot-mcp        |
|  127.0.0.1:6005  | ------------->   |   minimal-godot-   |
|  127.0.0.1:6006  |  (host socat)    |     mcp            |
+------------------+                  |   blender-mcp      |
| Blender 3.0+     |                  |                    |
|  127.0.0.1:9876  | ------------->   |                    |
+------------------+  binds on        +--------------------+
                      docker bridge    | /workspace (bind)  |
                      172.17.0.1       |   = Godot project  |
                          |            +--------------------+
                          +-- socat -> | ~/.claude (volume)  |
                         (container)   |   + skills/ (link)  |
                                       |   + CLAUDE.md (link) |
                                       +--------------------+

Godot binds to 127.0.0.1, but the container reaches the host via the Docker bridge gateway (172.17.0.1). The host-side bridge (bridge.sh / socat) relays between these interfaces. Container-side socat forwards localhost to host.docker.internal.

Blender's addon binds 127.0.0.1:9876 the same way, so it needs the same host-side relay. It needs no container-side relay though: blender-mcp honours a BLENDER_HOST environment variable, so it is simply pointed at host.docker.internal directly. (minimal-godot-mcp hardcodes 127.0.0.1 in its LSP and DAP clients, which is why those two ports need the extra hop.)

Ports 6006 and 9876 are treated as optional by npm run bridge:doctor — it reports them as — rather than failing, since the Debug Adapter and Blender are both commonly not running.

Ports shown are defaults — all four are overridable via .env (see Running multiple instances in parallel).

Common to all platforms

  • Your Godot project is bind-mounted into the container at /workspace
  • Your Claude user config (CLAUDE_USER_CONFIG_DIR) is mounted read-only; skills and CLAUDE.md are symlinked into the persisted ~/.claude volume on startup
  • The container has unrestricted network access (Docker provides filesystem and process isolation)

Asset Generation Tools

The container includes tools that Claude Code can use to generate and manipulate game assets:

Tool Type What it does
ImageMagick 2D Image manipulation, format conversion, compositing (convert CLI)
Pillow (Python) 2D Programmatic texture/sprite generation, pixel art, normal maps
numpy (Python) 2D/3D Numerical operations for procedural generation, used by Pillow and trimesh
FFmpeg Audio Audio format conversion, simple sound effect generation (ffmpeg CLI)
trimesh (Python) 3D Procedural mesh generation (primitives, extrusions, booleans), export to glTF/OBJ/STL
gltf-transform 3D Optimize, compress (Draco/meshopt), merge, convert glTF files
obj2gltf 3D Convert OBJ models to glTF
fbx2gltf 3D Convert FBX models to glTF (Node.js API, use via node -e "require('fbx2gltf')(input, output)")

Optional: audio tools

For procedural sound effects, music and speech, three independent switches in .env install extra audio tooling. All are off by default because of the image size they add. The agent renders sounds to files in your project. It cannot hear them, so it checks its work with spectrogram images and loudness stats, and you do the actual listening.

Switch Installs Size
AUDIO=on SFX stack: scipy + soundfile (DSP, WAV I/O), pedalboard (effects: reverb, delay, filters, compression, distortion), pyloudnorm (loudness), librosa (analysis), pyo (synthesis, offline render), jsfxr (retro sfxr presets) ~900 MB
AUDIO_MUSIC=on Music: mido (write MIDI), FluidSynth (render MIDI), soundfonts FluidR3_GM and GeneralUser GS in /usr/share/sounds/sf2/ ~350 MB
SPEECH=on Speech: espeak-ng (robotic, many languages, SSML; sings with librosa pitch shifting from AUDIO=on) and Kokoro-82M via kokoro-onnx (natural voices, Apache-2.0, fast on CPU), model in /opt/kokoro/ ~450 MB
AUDIO=on
AUDIO_MUSIC=on
SPEECH=on
npm run build && npm run up

Check the install with the smoke test, which renders a few sounds with every installed tool (and skips the parts that are switched off):

bash ~/.devcontainer/audio-smoke.sh

Sound effects are written as 16-bit 44.1 kHz WAV and music as OGG Vorbis, the two formats Godot handles best.

Optional: headless Blender

For modelling and export from scripts, without a GUI or the MCP connect step, BLENDER=on installs the official Blender LTS build (pinned by BLENDER_VERSION in the Dockerfile) as blender. It is off by default because it adds about 1 GB.

# .env
BLENDER=on
npm run build && npm run up

Check it, then run a script in the background mode:

blender --version
blender --background --python make_mesh.py -- --out /workspace/assets/x.glb

Arguments after -- reach the script as sys.argv. Export with bpy.ops.export_scene.gltf(filepath=..., export_format="GLB") and check the result with gltf-transform validate. Blender renders nothing here; for pictures of the result, import it into Godot and capture that (see the rendering section below).

Godot Headless CLI

The container includes the Godot engine binary (v4.7.2 by default), usable via godot --headless for:

  • Running scenes: godot --headless --path /workspace -s res://script.gd
  • Automated testing: Run test frameworks like GUT or GdUnit4 from the command line
  • Exporting projects: godot --headless --path /workspace --export-release "Linux" build/game
  • Project validation: godot --headless --path /workspace --check-only

The version can be changed by setting GODOT_VERSION in your .env file before building the container.

Note: Without RENDER_OPENGL=on the container has no display server, so always use --headless. The Godot editor runs on your host machine. To draw real frames in the container, see the next section.

Optional: rendering in the container

--headless runs scripts and tests but never draws pixels. For screenshots and videos from scripts (CI, automated playtests, capture), switch on a rendering pathway. There is one switch per graphics API, both off by default:

Setting When What it does
RENDER_OPENGL build on installs Xvfb (a virtual X display) and Mesa's OpenGL drivers
RENDER_VULKAN build on installs Xvfb and Mesa's Vulkan drivers (lavapipe, on the CPU), for Forward+ / Mobile
RENDER_DEFAULT run time Which pathway godot-render uses: opengl (default) or vulkan
RENDER_DEVICE run time Optional, Linux: pass the host GPU in (see below)
# .env
RENDER_OPENGL=on
# RENDER_DEFAULT=opengl
# RENDER_DEVICE=/dev/dri

Then npm run build && npm run up (changing only RENDER_DEFAULT or RENDER_DEVICE needs just npm run up), and check it:

npm run doctor:render            # tests RENDER_DEFAULT
npm run doctor:render -- opengl  # tests a specific pathway
# Pathway: opengl
# Device:  ... Using Device: Mesa/X.org - llvmpipe (LLVM ...)
# OK: real frame drawn, saved to /tmp/render-doctor/frame.png

The doctor renders a tiny built-in scene, independent of your project, and fails if the frame is blank or the engine logs an error. If the pathway is not installed it fails with a hint on what to switch on.

Run your own scenes with godot-render, which wraps xvfb-run and godot with the right display, rendering and audio flags for RENDER_DEFAULT; all other arguments pass through to Godot. On OpenGL it forces the Compatibility rendering method, so Forward+ and Mobile projects render too, with the visual differences of that renderer. On Vulkan it keeps the project's method (Forward+ or Mobile; a Compatibility project needs opengl) and pins Mesa's CPU driver, lavapipe, because Xvfb cannot present a GPU's Vulkan device. Forward+ effects (SSAO, SSIL, SDFGI, volumetric fog) render there, several times slower than OpenGL:

godot-render --path /workspace \
  --write-movie /tmp/clip.avi --fixed-fps 60 --quit-after 300 res://main.tscn

--write-movie (Godot's Movie Maker) advances the game one fixed step per recorded frame however slowly it draws, so slow rendering makes a capture take longer but never changes what it shows. It records at the project's viewport size (Display > Window > Size) and ignores --resolution; scale the result with ffmpeg -vf scale=-2:540 if you want it smaller.

About the GPU. RENDER_DEVICE=/dev/dri passes the host GPU into the container; unset, a harmless /dev/null is passed instead. Under Xvfb, though, Mesa usually still draws on the CPU (llvmpipe), because Xvfb offers no direct path to the GPU. The doctor prints which device drew. llvmpipe produces the same images, only more slowly, which is fine for captures but useless for measuring frame rates. If your host's render nodes are not world-accessible (ls -l /dev/dri), the container user also needs the host's render group.

Scripting the container

Everything the npm scripts do can be driven from your own scripts or CI, from the host, in this repo's directory:

What Command
Start the container npm run up
Run a command inside npx devcontainer exec --workspace-folder . <command>
One-shot agent prompt npm run claude:prompt -- "<prompt>" <extra claude flags>
Headless Godot ... exec ... godot --headless --path /workspace -s res://script.gd
Render or capture (RENDER_OPENGL=on) ... exec ... godot-render --path /workspace ...

Your project is at /workspace inside the container, a bind mount of GODOT_PROJECT_PATH, so files written on either side are visible on the other immediately. Exit codes pass through devcontainer exec, so a script can branch on them. Tools on the PATH: godot, godot-render (with rendering on), ffmpeg, ImageMagick, python3, gltf-transform.

Using OpenCode

OpenCode is included as an alternative AI coding agent with support for 75+ model providers (OpenAI, Anthropic, Google, local models via Ollama, and more).

Quick start

npm run opencode

On first launch, use the /connect command inside OpenCode to add your API credentials (e.g., OpenAI, Anthropic). Credentials are stored in a persisted volume.

MCP and skills compatibility

Both Claude Code and OpenCode share the same Godot MCP servers. Claude Code's MCP registration runs at container start; OpenCode's is generated lazily by npm run opencode so it doesn't hold the godot-mcp connection when idle. Your skills and instructions (CLAUDE.md, AGENTS.md, skills/) are also shared:

Feature Claude Code OpenCode
MCP servers Configured via claude mcp add Configured via opencode.json
Global rules CLAUDE.md AGENTS.md (falls back to CLAUDE.md)
Skills ~/.claude/skills/ Reads from ~/.claude/skills/
Permissions --dangerously-skip-permissions "permission": { "*": "allow" } in config

Model configuration

To change the default model, create or edit opencode.json in your Godot project root:

{
  "model": "openai/gpt-4o",
  "small_model": "openai/gpt-4o-mini"
}

See the OpenCode documentation for the full list of supported providers and models.

Context Compression (Headroom)

Headroom is an optional local compression proxy. When enabled it wraps both npm run claude* and npm run opencode*, shrinking tool output, logs, and other context before it reaches the model — compression happens locally, on the container, nothing extra is sent off-machine. Its anonymous usage beacon is disabled (HEADROOM_BEACON=off).

It is off by default, because it adds noticeable disk space to the image (uv, a Python interpreter, an ONNX model) and runs a proxy in the background. To enable it, set this in .env and rebuild:

HEADROOM=on
npm run build && npm run up

The same switch controls both the install (build time) and its use (run time), so changing it always needs a rebuild. Setting HEADROOM=off on an image built with it on skips the proxy and the wrap; setting HEADROOM=on on an image built without it prints a warning and runs the tools plain.

For one prompt, HEADROOM in the calling environment wins over .env: HEADROOM=off npm run claude:prompt -- "<prompt>" runs it plain on an image built with Headroom, for example when a script needs tool output exactly as the tools printed it (compression can drop words and symbols).

Upgrading from an earlier version: Headroom used to be always on. Add HEADROOM=on to .env before your next rebuild to keep it.

When enabled, a shared proxy starts once per container start (poststart.sh) so Claude Code and OpenCode can both be used concurrently. Run headroom doctor inside the container to check its status and savings.

To bypass an enabled Headroom for a one-off debugging session, run the underlying tool directly instead of through the npm script, e.g.:

devcontainer exec --workspace-folder . claude --dangerously-skip-permissions

Running Without the Devcontainer

You don't need the devcontainer to use the Godot MCP servers — both godot-mcp and minimal-godot-mcp are plain npm packages, and running your agent natively on the same machine as Godot is actually simpler than the container setup: Godot binds to 127.0.0.1, and a native agent process is already on 127.0.0.1 with it, so none of the bridging machinery (socat, host.docker.internal) is needed.

What you give up: the container's sandboxing/isolation, and the auto-installed asset-generation tools (ImageMagick, Pillow, trimesh, gltf-transform, obj2gltf, fbx2gltf) — install those yourself if you want them.

Setup

  1. Enable Godot's LSP server (and optionally the Debug Adapter) as described in step 5 above ("Set up Godot for MCP integration") — identical whether or not you use the container.

  2. Install the MCP server packages globally. Pin them: godot-mcp ships the Godot addon, and an addon that does not match the server silently drops whole tools.

    npm install -g @satelliteoflove/godot-mcp@4.1.0 @ryanmazzolini/minimal-godot-mcp@0.1.6

    Then install the addon into your project and enable it in Project > Project Settings > Plugins:

    godot-mcp --install-addon /absolute/path/to/your/godot-project

    Re-run that command after any version bump — it is idempotent.

  3. (Optional) For Blender, install the pinned server and the vendored addon:

    pip install blender-mcp==1.6.5

    Then install .devcontainer/blender/addon.py in Blender as described in "Set up Blender" above.

  4. Register the MCP servers with your agent, pointing at 127.0.0.1 instead of host.docker.internal:

    Claude Code:

    claude mcp add godot-mcp -s user \
      -e GODOT_HOST=127.0.0.1 \
      -e GODOT_PORT=6550 \
      -- npx -y @satelliteoflove/godot-mcp
    
    claude mcp add minimal-godot-mcp -s user \
      -e GODOT_LSP_PORT=6005 \
      -e GODOT_DAP_PORT=6006 \
      -e GODOT_WORKSPACE_PATH=/absolute/path/to/your/godot-project \
      -- npx -y @ryanmazzolini/minimal-godot-mcp
    
    # Optional — Blender. 127.0.0.1 is the default, so the env vars can be omitted.
    claude mcp add blender-mcp -s user -- blender-mcp

    OpenCode: add the equivalent block to opencode.json in your Godot project root:

    {
      "mcp": {
        "godot-mcp": {
          "type": "local",
          "command": ["npx", "-y", "@satelliteoflove/godot-mcp"],
          "environment": {
            "GODOT_HOST": "127.0.0.1",
            "GODOT_PORT": "6550"
          }
        },
        "minimal-godot-mcp": {
          "type": "local",
          "command": ["npx", "-y", "@ryanmazzolini/minimal-godot-mcp"],
          "environment": {
            "GODOT_LSP_PORT": "6005",
            "GODOT_DAP_PORT": "6006",
            "GODOT_WORKSPACE_PATH": "/absolute/path/to/your/godot-project"
          }
        },
        "blender-mcp": {
          "type": "local",
          "command": ["blender-mcp"]
        }
      }
    }
  5. Start Godot (and Blender, if used), then launch your agent as usual (claude / opencode) from your project directory.

Notes

  • No port bridge is needed on any platform — 127.0.0.1 reaches Godot directly.
  • Only one MCP client can hold the godot-mcp WebSocket connection at a time — the same "Another MCP server connected and replaced this one" behavior applies if you run Claude Code and OpenCode against Godot simultaneously (see Troubleshooting below).
  • .devcontainer/poststart.sh patches minimal-godot-mcp's diagnostics-manager.js so Godot's workspaceChange LSP notification doesn't overwrite GODOT_WORKSPACE_PATH with an unreachable container path. Running natively this mismatch shouldn't occur, since your workspace path already matches what Godot reports — but if diagnostics start resolving to the wrong file paths, that patch is the place to look.
  • Skills and CLAUDE.md/AGENTS.md are read directly from wherever your agent normally looks (e.g. ~/.claude) — no CLAUDE_USER_CONFIG_DIR symlink step needed.

Troubleshooting

MCP servers can't connect to Godot

Run npm run bridge:doctor first — it checks every link in the chain (Godot listener, host-side bridge, container-side relay) and prints which one is broken.

  • Ensure Godot is running on the host before launching Claude Code
  • Linux: Verify the port bridge is running (npm run bridge:start) — this starts automatically with npm run up but may need restarting if Godot was restarted
  • macOS / Windows: No bridge needed, but ensure Docker Desktop is running
  • Verify the godot-mcp addon is enabled in Project Settings > Plugins
  • Check that the LSP server is enabled in Editor Settings > Network > Language Server

"Another MCP server connected and replaced this one"

The godot-mcp addon allows only one WebSocket client at a time — when a second client connects (e.g. OpenCode), it disconnects the first (e.g. Claude Code). Run only one AI tool against Godot at a time. OpenCode's MCP servers are now registered lazily by npm run opencode (not at container startup), so OpenCode does not squat the connection slot when idle. To switch tools mid-session, exit one before launching the other.

Container can't resolve host.docker.internal

  • macOS / Windows: Docker Desktop provides this automatically. Ensure Docker Desktop is up to date.
  • Linux (Docker Engine): Requires Docker 20.10+. The --add-host=host.docker.internal:host-gateway flag is set in devcontainer.json. Verify with:
devcontainer exec --workspace-folder . ping -c 1 host.docker.internal

Login doesn't persist

Credentials are stored in the Docker volume godot-agents-config-<id>. If you destroy the volume (e.g., docker volume prune), you'll need to log in again.

If you get logged out repeatedly — typically on the first session of the day — and you also run Claude Code elsewhere under the same Anthropic account, that's the rotating refresh token being invalidated across clients, not lost credentials. Set a long-lived CLAUDE_CODE_OAUTH_TOKEN as described in Staying logged in across days.

npm run up suddenly acts like a brand new container

If you have both Docker Desktop and a native Docker Engine installed on the same Linux machine, you have more than one docker context, and only one of them is "active" at a time for the plain docker/devcontainer CLI commands this project's npm scripts use. Docker Desktop resets the active context to desktop-linux every time it starts, even if you'd left it on something else — so simply opening the Docker Desktop app can silently redirect all subsequent npm run commands to a different, empty Docker engine. Symptoms: npm run down reports "No container found" even though one is clearly running elsewhere, and the next npm run up creates what looks like a totally fresh environment (re-login, theme picker, no history) — because it is one, just on a different engine, coincidentally reusing the same volume names (they're computed from your project path alone) in a different, empty storage backend.

Check which context is active with docker context ls, and check where your actual container lives with:

docker context ls | awk '{print $1}' | tail -n +3 | xargs -I{} sh -c 'echo "=== {} ==="; docker --context {} ps -a --filter label=devcontainer.local_folder=$(pwd)'

If it's on a different context than the active one, either switch your active context to match (docker context use <name>) or, to avoid this happening again after the next Docker Desktop launch, pin this project to the right context regardless of the global default by setting DOCKER_CONTEXT=<name> in .env — every npm script here already sources .env before running docker/devcontainer, so this takes effect automatically. Leave it unset if you've never seen this problem; it doesn't affect anyone with only one Docker context.

Godot LSP connection refused

Godot binds to 127.0.0.1 only.

  • macOS / Windows: Docker Desktop can reach host localhost ports natively. Ensure Docker Desktop is running and up to date.
  • Linux: The host-side bridge relays from the Docker bridge IP to localhost. The bridge starts automatically with npm run up. If it still fails:
    • Verify socat is installed (sudo apt-get install socat)
    • Manually restart the bridge: npm run bridge:stop && npm run bridge:start

Windows / WSL2 notes

If running Godot natively on Windows with Docker Desktop using the WSL2 backend, host.docker.internal resolves to the Windows host. This should work without additional configuration, but networking through the WSL2 VM can occasionally cause connectivity issues. If MCP tools fail to connect, verify that Godot's ports (6550, 6005) are not blocked by the Windows firewall.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages