game-editor
Work on the Godot 4 project that is open in your editor from Hermes: look at it, change it with a plan you read first and approve once (twice when the plan deletes, overwrites or runs code it wrote), play it, get each error with its file and line, fix it in the next plan, and export it. Sprite sheets, tile sets, sound effects and music loops that GameForge wrote (or any files in the same formats) go straight into the open scene. It talks to the editor through Godot AI (hi-godot/godot-ai) 4.3.0, an MCP server and editor add-on, through a bridge in this package.
Next to GameForge
GameForge makes assets, scaffolds a new project and plays a build headless. This plugin works where that ends, in the editor you already have open:
- The real project in the open editor. A new Hermes session can add to a scene whose file already matches the editor, save it and play it in one approval.
plan_changedoes not compare while it builds the plan. Applyscene_doopcomparefirst (its own plan, or an earlier step of the save plan). A real difference (unsaved edits from before, a teammate's change on disk, also one made before the session) is named, value by value. A plain save is not planned until both sides are known;overwrite: truekeeps the editor's version and needs the second approval. Quit, revert and a reload return a plan that says unsaved work would be discarded.apply_planlists every editor-only node, every node this session added and every unsaved value before anything is discarded, or it does not run. - Guards, and approval that says what is lost.
plan_changewrites nothing and returns the whole plan as text: each value now and after, what Godot AI does inside each step (taken from its source), and every line that deletes, overwrites, discards unsaved work or runs code. Hermes asks once forapply_plan; a plan that deletes, overwrites, discards, writes outside the project or runs code it wrote asks a second time throughconfirm_risky_plan, afterapply_planlists what is lost. Each step is read back before the reply says it ran. Reads never run a@toolscript, decided the way Godot decides it. - GameForge assets into the open scene, then play and fix. One
place_assetsstep each: a sprite sheet becomes an AnimatedSprite2D (pixel art drawn Nearest), a tile set and a level map a TileMapLayer laid with GameForge's own dual grid, a sound effect and a music loop AudioStreamPlayers on buses.play_sessionplays the game and returns each error with itsres://file, line and code line;file_lookopgameforge_reportturns GameForge's own playtest report into the same fix steps.
Install
hermes plugins install TakeshiTGAL/hermes-plugin-game-editor
hermes plugins enable game-editor
For a local copy, give the folder as a file:// URL: hermes plugins install file:///path/to/hermes-plugin-game-editor. A file:// URL of a git repository clones that repository's HEAD commit. pyproject.toml declares mcp>=2.0.0,<3; enabling the plugin installs it. Open a new session so the tools load. A headless Godot editor disables the Godot AI add-on unless GODOT_AI_ALLOW_HEADLESS=1 is set before the editor opens.
Requirements
- macOS (this version declares only macOS; see "Other operating systems").
- Godot 4.7 or later (
Godot.apporGodot_mono.appin/Applicationsor~/Applications, or the Steam install). - The Godot AI add-on 4.x in the project at
addons/godot_ai/, enabled in Project → Project Settings → Plugins, with every tool domain on in the dock's Tools setting. https://github.com/hi-godot/godot-ai/releases uv(foruvx) onPATH. The server starts asuvx … --from godot-ai==4.3.0 python -I ${PLUGIN_ROOT}/bridge/game_editor_bridge.py.- The Godot AI ports free on 127.0.0.1, read as the add-on reads them:
godot_ai/v4_endpoint_ports, thengodot_ai/http_portandgodot_ai/ws_port, 8000 and 9500 by default.PORT_OCCUPIEDnames the port; while it stays taken, later calls answer at once instead of waiting about 30 seconds each. - For
checkswith GUT or gdUnit4: that framework in the project (addons/gut/oraddons/gdUnit4/), imported once withgodot --headless --import.
Tools
Eight tools are published. Four only read files on disk (readOnlyHint: true): scene_look (the .tscn text: scene tree, node search, children, groups, signals, stored properties), script_look (source, outline), file_look (text, file search, resource search, a GameForge playtest report), project_look (a setting, autoloads, the input map). change_guide (readOnlyHint: true) answers from this package's tables. plan_change (readOnlyHint: true) builds a plan. apply_plan (readOnlyHint: false) runs it, and confirm_risky_plan (readOnlyHint: false) runs a plan that needs the second approval; with trust: untrusted, Hermes v0.22.2 asks before each call. editor_look (state, open scenes, selection, monitors, sessions, logs, class info) and asset_look (materials, animations, cameras, particles, tiles, grid maps, navigation paths) ask the editor, so they are plan steps, not direct calls.
A plan step names one of these (they are not in tools/list): editor_do, scene_do, script_do, file_do, project_do, asset_do, game_do, checks, batch, ship_build, and place_assets. change_guide with tool and op gives the arguments, what the op does and an example step. Every Godot AI 4.3.0 operation is reached except the five in "What was left out".
Plans and approval
plan_changesends Godot AI only the session list. It does not read editor state, the open scenes, the scene tree or node values: those can run a@toolscript's_get. Values in the plan text come from the scene file and from values this session already set. The reply is every step's operation, target and values (now and after), what Godot AI's handler also does in that step (side_effectsinbridge/effects.json, extracted from the upstream source), a script's.uidwhencreate_script,edit_scriptor a scriptwrite_filewould write one, the.uidand.importbeside both sides of afile_domove, and the lines under "This plan deletes, replaces, discards or runs something": deleted nodes and files, cleared tiles, removed actions, replaced files, each disk value a save withoverwritedrops, any write onto a file the plan named that already exists (a theme, a material, an environment, another resource, or a move's destination) with both contents, unsaved work that a quit, revert or reload would discard (the plan says so;apply_planlists every editor-only node, every node this session added and every unsaved value before anything is discarded, or it does not run), code written in the plan, and project code that runs (playing, tests, export, opening a scene with@toolscripts, a@toolscript on a node the step touches or that its handler reads, andscene_doopcompare). Abatchstep gets a line for each such command. A plan over 256 KiB is refused whole.- A save of the scene that is open now is planned only after
scene_doopcomparehas been applied since the file last changed, or that compare is an earlier step of the same plan. When the last compare differs and the save has nooverwrite, no plan is made and nobody is asked, except that a node this session added does not by itself block that save. The compare reply still names each difference (a node only on one side, including one added in this session, a value on disk and in the editor, including a script that is in the editor and not in the file). A script the node's base scene already has is not a difference. An approved compare names the script of a node that is only in the editor when that node has a script. A node with no script is not a second line. The reply does not say the editor and the file agree while such a node exists. Plan again withoverwrite: trueto keep the editor's version: the plan names both sides. Quit, revert andopen_scenewithforce_reload: truereturn a plan that says unsaved work would be discarded. The list is not in that plan:plan_changedoes not read the editor.apply_plancompares first and lists every editor-only node, every node this session added and every unsaved value, or it does not run. A stored empty compare is not that list. When the open scenes or a value cannot be named, nothing is sent. - Steps of one plan see the earlier steps: a scene made or opened earlier is the scene later steps are checked against (cycles, prefab rules, saves), nodes, scripts and files it makes count, and a main scene it sets is the one
play_sessionreads. apply_planruns only the newest plan, once, within 10 minutes, and only if nothing it was made against changed: the open scene, the scene file's nodes (path, type and child count; the editor's live tree is not read while the plan is made), the files it names, the files its steps change as a side effect (project.godot, an asset's.import, a script's.uid), an export's neighbours, and what a save withoverwritewould drop. When the plan saves and no fresh compare is stored,apply_plancompares before the first step. Steps run in order and stop at the first error. After each step the bridge reads the result back (game-editor checked:); when Godot did not keep what the step asked, the plan stops with an error.- The second approval. A plan that deletes, overwrites (a save with
overwritethat drops something, a replaced file), discards unsaved work, writes outside the project, or runs code written in it (run_game_code, a new or edited@toolscript) does not run atapply_plan.apply_planchecks the editor and returns what is lost, first and whole, in at most 1,700 characters (control and bidirectional characters escaped), and nothing changes. The user reads it;confirm_risky_planwith the sameplan_idthen runs the plan, and Hermes asks for it too. If the editor or the files changed in between, it does not run. Playing and tests run the project's own code and ask once. This works the same in the terminal CLI and in a gateway: both are Hermes's own approval prompt, which names only the tool. MCP elicitation is not used; Hermes v0.22.2's terminal CLI fails it closed. - Declining sends nothing. Cron,
-qand other unattended runs refuseapply_planandconfirm_risky_plan; reads andplan_changestill run. - Long steps (apply_plan steps, play, tests, export) send
notifications/progresswhen the call carries aprogressToken.
Reads and @tool scripts
A read never runs a @tool script. Which Godot AI handlers read a node's name or values comes from the upstream source (node_access in bridge/effects.json). A value read (properties, a held resource, a camera, particles, an animation check) calls the script's _get, _get_property_list, _validate_property, _property_can_revert, _property_get_revert, _to_string and property getters; a name read calls _get. A read is refused when it would call one of those; the refusal names the script, and the same read can be a plan_change step, which says that script runs. A tree read (scene tree, node search, children) is refused when any node in the scene has such a _get. plan_change never reads editor state, the open scenes or the scene tree. The fingerprint uses the scene file's nodes, so a node that exists only as an unsaved editor edit is not in it. When the open scene file cannot be parsed (no file, a byte-order mark, an inherited scene, or a parse failure) and the plan would change that scene, no plan is made and no note is added to let it through.
Whether a script is @tool is decided the way Godot 4.7.2 decides it (measured with a marker file per variant): @tool among the annotations at the top of the file, also after a UTF-8 BOM, a doc string or a comment, with CRLF line ends, and on one line with @icon(...) or @static_unload. A @tool anywhere else is a parse error in Godot and is still counted. A script that extends a @tool script without @tool of its own does not run in the editor; a @tool script does run the functions it inherits, so the hooks are collected along the extends chain (a path, uid://, or a class_name found in the project's .gd files). An inherited scene's root and an instanced scene's root have the base scene's script. C#: a [Tool] attribute. An embedded script is read from the scene. A script or a base that cannot be read counts as @tool with every hook. The comparison before a save and the plan steps use the same rule.
GameForge assets
place_assets reads the files on disk and becomes Godot AI steps that the plan lists:
sprite_sheet—sheet(.png),frames(a SpriteFrames.tres) oratlas(.json). GameForge writes<name>.png,<name>.json(TexturePacker JSON-hash with AsepriteframeTags,meta.apphermes-gameforge) and<name>_frames.tres; the.tresis used, or one is written from the.json. An AnimatedSprite2D gets the frames,animation(default the first) andautoplay.tileset—tileset(a TileSet.tres, as GameForge writes with terrains and collision) orsheetwith GameForge's layout.json. Withmap(rows of text as in GameForge'slevel.json:~is the lower terrain, any other character the upper one, or a 0/1 mask; up to 64×64) the TileMapLayer gets GameForge's dual grid, by the formula of its Godot template (engines/templates/godot_topdown/main.gd): the tile on each cell corner blends the four map cells around it (indextl*8 + tr*4 + bl*2 + bron the Wang sheet), open ground gets fill variants where(x*73856093 ^ y*19349663) % 3 == 0, and the layer is offset by half a tile. Withoutmap,fill{x, y, w, h}fills one tile (tile{col, row}).sfx— a.wav,.oggor.mp3(the first bytes are checked) on an AudioStreamPlayer, busSFXby default.music— the same withloop(a WAV that does not loop gets loop mode Forward in its.import, then a reimport; one that already loops keeps its mode and itsloop_beginandloop_end; OGG/MP3 getloop),autoplayand busMusic.buses— finishes the bus of players an earlier plan placed (below).
GameForge's pixel art (32 px or smaller, from an atlas with meta.app hermes-gameforge or a tile set with GameForge's layout) gets texture_filter Nearest on its node, because a project's default is Linear, which blurs it; a project that already defaults to Nearest (GameForge's own projects do) gets no extra step. A .tres that points at a res:// path this project does not have — GameForge writes the paths of its own project folder, so a .tres copied elsewhere points nowhere — is rewritten to the file of the same name beside it; the plan says why, and only those [ext_resource] lines change. A file that is missing, of another format, or names a frame outside its sheet is refused when the plan is made. No file is downloaded and GameForge is not called.
A bus missing from the project's bus layout file is added to it. Godot keeps a player on Master until the editor has loaded that bus, which happens when the project is opened again, so the reply says the player is waiting and this plugin remembers it (in pending_buses.json in its data folder). A later step of the same plan that fails does not drop a player already waiting. After the project is reopened, editor_look lists what is waiting and one plan with place_assets op buses sets each player's bus and saves, then reads the bus back and forgets it.
file_look op gameforge_report reads GameForge's playtest report.json (the newest under res://playtests/ when path is left out; GameForge writes it there from forge_playtest). Each finding comes back with the res:// file, the line and that line's code for a script error, the tile cell for a "Player got stuck", and fix_plan.steps: a script_do op script step per line (write new_text) and a play_session step to check the fix. GameForge finds it; this plugin fixes it in the open project and plays it again.
Play, fix, test, export
game_doopplay_session:goal(score >= 3or a node path),steps(action,keyorwait, 12 seconds in all),watch(runtime paths). Where the watched nodes start is read first, so the first move is judged too. A score goal adds the nodes that hold score (a script withvar score, a Label showingscore:) towatchwhen the plan is made.oktrue means the game was playing, the frame counter advanced, positions were watched, and the goal was judged and met. A script error, a parse error, a line that saysERROR:, a line that names a script and a line (level.gd:7 @), a line that sayson a null instance, or a line that saysnode not foundis a failure. When that line names a file and a line, the error finding carriesfile,line,code(the text of that line) andnext. A game that never stays up is still read from the editor log. Each error entry keeps the script path and line beside the message (Godot's Output prints that pair asat: GDScript::reload (res://scripts/file.gd:3)), and the finding carries that file, that line and the text of the line. Other logged lines (a warning that names no script line) staylogand do not by themselves makeokfalse. idle and frame_drop can still be listed. A missed goal staysokfalse even when frames advanced. Every finding carriesevidencefrom the build it played: the step it follows, the game's frame counter at that point, the watched nodes' positions and texts, the error lines from the game and editor logs, and a 240-pixel picture of the game after that step attached to the reply (up to three). An error finding that names a file and a line also carriesfile,line,codeandnext.replayis the exactplay_sessionstep to run the same input script again. A stall that follows a script error says so: Godot's debugger pauses a game run from the editor on an error. The report is a normal reply, not an error, because Hermes cuts an error reply to 2,048 characters; a plan still stops after a play that was not ok.not_livemeans the game process did not stay up with frames advancing. On a headless editor, the play step writesres://ge_headless_arm.gdandres://ge_headless_arm.tscn, opens that scene so its@toolscript can add--headlesstoeditor/run/main_run_argsin memory, closes the scene, then removes the files.project.godotis not left changed. A windowed editor is not given--headless. The plan names these files before anyone is asked.oktrue still requires the goal to be met; a missed goal staysokfalse. A goal with neither a score comparison nor a node path isgoal_not_checked.checksopgut,gdunit4orgodot_ai. A pass needs"passed": true(at least one test ran and none failed). A plan is refused when the framework, the test folder or the imported class names are missing.ship_buildopexport_game: a local macOS, Web, Android or iOS build when that Godot version's export templates are installed. The plan names every file beside the output that exists; those needoverwrite, and a source file beside it is refused. Missing templates are reported with file, source and size, and nothing is downloaded or uploaded.
What the bridge checks before Godot AI acts
- Prefab children: a change Godot would not keep on an instance's own child (a property without Editable Children or one that is not stored per ClassDB, a delete, a rename, a sibling move, a move to another parent, a group change without Editable Children, a connection from a non-editable child, disconnecting a connection the prefab defines) is refused with the prefab scene to open. Duplicating a node that is or holds an instance is refused (Godot AI saves its children twice). Instancing a scene into itself is refused.
- Overwrites: creating a script, a scene, a
save_astarget or a written file over an existing one needsoverwrite: true, and the plan says the file is replaced. - Saves: both sides come from an applied
scene_doopcompare(or a compare step earlier in the same plan), including values, groups, connections, subresources, tiles and other stored values the editor does not list. A script that is on the node in the editor and absent from the file is a difference, not an agreement. A node whose@toolscript would run on the read is not read during an unapproved compare; an approved compare reads those values and can run the script, and the plan says so. A plain save is refused while a difference remains.overwrite: trueis planned only when both sides are already named. Over 5,000 nodes it refuses and namessave_as. - Scripts:
script_doopscriptpatches the file on disk; an unsaved or stale script-editor tab can overwrite it later, and the reply after a plan names a script that changed that way. - Node paths and names: a node path with
.,..,%Nameor a doubled slash is refused (Godot resolves them, so a check on the plain spelling could be skipped). Adding a node whose name is already taken under that parent is refused (Godot would rename it and the next steps would change the existing node). A node that is not in the scene file and was not added this session is not read. A plan step cannot carrysession_id.
Files the plugin refuses
String values in the arguments that start with res://, uid:// or user:// (after leading spaces, at any depth in lists and objects) are checked before the call; script and file text (content, code, text, old_text, new_text) is not. uid:// is looked up in the project's .uid, .import and scene headers. Refused:
- a write to
user://, the game's player data folder outside the project. Godot AI 4.3.0 itself reads onlyres://(Path must start with res://on auser://read). A key-likeuser://name, a hidden name, and..are refused before Godot AI is asked; - a path with a folder or file name that starts with a dot (
.godot/, whereexport_credentials.cfgkeeps signing passwords;.env;.git/); - names that commonly hold keys or passwords:
export_credentials.cfg,google-services.json,GoogleService-Info.plist,key.properties,gradle.properties,*.keystore,*.jks,*.p12,*.pfx,*.pem,*.key,*.p8,*.mobileprovision,*.env,id_rsa*, and files that are not Godot assets, or folders, whose names contain credential, secret, password, api_key, access_token, auth_token or private_key (game data such asres://data/tokens.jsonis allowed). Trailing dots and spaces and letter case are ignored, as Windows and macOS do; .., and paths that after following symbolic links point outside the project or at a file above. The bridge asks Godot AI for the open project's folder (session_managelist, read-only).
This is a file-name rule, not a content check. Every other project file can be read without approval and goes to your model provider, and file_look op files can list the names of refused files. Values stored in scenes, node properties, project.godot or logs are returned as they are, and game_do op run_game_code runs any GDScript in the game, which can read any file the game can.
Disclosure
- Before the tools run: Godot 4.7+ (see Requirements), the Godot AI 4.x add-on enabled in your project, and
uvonPATH. Enabling the plugin installsmcp>=2.0.0,<3into the Hermes environment. When the Godot AI add-on starts in the editor, it adds a_mcp_game_helperautoload to yourproject.godot(the add-on's own behavior). - When it starts: the tool list comes from this package; Godot AI starts on the first tool call, so a session that never calls a tool does not start it or use the attach ports. Those ports are read from the editor settings as the add-on reads them (see Requirements).
- Where data goes: the first start downloads
godot-ai==4.3.0and the packages named ingodot-ai.lockfrom PyPI (pypi.org,files.pythonhosted.org) withuvx. Later starts take the packages from the uv cache. uvx can still askpypi.orgwhether an index page changed. The lock chooses the versions (see "Pinning every dependency"). Without that lock, uvx would resolve dependencies other thangodot-aiagain insidegodot-ai's version ranges. The bridge and Godot AI talk to the editor only on 127.0.0.1 (HTTP on port 8000, WebSocket on port 9500, both with Godot AI's own rotating keys). There is no API key and no account. - Usage telemetry: Godot AI sends usage telemetry by default to
godot-ai-telemetry-v2-pudmurzsnq-uw.a.run.app: an installation ID, the tool name and operation of each call, a salted hash of the editor session ID, call counts and a latency histogram, errors, the operating system and its version, the Python version and the Godot AI version (upstreamtelemetry.pyat 4.3.0; upstream says no code, scene contents or file names). For a Godot AI server this plugin starts it is off (GODOT_AI_DISABLE_TELEMETRY=trueand--disable-telemetry), and so is FastMCP's update check (FASTMCP_CHECK_FOR_UPDATES=off). The add-on in the editor, and a server the editor starts (the usual order: the editor starts it when the project opens), follow the editor settinggodot_ai/telemetry_enabled, which is on by default (upstreamtelemetry.gdandclient_configurator.gd); turn it off in the Godot AI dock. - Update checks: the Godot AI add-on asks
api.github.comfor a newer release each time the editor opens a project with it enabled. This plugin cannot turn that off. - What the model sees: the scene tree, node values, script source, the contents of project files the rules above do not refuse, logs, file paths, screenshots and the
plan_changetext are tool results, so they go to your model provider. Hermes keeps images from tool results, including screenshots, in<HERMES_HOME>/cache/images. - What runs on this Mac:
uvxwith Python 3.11 (godot-ai4.3.0 accepts Python 3.11 to 3.14; this plugin starts 3.11 fromgodot-ai.lock), the bridge,godot-ai attach, and a Godot AI server on port 8000 (started by the editor or byattach).project_dooprun_game,game_doopplay_session,checksandgame_dooprun_game_coderun your project's own code;checkswith GUT or gdUnit4 starts a second, headless Godot on the project.ship_buildstarts a headless Godot to export, and whenserveis true it also starts Python'shttp.serveron127.0.0.1for that build folder.scene_doopopen_sceneruns the opened scene's@toolscripts, and saving a scene makes Godot read every property, which calls a@toolscript's_get; plans say so. Reads never run a@toolscript (see "Reads and @tool scripts").plan_changesends Godot AI only the session list. It does not read editor state, the open-scene list, the scene tree or node values. A save is planned only afterscene_doopcomparehas run, or that compare is an earlier step of the same plan. When the plan saves and no fresh compare is stored,apply_plancompares before the first step and reads node values whose scripts are not@tool. On a headless editor,play_sessionandrun_gamewriteres://ge_headless_arm.gdandres://ge_headless_arm.tscn, open that scene, close it, and remove the files;project.godotis written back if that step changed it. A windowed editor is not given--headless.resources/readofgodot://node/.../propertiesis refused before anything is sent. - What it writes: inside your project only when you approve
apply_plan, except the headless play files named above, which the same approved play step creates and then removes. A new or edited.gdor.csscript also gets a.uidbeside it; the plan names that file.ship_buildwrites the build you named and, only when that platform has no preset yet,export_presets.cfg(the preset only; no passwords). If the export templates are missing it writes nothing and downloads nothing. The local page stops when the bridge exits or before the next export. Godot AI keeps its connection keys under~/Library/Application Support/godot-ai/. The uv cache holds the downloaded packages. The server's working directory is<HERMES_HOME>/plugin-data/agent-plugin-game-editor-<id>/work.place_assetswrites, inside the project, a SpriteFrames or TileSet.treswhen only GameForge's.jsonis there, the[ext_resource]lines of a.tresthat points at another project's path, the bus layout file (res://default_bus_layout.tresunless the project names another) when a bus is missing, and loop settings in a music file's.import. Outside the project the plugin keeps one file,pending_buses.jsonin its working directory, listing players whose bus waits for the project to be reopened (scene, node path, bus); it is deleted when nothing waits. Everything else the bridge remembers for its checks (scene fingerprints, nodes it added, plans) lives in memory for the session. Thetests/folder is for development and is not loaded by Hermes. - Who can call: with the plugin enabled, the tools are on in every Hermes surface, including gateways and cron. In a gateway chat (Telegram, Slack, Discord …) anyone who can message Hermes there can ask for a change and can answer both approvals; this plugin has no user list of its own. The four file reads return project contents without asking.
change_guideandplan_changealso run without asking. - Approval: Hermes asks for
apply_plan, and forconfirm_risky_planwhen a plan deletes, overwrites, discards unsaved work, writes outside the project or runs code written in it. The trust prompt shows the tool name and does not show the arguments, so the plan text andapply_plan's list are what the user reads.plan_changeshows the operation, the target, the values now and after, and what the plan deletes, discards or runs, and it does not ask. Only the newest plan runs, within 10 minutes. Declining sends nothing. The CLI prompt offers once, session and deny, and a gateway may show session or always; on this gate each of them approves only that one call, and the next call asks again. Cron,hermes chat -qand other unattended runs refuse both; the reading tools andplan_changestill run there.plan_changewrites nothing. - A second server without approval: pressing Configure for Hermes in the Godot AI dock, or running the
hermes mcp addcommand that the godot-studio plugin'sgodot_setupsuggests, adds agodot-aiserver to theconfig.yamlof your Hermes home with notrustsetting. Its 47 tools then run next to these with no approval and none of the checks above, includingclient_manage, which can rewrite AI client settings. Do not add it. This plugin does not detect that server and does not refuse a write because it is listed. Press Remove in the dock or runhermes mcp remove godot-ai. - How to stop:
hermes plugins disable game-editor. To turn one surface off, usehermes tools. - What remains: after Hermes exits, the Godot AI server keeps listening on the editor's two ports (8000 and 9500 by default) while the editor is open; it was started by the editor or by
attach, and it stops on its own after the editor is gone (41 seconds measured after the editor was killed; upstreamorphan_reaper.py). Afterhermes plugins remove game-editor, the plugin-data folder (<HERMES_HOME>/plugin-data/agent-plugin-game-editor-<id>), the copy Hermes made in its package workspace (<HERMES_HOME>/installs/…/workspace/plugin-sources/game-editor-<id>), the uv cache,<HERMES_HOME>/cache/imagesand~/Library/Application Support/godot-ai/remain. - On a Mac without Godot:
plugin.jsonrequires the Godot app (requires.app, minimum 4.7) at the paths in Requirements.
Pinning every dependency
uvx installs godot-ai==4.3.0 but resolves its dependencies within their ranges at each start. To pin all of them with hashes:
echo "godot-ai==4.3.0" > in.txt
uv pip compile --python 3.11 --generate-hashes in.txt -o godot-ai.lock
mcp.json passes "--python", "3.11" and "--with-requirements", "${PLUGIN_ROOT}/godot-ai.lock" before "--from". On 2026-10-20 the same uvx arguments installed 74 packages; python -I -c printed Python 3.11.15 and imported godot_ai.
Numbers
- Startup: the tool list is immediate; the first tool call waits for uvx and Godot AI (about 1 second when the editor is open and the packages are cached; the first start, which downloads packages, takes longer).
- Before a plan that saves,
apply_planreads the editor's tree in pages of 500 and the saved values of each node; a 4-step plan that saved the game scene (17 nodes in the editor) took 0.77 seconds in all. - Time: a call times out after 120 seconds;
run_testsafter 290 seconds, because Hermes stops waiting for any MCP call at 300. - Size: replies over 256 KB are cut by the bridge and start with a
truncatedmarker. Larger replies, about 1 MB and up, fail inside Godot AI withTransport failed after dispatch … may have completedorOutbound WebSocket buffer is full.run_testswith GUT or gdUnit4 returns the last 60 KB of output. - Errors: three error replies in a row pause every tool of this server for about 60 seconds (Hermes's circuit breaker), and a refusal by the bridge counts as one.
- Tool Search: Hermes puts the 8 published tools' names and first sentences in its deferred catalog; when many plugins are installed it shortens the largest servers' listings first.
What was left out
Of Godot AI's 161 operations and 19 single-call tools, five are not offered. The other 178 upstream calls are reached by the 16 operation tools. A caller reaches the 10 that change the editor only as steps of plan_change.
| Godot AI operation | Why |
|---|---|
client_manage configure |
Writes another AI client's MCP settings. For Hermes it adds a second godot-ai server with no approval, so the model could remove this plugin's approval. This plugin is already the connection. |
client_manage remove |
Removes AI client settings; same reason. |
client_manage status |
Reads the settings of about thirty other AI clients and runs some of their command-line tools (for example claude mcp list); not needed to use this plugin. |
editor_manage state |
The same handler as editor_state, which editor_look op state offers. |
custom_manage invoke |
The add-on tool body is not in the Godot AI source, so this plugin cannot tell whether the call runs code, writes, deletes or sends data. It is not offered. |
Writes to user:// are refused (see above), and Godot AI 4.3.0 does not read user://. Key-like names stay refused, so those operations use res:// and uid:// for anything secret.
Godot AI behavior found while testing
Recorded on 2026-10-20 with Godot 4.7.2 and godot-ai 4.3.0; the bridge guards against all but the last two:
- A property on a prefab child is kept only with Editable Children and only when it is stored. A delete of that child comes back, and moving it out of the instance duplicates it, even with Editable Children on. A node added directly under the instance root is kept without the flag. Two levels down, the inner scene's own Editable Children flag is enough when the outer instance is editable.
node_manageduplicate of a scene instance saves its children twice; the reopened scene hasShape, Body, Shape, Bodyand a doubled signal connection.script_create,scene_managecreate andfilesystem_managewrite_text overwrite an existing file without a warning.scene_saveoverwrites a change made on disk while the scene is open, without a warning.node_createwithscene_pathaccepts the open scene itself, which makes a scene that contains itself.- Godot AI's own test suites (
McpTestSuite) run inside the editor: a suite script must start with@tool, and it cannot call a game script that is not@tool. Game logic needs GUT or gdUnit4. - Replies of about 1 MB and more fail with a transport error that says the call may have run, also for reads.
- Godot AI's tree reads (
scene_get_hierarchy,node_find,node_manageget_children) read each node'sname, and a@toolscript's_getruns on that lookup (measured 2026-10-21: one_get nameper such node per read).node_get_properties,camera_manageget,particle_manageget andanimation_managevalidate run_get,_get_property_listand_validate_property. AudioStreamPlayer.busis saved only for a bus the editor's audio server has. The editor reads the bus layout file when the project opens, so a bus added to that file later is saved as Master until the project is opened again (measured 2026-10-21). Godot AI has no call that loads a bus layout or reopens the project.- With an inherited scene open whose root script is
@tooland defines_get, the editor calls that_getabout 145 times a second (forscene_file_path) whether or not anything is asked (measured 2026-10-21: 1,453 calls in 10 idle seconds; none after another scene was opened). Such code runs while the scene is open, with or without this plugin. autoload_manageadd of a@toolscript writesproject.godotand does not start the script in the open editor (measured 2026-10-21: no_readyin the editor after the call).- Godot 4.7.2 runs a script in the editor only when its own file has
@tool: a script thatextendsa@toolscript without@toolis not run (measured by path, byclass_nameand with an empty body), and@toolafterextendsis a parse error. - A music loop set through the WAV
.import(edit/loop_mode=2) andfilesystem_managereimport givesAudioStreamWAV.loop_mode1 (Forward); a minimal.importwritten before the first import is honoured too. tile_map_dataand AnimatedSprite2Dautoplayare stored in the scene but are not in the editor's property list.- A script created in the editor session through Godot AI, patched, played, patched back and played again was written back to the first patched text by the editor when a later plan saved the scene (2026-10-21, reproduced twice; the editor holds an older copy in an open script tab). When the file is one this plan wrote, the save keeps the plan's text and the step is not an error. A file the step did not name, including a script this plan did not write, is still put back, and the step is an error that names the file and the differing line first. A script that still differs after the plan, and that no step named, is reported and is not written back, because it could also be the user's own edit.
- A path in a step's also list is named in the plan with the text that stays on disk. If that file cannot be read, no plan is made. After the step, that text remains, including when an open script tab saves an older copy. A scene save names each .tres the scene or the plan already names, and Godot's rewrite of that resource stays. A play report uses log lines from this run only. revert of the scene whose tree can be read lists that scene's unsaved values and runs when another scene is open; the other scene is not opened or saved. quit still does not run while another scene cannot be named.
- A file an earlier step of the same plan already names (the .uid beside a script that step creates) stays when a later step writes it. A file no step names is still put back, and that step is an error that names the file. A failed step's reply starts with that step's number and conclusion, including
play_session ok=…with the reason and the file name when the step played, and the reply is at most 2,048 characters. Compare does not add an editor-only script line for a node with no script. - A
.uidor.importfile the editor writes beside a file already on disk stays when the bytes are oneuid://line, or an import file with a[remap]section. The reply names each kept path on one line. A script, a scene, a resource, or a.uidwhose bytes are not that shape is still put back, and that step is an error.
Other operating systems
Godot AI runs on macOS, Windows and Linux. This version declares only macOS because the bridge has been run only there. Its path checks handle Windows paths (drive letters, backslashes, trailing dots and spaces, letter case) and are tested with Python's ntpath, but no Windows or Linux editor was run.
What was tried
On 2026-10-21 on macOS with Godot 4.7.2 (headless editor, GODOT_AI_ALLOW_HEADLESS=1, ports 18861/18862), the Godot AI add-on 4.3.0, and Hermes v0.22.2 (the plugin loaded with load_and_go_live, its server started with _build_safe_env, a temporary Hermes home and HOME), plus Hermes's own terminal CLI in a pseudo-terminal with a scripted local model. Each @tool script in the test projects wrote a line to a marker file whenever Godot ran it.
@toolvariants: reads of nodes whose script has@toolafter a BOM,@icon(...) @toolon one line,@toolthat inherits_getby path or byclass_name, and an inherited scene's root were refused, and the marker files did not grow; a script that onlyextendsa@toolscript and a@toolscript with no hooks were read.- Overwrite: an unsaved edit in the editor (made through Godot AI directly, as a person at the editor would) made a plain save plan refuse before anyone was asked, naming
/Main/Player position: disk Vector2(40, 96), editor (70, 96). Withoverwritethe plan, the second approval and the reply named it; declining the second approval left the file unchanged. In a new Hermes session, a teammate's change on disk (Score 0 (teammate)) was named the same way; a change made after the plan stoppedapply_plan. - Second approval in the real terminal CLI: a plan that deletes a node showed Hermes's panel for
apply_plan, then forconfirm_risky_plan; after both the node was gone from the file. - A game from an empty project with files in GameForge's formats (made here from GameForge's source): a coin prefab, then the main scene with the player, a 30×16 level map laid as GameForge's dual grid (527 cells, Wang edges and fill variants), the hero sprite and the ground drawn Nearest, three coins, a sound and looping music; a play to
score >= 3; a broken script line found by play with its file and line; a GameForge-formatreport.jsonturned into the fix plan and played toscore >= 3; a.treswith GameForge's own path relinked. Six approvals and 36 seconds for all of it; the same Godot AI calls with Godot AI added straight to Hermes (trust: untrusted) asked 77 times and took 13 seconds, with no comparison, no read-back and no check that the game played. - Buses: the players stayed on Master and were remembered; after the editor was opened again,
editor_looklisted them and oneplace_assetsopbusesplan savedSFXandMusic. - A
@toolautoload added through a plan did not run in the open editor and did run after the project was opened again; music whose.importalready looped keptloop_begin=11025andloop_end=33075. - Cron and
-qdeclinedapply_planwithout asking; a program holding the Godot AI port gavePORT_OCCUPIEDafter about 31 seconds and at once on the next call; four scenes broken on purpose reportedfall,goal,stallanderrorwith evidence and replayed the same way. - After Hermes exited the Godot AI server still held the port; after the editor was killed it stopped on its own in 41 seconds.
tool_searchfound these tools for every query the skill lists (18 of 18).
What was not tried
- A live gateway chat and a session with a real model.
- Windows and Linux, the .NET build of Godot, add-on versions other than 4.3.0, Android and iOS export, and GameForge itself (its formats and its playtest report were read from its source and made here).
Other Godot plugins for Hermes
gameforge(an open hermes-agent pull request) makes sprites, tile sets, sounds, music and 3D models, scaffolds projects and playtests builds headless. Its files go into an open scene withplace_assets.godot-studio(an open hermes-agent pull request) runs headless Godot checks and exports, and itsgodot_setupsuggests registering Godot AI withhermes mcp add; with this plugin installed, do not add that second server.game-dev(in the Hermes plugin catalog since 2026-10-20) inspects a project folder read-only and audits assets. This plugin works inside the open editor instead.
Development
tests/ runs offline with the standard library (python3 -m unittest discover -s tests). tools/extract_effects.py --upstream <godot-ai v4.3.0 checkout> regenerates bridge/effects.json, and --check says whether it would change.
Source and license
This repository is MIT. See LICENSE.
Godot AI is MIT, https://github.com/hi-godot/godot-ai (tag v4.3.0, PyPI godot-ai==4.3.0).