Changelog
Every release, newest first, with the reasoning behind each change.
All notable changes to this project are documented here. Format based on Keep a Changelog; this project aims to follow Semantic Versioning.
[Unreleased]#
[0.11.0] - 2026-08-31#
Added#
godot-mcp test, a cold pure-logic test runner. It completes the set of subcommands that run without an editor. With no path it takesres://test/, swept recursively for*_test.gdandtest_*.gd; a path you name may be a directory or a single file. A test file is any script that extendsRefCounted, everytest_*method is a test, and the method takes one argument: a harness carryingt.ok,t.eq,t.neandt.fail, which records failures and keeps going so one method can report several. Nothing is installed in the project and nothing is imported: the runner is generated into.godot/, run by one headless engine, and deleted when the run ends. A test also fails when the engine prints a script error while it runs, attributed to the method that raised it, and a file that does not parse is reported as one failed file with the parse message and its line. Results come back per file and per method, with--jsonfor CI; exit 0 when everything passed, 1 on any failure, 2 when the run could not be set up. The runner extendsMainLooprather thanSceneTree, because the engine starts the project’s autoloads only for aSceneTreemain loop and a test run must not have that side effect. It does not refuse while an editor is open, andtest run-scenario,test reportand the rest of the addon’stestgroup still reach the editor.boss-patterns.md, a craft reference for multi-phase boss AI. A declarative phase table triggered by health fraction or timeout, a reusablePhaseControllerthat dispatches to named handlers on the host, and a behaviour container holding a boss’s moves as named objects in ordered groups, registered by reference count and lockable a group at a time so a dash or a channel stands the rest down. Built and driven live on 4.7.2-rc: four phases entered, the group lock read back mid-dash, and the stale-timeout guard proven with an assertion rather than a screenshot. Five gotchas the build turned up are recorded with it, including twoclass_namescripts being unable to reference each other’s type.
Changed#
- Six craft references extended, each recipe driven against a live editor.
run-based-games.mdgains the declarative wave timeline beside the reactive blackboard (typed events on a clock, a kill-rate scale factor, and when to prefer either).in-game-docs.mdgains toggleable debug draw overlays for the running game, gated onOS.is_debug_build()and driven fromruntime call.game-patterns.mdgains the JSON-sidecar content registry as an alternative to.tres, with the trade stated in four axes.menus-settings.mdgains the edit-then-commit-or-discard idiom for staged screens.save-systems.mdrecords relocating a save file for portable installs as one more rung on the migration ladder.shipping-export.mdgains theexport_presets.cfg.exampleconvention for a secret-bearing preset file and the feature-tag isolation gate for platform SDKs.
[0.10.1] - 2026-08-31#
Fixed#
- A command that writes a file now makes the editor current before it
returns. Writing through
FileAccess/DirAccess/ResourceSaverbypasses the editor’s own save path, and the answer had beenEditorFileSystem.scan()plus a documented “runeditor reloadafterwards”.scan()is asynchronous: it returns while the scan is still running, so the result was published before the editor had caught up. Over one connection,script createfollowed byresource findanswered zero results,fs deletefollowed byresource findstill listed the deleted file, andfs movereported the old path and not the new one. The CLI hid it, because each call is its own process and the launch latency happened to cover the scan. Every write sink now calls a synchronous per-file notify, and the one case it cannot express, a directory arriving or leaving, awaits its scan. The reload step is gone from the skill and the server instructions;editor reloadstays for disk changes this tool did not make, such as a git checkout or an external editor, and now awaits its rescan too. script editleft the loaded script object stale.Script.reload(true)recompiles from the object’s ownsource_code, and nothing re-reads the file into it, so a method the edit had just added was missing from the script’s API. Neither a rescan norCACHE_MODE_REPLACEhelped, which means theeditor reloadritual never fixed this half either.script createandscript editnow hand the text they wrote to the reload.project treereported any missing path as an empty directory. It stamped the directory node before opening anything, so a folder that had been deleted, and one that never existed at all, both came back as{"type": "directory"}. That is what read as a stale editor cache holding a ghost; the editor’s filesystem view never held one.tree,search, andgrepnow refuse a--paththat is not a directory on disk, and a directory the walk cannot open is markedunreadablewith the reason.fs movestranded a script’s.uidsidecar. Only.importtravelled with the file, so the editor minted the destination a new id and broke theuid://references the move exists to preserve. Both sidecars now move with the file, andfs deleteremoves both.
[0.10.0] - 2026-08-30#
Added#
-
godot-mcp upgrade, the 4.3-and-up port pipeline, run as five gated phases:preflightaudits the tree cold and commits every GDScript warning on,baselinerecords how the game behaves under the old binary,opentags and branches and harvests the to-do list under the new one,fix --category NAMEapplies one mechanical category, andverifyreplays the drive and diffs it. Each phase writes a report under<project>/.godot/upgrade/and stops, because each ends with something a person should read. Two rules run through all of it. A harvest reads seven sources rather than the editor’s error panel, and every finding names the source that found it: the launch log, the forced-on warnings, the tree-wide compile, every scene opened and validated, the resave diff parsed into droppedfile, node, propertytriples, the static rename sweep, and the drive. And a fix applies, proves itself, then keeps or restores: the category’s count has to reach zero with neither the compile nor the error panel getting worse, and a failure restores the scene checkpoint, checks the files back out, and commits nothing.--dry-runprints a unified diff and touches nothing. Both binaries are named explicitly with--old-godotand--godot, and neither is guessed. A GDExtension addon with no build for the target is a hard refusal inpreflight, since only its author can rebuild it. -
godot-mcp launch, a local subcommand that opens one editor for the project. The launch policy used to live only in prose in the agent skill, so every agent shelled out togodotby hand and some of them stacked a second editor onto a running one, which breaks port discovery for both.launchreads the liveness verdict first:runningrefuses and starts nothing,startingwaits rather than launching,crashedandclosedeach start one. An editor answering for another project counts as nothing serving this one, the same readingstatusgives. The child is detached, so the CLI exits and the editor lives on, and its stdout and stderr go to<project>/.godot/godot-mcp-launch.loginstead of the terminal, which a Godot editor writing to it corrupts.--wait(on by default) polls until the editor answers and exits non-zero if it never does;--headless,--timeout,--godot, and--jsonare there for scripts. -
godot-mcp import,check,export, andrun, four subcommands that spawn the engine themselves and never talk to the addon, so they work with the editor closed.importbuilds the.godot/import cache a fresh clone or a CI checkout needs before anything will open.check <file|dir>...parses.gdfiles with--check-only,--jobsat a time, and exits 1 withfile:lineper failure; directories are walked recursively, skipping.godot/andaddons/unless the path named is itself inside addons.export <preset>runs the headless export the addon’sexport projectonly ever printed, defaulting the output to the preset’s ownexport_path, and reports the file, its size, and the engine’s error lines.run [scene]starts the game standalone with the debug overlays, fixed timing, fps and gpu profiling, and--write-movie, then waits for the game’s own direct channel and reports the port to drive with--game.Two behaviours are worth knowing. An export that exits 0 having written nothing exits non-zero here, which is what missing export templates look like, and the parsed errors name each file the engine looked for.
importrefuses while an editor is running on the same project, because both own the same.godot/cache;import reimportis the live-editor form. Each command writes the engine’s output to<project>/.godot/godot-mcp-<name>.logand returns the exact argv it ran.exportandimportare addon group names too, and both still work: a command name after either one (export list-presets,import reimport) routes to the running editor.
Fixed#
-
upgrade verifyreported 0.00 percent changed pixels on every frame pair regardless of the images: the percentage was decoded fromchanged_percentwhile the addon sendsdiff_percentage. Caught by the first real cross-version run (4.4 to 4.7), where six-figurechanged_pixelscounts sat beside the zeros; the real noise measured mean 7.76 percent per frame on a lit 3D scene. -
The
upgrade openharvest counted headless dummy-renderer noise from the launch log as findings; engine C++ lines naming no project file are now dropped, the same ruleeditor errorsapplies to the panel. -
scene.openreported success for a scene the editor could not load.EditorInterface.open_scene_from_pathreturns nothing and reports nothing, so a scene whoseext_resourcepath no longer exists left the previous scene current while the call answeredopened: true, and every followingnode.*command silently targeted the scene the caller had left. The open is now verified before it is reported, and a refusal carries the dead paths, which are the usual cause and cannot be read out of a tree that never loaded. -
scene.validatenow reads four things, not two. Alongside theAnimationPlayertrack paths and storedNodePaths it already checked, it reportsMissingNodeandMissingResourceplaceholders left where the running build no longer registers a class, and everyext_resourcepath that is not on disk. The last check reads the scene’s own text rather than the loaded tree, because a scene with a dead reference never loads at all. -
A screenshot saved into a directory that does not exist now creates it.
Image.save_pngdoes not, soeditor.screenshotandruntime.screenshotfailed with the engine’s ownCan't save PNG at pathline and a result that never said why. Both paths create the parent directory and name the path in the failure. -
export inforeported a templates directory that does not exist. It readOS.get_data_dir(), which is the platform data root (%APPDATA%on Windows), while the editor keeps export templates one level down under%APPDATA%/Godot. The command therefore answeredtemplates_installed: falseon machines that had them installed. It now reads the editor’s own data directory. A present version directory still does not prove the platform’s binaries are in it, and the engine’s export error names each missing file. -
The Asset Library check compared against the wrong sha. It diffed the addon against the
asset-librarybranch tip, which matches what the portal serves only until a release refreshes that branch. Refresh the branch first, as the release ritual does, and the check compares the working tree with itself and reports that nothing needs submitting, while the portal goes on serving the previous release. It now reads the sha the portal actually stores from the library API and compares against that, so the answer no longer depends on running the check before the refresh. It also says when the branch is ahead of the portal, which is the state that produced the wrong answer, and refuses to guess if the API cannot be reached. Maintainer-only.
Verified#
- Godot 4.8-dev4 (
b56a91878, released 26 August 2026), the first check of the 4.8 claim since the command router changed. All 336 commands across 51 groups register with no group skipped, the live ClassDB reads, the game IPC answers, the new param refusal behaves as it does on 4.7, and the HTTP MCP conformance sweep passes 37/37. No code change was needed. The previous evidence was 4.8-dev3 at 329 commands. There is still no 4.8 beta; dev4 is the newest snapshot. dev4 converts the editor’s main screens into docks, which is the kind of change that could breakeditor errors, since it reads the editor’s own panels rather than a log file. That was checked from a windowed editor with a planted parse error, not only headless, because a headless run falls through to the log file and proves nothing about the panel path.
[0.9.1] - 2026-08-27#
Added#
- Two sample projects,
samples/ghibli-meadowandsamples/lighthouse-demo, each a standalone Godot 4.7 project an agent built by driving the tool. The meadow scatters 250,000 grass blades as a MultiMesh over a 70 m field, built at load from a seed rather than stored, so the scene file stays small. Its panel retunes the grass and the wind and rebuilds the field at a new density without a reload. The lighthouse demo is a stylized water surface whose control panel is generated from the shader’s own uniform list, so it shows whatever the material exposes. Both run withgodot --path .. - An agent eval harness as the scripted release gate, under
evals/: five scenarios carrying 37 deterministic checks, a worker preamble, a review rubric, and the run ledgers. A blind worker agent builds against a sandbox in the maintainer’s dogfood project, thenscripts/eval-check.ps1grades the result against the live editor, so what the worker says it did never counts toward the score. The/evalskill orchestrates a run. Methodology is published on the docs site under Agent evals. Maintainer-only:evals/does not ship in the public mirror. configure --config-dirwrites the client config somewhere other than the project it points at. The two are the same directory in an ordinary project, and different in a repo whose Godot project sits in a subdirectory, where the client reads.mcp.jsonat the repo root while the server has to target the subdirectory. It is rejected alongside--globaland for codex, both of which write fixed paths, and the directory has to exist already.- A “What’s not supported” page on the docs site, under Reference. It
records features that were considered and deliberately not built, with the
reasoning and what to use instead: declarative VisualShader graph
authoring, per-domain tool subsets, and driving an editor on another
machine. Each entry argues from the engine rather than from preference. The
first notes that 4.7 ships 110
VisualShaderNodesubclasses whose port metadata is not bound to GDScript, so wiring ports by index means a hand-written table nothing can validate, and thatconnect_nodesaccepts a type-mismatched edge and returns success, so a stale index yields a shader that compiles, renders wrong, and reports no error. scripts/mirror.shandscripts/lib/snapshot_guard.py, a POSIX port of the mirror snapshot ritual, so a release can be mirrored from macOS or Linux and not from Windows alone. The guard is Python rather than shell because the policy patterns use\band character classes, which POSIX ERE lacks and macOSgrepcannot match with-P. A shell port would quietly under-match and pass a leaking tree. Verified by rebuilding the published 0.9.0 snapshot and diffing trees: 421 files, byte-identical.task mirrordispatches to whichever script fits the platform. Maintainer-only:scripts/does not ship in the public mirror.
Changed#
task buildwritesbin/godot-mcpon every platform except Windows, which still getsbin/godot-mcp.exe. TheBINvariable had the.exename hard-coded, so building from source on macOS or Linux produced a working binary under a misleading name.
Fixed#
- A screenshot step ignored
half_resolutionwhen it saved a file. Atest run-scenariostep carrying bothsave_pathandhalf_resolutionwrote a full-resolution PNG and reported the full dimensions, so the flag read as accepted and did nothing; the capture was byte-identical to one taken without it. Only the discard branch, which returns the frame instead of saving it, had ever honoured the flag. The step now forwards it and the game downscales with the same callcapture_framesuses, and the result reports the dimensions actually written. Saving defaults to full resolution, unlikecapture_frames, because a saved PNG is evidence someone looks at; both help strings now name the default.runtime screenshotaccepts--half-resolutionfor the same reason. node getaccepts--property, the singular spellingnode setrequires, as shorthand for a single-name--properties. Agents that had just usednode setcarried the spelling over and got a refusal for a mistake most callers make.- Two help strings described less than their commands return.
editor errorsreports asuppressed_noisecount its help never named, so a result readingcount: 0beside a non-zero suppressed count invited the reading that something was being hidden.spatial boundssaid it readsVisualInstance3Dgeometry and offeredMarker3Das the example of a node with none, which left lights looking like points. A light reports its influence volume, sospatial relatesays it overlaps whatever it lights, and a light’s placement has to be read from its pivot. Both now say so, and the skill’sdoc-searchexample no longer claims a query that does not return what it says it does. - A param a command does not declare now refuses the call instead of being
noted in the result. The router compared sent param names against the
command’s docs and annotated the payload with
unknown_paramsplus a did-you-mean hint, which meant a flag borrowed from a sibling command, or one spelled for a different command, returned a plausible answer to a question the caller had not asked. Three independent agents hit it in one session:scene validate --path res://...answeredvalid: trueabout whichever scene was open,node get --property xreturned every property wherenode setrequires that same singular spelling, and--format jsonplaced after the command reached the addon rather than the CLI’s formatter. Each is now-32602with the unknown names, the hint, and the command’s declared params, and the check runs before the handler, so a refused call cannot have already changed the project. Commands that deliberately accept an unadvertised param keep working: the router’s alias table now carries all 26 of them, includingparentas an alias forparent_pathacross the 19 add-style commands that take it, which the old annotation had been flagging as unknown on calls that worked.task checkgainedtask audit:params, which walks every handler for the param keys it actually reads and fails on one the docs do not declare, because refusing an undeclared param is only correct while the docs are a complete map of what each handler accepts. - A typed
Array[X]property took an untyped array and dropped it on save.Object.set()with an untypedArrayon anArray[X]export is either refused by the engine or stores a value the serializer discards, and neither outcome reached the caller: a gate build watchedresource.createreportplanetsunderproperties_setwhile the saved file held[]. Writes now build a properly typed copy first. Elements coerce toward the array’s element type, so an Object passes a class or script check, ares://oruid://string is loaded, a Dictionary instantiates the element class and applies its keys strictly, and a nested typed array recurses. An element that cannot be carried refuses the whole array and names its index instead of quietly shortening it. Both write paths read the size back afterwards, because a container can refuse an assignment without raising anything the caller sees, and a refusal must not hide inside a success. release.ps1would have shipped an addon zip with no.gdignoreif it were ever run on macOS or Linux.Compress-Archivetreats a leading dot as hidden on PowerShell for Unix and skips those entries without a word, soassets/.gdignorevanished and a consumer project would have imported the whole bundled texture pack. It now refuses to run outside Windows and namesrelease.sh, the POSIX port, whichtask releasealready selects there. Published archives were never affected: they were built on Windows, where a leading dot carries no hidden attribute.- Release archives carried files from the maintainer’s working directory.
The packaging step copied
project/addonsandskills/godot-mcpoff disk rather than out of the commit, so anything sitting in the checkout rode along. The 0.9.0 archives ship 94 gitignored.importfiles because of it: stale ones, sinceassets/.gdignoremeans Godot never imports that folder, and each names a desktop S3TC path underres://.godot/imported/that no consumer project has. Being untracked, they never passed the policy scan that gates every public artifact, because that scan reads a commit. The same copy baked in Windows line endings, giving 65 of 244 text files in the addon zip CRLF where a POSIX checkout gives LF. Packaging now stages throughgit archivefrom the commit being released, so the artifacts hold exactly the tracked files and the line endings those files carry as committed.core.autocrlfis forced off for that step:git archiveconverts line endings on the way out exactly as a checkout does, so on a Windows clone it re-baked CRLF into every text file it staged, which left the artifact depending on the building machine again. Verified by rebuilding 0.9.0 on Windows from the released commit and diffing against the published assets: 94.importfiles gone, nothing added anywhere, every file in the addon zip byte-identical to its blob, and CRLF down to the one bundled license file whose blob holds it. The GitHub mirror and the Asset Library branch were never affected: both already built from the commit. configureaccepted a directory that was not a Godot project. The config it wrote looked correct, butservecould not resolve a project root from that directory, so it fell back to port 9080 and also skipped the check that the answering editor belongs to this project. Pointed at a repo root whose Godot project sits one level down, it silently drove whichever editor held the default port.--projectnow resolves through the same upward walkinstalluses, a directory in no project is refused outright, and the refusal names anyproject.godotone level below it.- The two mirror scripts built different snapshots from the same commit.
mirror.ps1piped its filtered.gitignoreintogit hash-object --stdin, and PowerShell terminates what it writes to a native command with the platform newline, so that blob picked up a trailing CRLFmirror.shnever produced. Alternating between the two machines flipped the line back and forth in the published snapshot. It now writes the text to a file and hashes it with--no-filters, and both scripts land on the same blob for the same input. The two exclusion lists had also drifted apart in the POSIX port:mirror.shcarried noevalsentry, andmirror.ps1lacked the any-depth globs that catch a context doc arriving in a folder the exact-path list does not name. Maintainer-only. mirror.shrefuses to run on Windows. Under Git Bash the guard writes its filtered.gitignorein the locale codepage and in text mode, which drops the bytes of every non-ASCII character and puts the carriage returns back, so the snapshot would differ from the onemirror.ps1builds from the same commit. It now exits and namesmirror.ps1, while--helpstill works.task mirroralready routes by platform, so this catches a direct invocation.release.ps1carries the same kind of refusal in the other direction. Maintainer-only.
[0.9.0] - 2026-08-13#
Added#
- The addon runs on Godot 4.3 through 4.8 (beta). Development still targets
4.7. Registration is per group now, so an engine that cannot compile a group
skips it and
engine.commandsnames the file underunavailable_groupsinstead of one unsupported group taking the whole plugin down. Verified against the official 4.3, 4.4, 4.5, and 4.6 stable builds plus 4.7.2, one headless editor per version against its own copy of the project: all 330 commands registered on every one of the five,unavailable_groupsabsent throughout, and the game IPC answering on each. Six APIs newer than the floor refuse cleanly and name the version they need.scene closeneeds 4.5, and below 4.7 every close needs--discard, because the editor cannot report which tabs hold unsaved work and unknown is not clean.csg bakeand the AGX tonemap need 4.4. Runtime error capture needs 4.5, below whichruntime errorsanswerscapture: "unavailable"naming the version rather than an empty list that reads as no errors. The floor is beta: the evidence behind it is five engine builds and one real port, with no shipped projects on it yet. - A craft reference on porting a project between 4.x versions (beta),
porting-godot-versions.md, published as Porting between Godot versions. It runs the upgrade with evidence on both sides of the move: record how the game behaves before the new editor ever opens it, as a replayabletest run-scenariostep list plus screenshots and the numbersruntime getreturns, then replay the identical list afterwards. The comparison threshold comes from measurement: run the baseline twice on the same build, and the spread between those two runs is what a post-port difference is read against. On the port it was validated against, a 4.6 FPS starter moved to 4.7, two runs of the same build differed by 0.083 units in_process-driven position and 8.35 percent between screenshots, while the cross-version pair differed by 1.85 percent, so the after-port run sat inside the noise. The guide also carries the per-minor break list, the rollback point that has to exist before the first launch, the reimport and resave passes, and theconfig/featuresstring the upgrade never rewrites. Scope is 4.x to 4.x; a Godot 3 project goes through the engine’s own converter first. It is marked beta on the strength of that single port. node.set_editable_instanceandproject.remove_setting. The first flips Godot’s editable-children flag from the CLI, which the instanced-scene write guard (under Fixed) names as its remedy; the second removes a project setting, which until now required--allow-unsafe-editor-iocode execution. The catalog is 332 commands across the same 50 groups.- The
test.run_scenarioscreenshot step saves evidence:save_pathroutes the capture through the game-side screenshot handler and the step result names the file; without it the step reportssaved: falseinstead of a barecaptured: truethat wrote nothing anywhere. - Four craft guides gained the breadth their titles promised.
level-designrestructures around space families: the shared spine stays, the combat-interior material is named as one family, and exterior/open space (landmarks, terrain as pacing, walk-second travel budgets), 2D level design as its own discipline (screen vs scroll, the camera window as the sightline, room graphs), and pacing without combat are new.shipping-exportgains the Android, web, macOS, and iOS legs with honest statements of what a Windows host cannot finish.in-game-docsgains the 2D equivalent of the gym/zoo/museum/notes patterns.tile-constraintnames its GridMap 3D scope and bridges the 2D case to terrain painting. - Encryption’s honest edges.
save-systemsseparates encrypting a save (FileAccess.open_encryptedand variants; the key ships in the binary) from validating one (Crypto.hmac_digest, live-confirmed);multiplayer-patternsstates that ENet is plaintext UDP and names the DTLS setup calls; the shipping guide connects the APK-is-a-zip receipt back toencrypt_pck; the site’s gotchas page corrects the two expectations readers bring. - The trailer guide became a matrix of cuts and formats: duration profiles from a 15-second hook to a 2-3 minute feature, delivery profiles from 16:9 to 9:16 composed per viewport rather than cropped, shot lists carrying per-profile framing, and a render manifest.
Changed#
- Every em dash is gone from every shipped surface. Roughly 1,700 of them across the craft guides, the site pages, the README, this changelog, the CLI’s help and error text, and the addon’s messages and source comments, each removed by rewriting its sentence rather than swapping punctuation. Code behavior is unchanged: only string literals and comments moved, and the full test suite plus a scratch-copy editor compile prove it.
Fixed#
node.connectnever persisted a connection. It connected withoutCONNECT_PERSIST, so the wire worked all session,editor.signalsreported it, and the scene file never carried a[connection]line: everything built on it was dead on reload. Connections now persist (persisted: truein the result), and re-connecting an old non-persistent wire upgrades it in one undo step.node.connectalso now refuses a target method that does not exist, with a nearest-name hint;--allow-missing-methodoverrides for a script that gains the method later.- Writes into an instanced scene’s children were dropped on save, with a
success envelope. Godot’s packer discards overrides on a non-editable
instance, so
node.setreported old and new values while the scene file never changed. Write paths across the node group now refuse (-32009) naming the instance and the remedy,node set-editable-instance. - Adjacent scenario input steps destroyed each other. The editor-to-game
input channel is a single file slot, and writing truncates it, so a press
adjacent to another input step vanished while both reported
sent: true; a payload arriving mid-input.sequencelikewise discarded the sequence’s remainder. Editor-side writers now wait for the game to consume the slot (bounded, naming a debugger break when the wait expires) and the game-side queue appends instead of replacing. test.reportscored every wait, input, and screenshot step as a failure, so a green session read as 13 passed, 81 failed. It scores assertions only now and reports the other steps assteps_recorded.project.set_settingsilently created phantom settings on typos: a mistyped key wrote a brand-new section intoproject.godotand reported success while the intended setting stayed unset. The result now carriesexisted, plus a nearest-key hint when a created key looks like a typo of a real one.- Packed-array properties truncated silently when handed one string
literal: a three-point polygon written as a single
"[Vector2(...),...]"string became one point. The strict write path now parses the whole literal or refuses, naming the documented JSON-array-of-literals form. script.edit --insert-at-linepast the end of the file clamped silently; the result now reportsinserted_at,clamped, and the requested line.status --allexited 1 when a successful scan found nothing running. A scan that finds nothing has still answered; it exits 0 with the empty payload.script validate --pathfalse-failed on files underaddons/. The throwaway compile the command runs has noresource_path, so thedebug/gdscript/warnings/exclude_addonsexclusion (default on) never applied to it and any warning the project escalates to an error failed a file the editor itself compiles clean. The throwaway now carries a path under the real file’s ownaddons/location, so the parser applies the exclusion the same way the editor does. A real parse error still fails, and a project running with the exclusion off keeps its addon warnings.install --enableleft out the two autoloadsruntimeandinputneed, so a project installed that way answered everyruntime.*andinput.*call with a missing-singleton failure while the addon reported as installed and enabled. Godot runs a plugin’s_enable_pluginhook only when someone ticks the checkbox in the editor, and that hook is what injectsMCPGameInspectorandMCPGameInput; writing the enabled line intoproject.godotnever fires it. The installer now writes the pair itself. Running it again is a no-op, and a same-named autoload pointing at another script is reported rather than overwritten. Affects 0.6.0 through 0.8.2: on those versions, toggle the plugin checkbox off and back on in the editor, which runs the hook.doctorgained a game autoloads check that names the missing entries and prints the command that repairs them.- A scenario assert comparing a vector property against a string crashed the
running game into the debugger. GDScript treats
==across those types as a hard error rather than a false result, so a typo in atest run-scenariostep list stopped the game mid-run with nothing naming the step. An assert now coerces the expected value toward the type of the live one and returnspassed: falsewith areasonnaming both types. - A scenario input step silently turned a held action into a one-frame tap.
A pressed action is released again in the same batch unless the step sets
auto_releasefalse, which is the right default (an action never released stays down for every later step) and is also why a hold-then-wait moved a player 0.08 units instead of 7. The release is unchanged, the step result now carriesauto_released: truewhen it happens, and the command’s own docs state the default and the flag. - A stranded game reply could be delivered as the next command’s answer. A
runtime.*call that gave up waiting, most often because the game had stopped at a debugger break, left its reply on disk for the following command to read and return as its own. Game IPC requests now carry a correlation id the game echoes back, and the editor discards a response that does not match. The old recovery that pressed the debugger’s Continue button to free a stuck reply is gone: a break is reported and left where it is. script validatewrote a phantom parse error into the editor’s error log for every script carrying aclass_name. The throwaway copy it compiles registered as a second declaration of the same global class, so the editor logged “Class X hides a global script class” against a file that was fine andeditor errorsreported it afterwards. The declaration is stripped from the copy before the compile, with line numbers preserved so every diagnostic still points at the real line.statusread a live editor as absent on Windows. The answering editor’s project path and the caller’s were compared as text, so an 8.3 short path (D:\PROJEC~1\game) and its long spelling read as two different projects and the verdict came back as if nothing had answered. Both sides now resolve to a real path before the comparison. A genuine project mismatch also reported a verdict that disagreed with its own advice, saying to wait while the action said to launch; it now reportsclosedorcrashed, which is what the launch policy reads.- A malformed JSON flag value went nowhere and still returned success. A
value opening with
[or{that did not parse was passed on as a plain string, and the addon’s typed parameters discard a string they cannot read and fall back to a default, so a mis-escaped--propertiesreturned every property inside a success envelope with nothing saying the flag had been dropped. Such a value is now an error naming the flag. A string meant to begin with a bracket or brace is sent verbatim behind a leading backslash (--text '\[literal]'). project tree --filterreturned every empty directory. A directory whose files all failed the filter was still listed, so a narrow filter came back as the project’s whole folder structure with a few files in it. Directories with no surviving descendant are pruned.godot-mcp --versionandgodot-mcp versionprint the CLI version. Both spellings failed before, the flag on an unknown-flag parse error and the bare word by being read as a command group.doc note --action add --atnow refuses a 2D scene like its four scaffold siblings, instead of silently parenting aMarker3Dunder aNode2D. The release-gate verification build found it. The billboard labels thedocgroup creates are also named (DocLabel) so they are addressable, where they previously landed as unstable auto-generated names.- The 2D jump-arc measurement recipe in
level-designworks as written now: it read positions fromruntime capture-frames, which returns PNG frames; the corrected loop holds directional input and samplesruntime monitor, and its numbers were verified against analytic jump physics. Every other claim in the two new-guide verification sessions passed against the live editor with results read back.
[0.8.2] - 2026-08-12#
The keeper’s-light release: twelve defects found by driving a complete game
build through the CLI, each fixed at the root, plus the new scene close that
build showed was missing. It also carries the CLI’s presentation pass (results,
help, and the banner now render for a human at a terminal while piped output
stays exact JSON) and the docs site’s Godot MCP CLI identity and type pass.
A sustained project build now gates every release; the how and why is on the
site’s new How it’s tested page.
Added#
scene close. Closes a scene tab, either--pathor the active scene when no path is given, and reports the remainingopen_scenesand the resultingactive_scene. It closes the gap the rest of the surface assumed was covered:scene delete,fs delete, andfs moveall refuse a scene that is open and told the caller to close the tab, which no command could do, so a throwaway scene could not be cleaned up without a person at the editor or a restart. Those three refusals now namescene closeand the exact path to pass. Closing discards unsaved changes without prompting, so a scene with unsaved changes is refused with-32009unless--discard true; and closing a background tab restores the scene that was current, because Godot otherwise selects the closed tab’s neighbour and every laternodecommand would quietly target it.editor errors --clearand--internal=false.--cleardrains the Output panel after reading, the same pull-then-drain contractruntime errors --clearalready had.--internal=falsedrops the entries whose source file is the engine’s own C++ rather than a project script. Every editor error line already carries its<file>:<line>, and a filesystem rescan aftereditor reloadfills the buffer with dozens ofprogress_dialog.cppandclass_db.cpplines that bury the parse error you were looking for. The default is unchanged, and a line whose source cannot be read is never treated as internal. Note that the engine attributespush_errorandpush_warningtovariant_utility.cpp, so the filter drops those too.script read --start-line/--end-line. Returns a 1-based inclusive slice instead of the whole file, using the line addressingscript editalready takes, so reading around a reported error line no longer costs the file.line_countstill reports the file’s real total. Both were previously accepted and ignored.project settings --filter. A case-insensitive substring match anywhere in the key, for when you know the word (msaa,shadow) but not which section owns it.--sectionremains the prefix match and the two combine. Also previously accepted and ignored.scene instance --pathis accepted as an alias for--scene-path. Every otherscenecommand names the file--path, so reaching for it here is the obvious mistake, and it used to fail on a missingscene_pathwhile the flag was annotated as unknown.- Readable terminal output. On a terminal, a result renders for a human: an
object becomes a titled key/value box, an array of objects becomes a table,
values are color-coded by JSON type, and a nested value too large for one
line prints as an indented JSON block under its key. Help listings,
doctor, and error/diagnosis output carry the same color scheme (traffic-light badges, cyan command names, red errors), and the top-level help is restructured into aligned Usage / Subcommands / Examples / Flags sections. Piped or redirected output stays exact pretty-printed JSON with no escape codes, so scripts and agents are unaffected;--format pretty|json|tsvpins a format explicitly andNO_COLORdrops the color while keeping the layout. The styling lives in a new zero-dependencyinternal/uipackage that also enables VT processing on legacy Windows consoles. - Subcommand help is uniform. Every local subcommand renders the same
styled shape as the top-level help (heading, usage, notes, an aligned
--flagtable) instead of the raw single-dashflagdump, and three routes reach it, all exiting 0:<sub> --help,<sub> help, andhelp <sub>. Previouslycreate helptried to run and failed on the missing--path. Section headings carry the accent, usage-line placeholders (<group>,DIR,NAME) tint magenta so the fixed words read apart from the fill-ins, and the banner carries the docs-site URL. status --all. Scans the editor auto range (9080-9095, plus any env- or discovery-pinned port) and the game range (9200-9215) concurrently and lists every live instance with its port, project name and path, Godot version, pid, and whether it serves the current project. A terminal gets the table render, piped output gets JSON (editors+gamesarrays); exit 0 when at least one editor is live. Game servers report presence only, because that channel carries no project identity to ask for.- The accent color is a burnt yellow. Headings, command names, and the banner wordmark render in 256-color amber instead of cyan, keeping the traffic-light ok/warn/fail tokens as they were.
- Docs site type and surface pass. Code, commands, and terminal frames render in Source Code Pro; display headings (docs h1/h2, the landing hero and section heads) in Manrope; body text stays Inter. Callouts trade the accent side bar for a tinted full hairline on the same wash, and blockquotes get the matching quiet full-border surface.
- The display name is Godot MCP CLI. The docs site and README now present
the product under the same identity as the Asset Library listing and the
banner (which stylizes it GODOT MCPCLI). The command you type is still
godot-mcpeverywhere. - A front-door banner. Running
godot-mcpwith no arguments at all now prints the brand mark as a block-glyph tile beside the wordmark (reading GODOT MCPCLI), the version, and where to go next, exiting 0. A bare invocation is a person exploring, not a script. Run inside a project it also reports that project’s editor on one line (running with port and pid, starting, crashed, or none), from the same diagnosisstatususes; outside a project it stays silent rather than probe a port that might belong to someone else’s editor. Usage errors,--help, andhelpstill print the structured help; there is no banner anywhere else, so no suppression flag is needed. --format ndjsonandGODOT_MCP_FORMAT. NDJSON emits one compact JSON value per line for a top-level array result and the whole result on one line otherwise, keeping nesting intact where TSV flattens it. The environment variable pins a format for a whole shell or CI job when the flag is absent; the flag wins, and an unrecognized value warns and falls back to the default.
Fixed#
scene createnow opens the scene it creates. It wrote the file and left the editor where it was, so every followingnode addsilently built into the previous scene, or, with nothing open, failed with a suggestion to “use scene.open or scene.create first” immediately afterscene createhad been used. The result reportsactive_scene,--open=falsekeeps the old behaviour for a batch that writes many files, and the no-scene error no longer names the command the caller just ran as the remedy.runtime screenshotright afterscene playreturned an all-black frame with no error. The capture read a viewport texture nothing had drawn into yet; the game now waits for a realframe_post_drawbefore reading it, and reportsblack_framewhen the result is uniformly black anyway. A scene that is black by design looks the same, so this is a flag rather than an error.- Game commands failed intermittently with “Could not read game response
file”, where an immediate retry always worked. The game wrote its reply
straight to the path the editor polls, and
FileAccess.opencreates that file empty, so a poll landing between the open and the close read a half-written file. The game now writes to a scratch name and renames it into place, and the editor’s read retries briefly rather than treating one failed open as fatal. - A game paused at a debugger break timed out with the wrong explanation.
Every
runtime.*call spent its timeout and then suggested checking theMCPGameInspectorautoload, which the same call had already verified on disk. The editor now asks the debugger bridge and says what is actually true: the game is stopped at a break, with the reason,debugger_breaked: true, anddebug.state/debug.resumeas the way out. editor run_scriptrefused a space-indented body. The submitted code was wrapped in a tab-indented function, so anything indented with spaces became a tab-and-space mix that GDScript rejects, for reasons the caller never saw. The body’s common leading indent is stripped and what remains is re-expressed in tabs before wrapping, so space-indented, tab-indented, and already-nested snippets all compile.editor set_cameraposed the 3D camera without bringing the 3D screen with it. It succeeded and returned a 3D pose while the editor’s main screen sat on 2D, and the followingeditor screenshotcaptured the canvas: the right call, the wrong picture, no error anywhere.set_cameranow switches the main screen to 3D (--switch-main-screen=falseopts out), and bothscreenshotandget_camerareport the activemain_screen. A screenshot never forces a switch: capturing the 2D editor is a legitimate thing to want.input_map get_actionscould not see the project’s own actions. It read the InputMap of the editor process, which holds the editor’s bindings (ui_*,spatial_editor/*) and not the game’s, so a project’sjumpwas invisible whileset_actionpersisted it correctly. It now reads ProjectSettingsinput/*, which is where project.godot keeps them and where the running game reads them, and reports which source answered.spatialrefused every node without visual geometry. AMarker3Dor a bareNode3Danchor answered “has no 3D visual geometry”, which made marker-to-marker questions unanswerable through the group that exists to answer them. Those nodes now read as a zero-size box at their origin, flaggedpoint_only(per side, inspatial relate, which also returnsdistancenow). Only a node with no transform at all is still refused.editor clear_outputdid not clear anything. It printed fifty blank lines to scroll the panel, leaving every old line in the buffereditor errorsreads, so a “cleared” panel still answered with the errors it had just reported. It presses the Output panel’s own Clear button, and falls back to the blank lines only when that button cannot be located, saying so.
[0.8.1] - 2026-08-09#
Documentation, media, and the skill archive. No command, addon, or CLI behaviour changed, and the binaries and the addon are functionally identical to 0.8.0, so this release exists to ship a new craft guide and the pages that came with it.
Added#
- A craft reference on filming a game,
game-trailers.md, published as Videos and trailers. It covers the shot list as the source of record, a dev-only director scene and the five rules that keep an unattended render honest, cutting between shots through a cover while the world runs backstage underEngine.time_scale, Godot’s movie writer and its three output formats, the ffmpeg encode, and thescene playplusruntime screenshotloop that proves a shot before a render is spent on it. Two of its facts were measured rather than assumed: the recorded resolution comes fromdisplay/window/size/viewport_widthandviewport_heightrather than the window, since a 2560×1440 project filmed in a window forced to 1280×720 (with--resolution 1280x720passed as well) still wrote a 2560×1440 film; and under--fixed-fpstweens and timers measure film seconds whileTime.get_ticks_msec()measures wall clock, which ran about four times longer than the footage it was writing. - A worked example for project-local commands, answering what the
res://mcp_commands/hook is actually for rather than only proving it works.custom.broken_refssweeps the project’s text files forres://paths anduid://ids that no longer resolve, with the file and line each sits on;custom.replace_refrepoints every reference from one resource to another and repairs theext_resourceuid beside each rewritten path, because the loader prefers a uid that resolves and a path-only rewrite still loads the old file. Between them they show both halves such a command may need: a read-only sweep, and a write path that guards its inputs, refuses a scene open in the editor, and plans every file before writing any of them. Add your own commands carries a compact version of the sweep. - A three-minute demo video on the README, linked from a still: a seascape built inside a live editor, with water tuned while the game runs, boids for the fish and gulls, and audio buses wired from a panel.
Verified#
- Godot 4.8-dev3 (
51105ccbe), with no code change needed. The plugin compiled and bound both transports, all 329 commands registered, the HTTP MCP conformance suite passed 37/37, and a sweep overproject,scene,node,script,engine,runtime,inputanddebugbehaved as it does on 4.7.2, including the two-hop runtime IPC end to end.
Fixed#
- The commands badge and one line under the context-cost table still read 316. The count has been 329 since the debug group landed in 0.8.0.
- The Asset Library record named a download commit a release behind. The
asset-librarybranch was refreshed for 0.8.0 on 2026-08-07, but the record still pointed at the 0.7.x snapshot. A listing is pinned to whatever commit it was submitted with, so a stale note is how an old download keeps being served.
[0.8.0] - 2026-08-07#
The editor-side release: an interactive debugger the agent can drive, the engine documentation’s prose to go with its structure, and the dashboard docked into the editor itself. 329 commands across 50 groups, every new command driven live before landing.
Added#
- The dashboard, docked in the editor. The addon now ships an MCP dock
(right side by default, movable like any dock) with the web dashboard’s full
feature set: stat tiles, error banner, top groups with per-method tooltips,
recent errors, and the live timeline with filter chips. It adds what only an
in-editor surface can, with stats read in-process (no extra process, no port,
and no polling at all while the dock is closed), full call parameters on
each row’s tooltip, and a Reset button. The design language is the web UI’s
instrument-cockpit look (flat blocks, hairlines, zero radius, mono numerals,
the Godot-blue accent) translated onto colors derived from the editor theme,
and every dimension scales with the editor’s display scale.
godot-mcp dashboard(the web UI) is unchanged; both read the same counters. - The
debuggroup: an interactive debugger for the editor-launched game (329 commands, 50 groups).set_breakpointarms a line through the editor’s own script gutter, so it is visible in the gutter and the Breakpoints list, live for a game already running, and kept for the next run. It refuses lines that can never be hit (blanks, comments, declarations) with the first executable line suggested instead; arming a per-frame callback like_processearns a warning, since every resume would re-break immediately.stateandframeread the paused game’s stack and variables,step/resume/pausedrive execution through the debugger’s own controls, andreload_scriptshot-reloads edited.gdfiles into the live process with state kept.scene.playnow launches every run with the editor’s script-sync options armed so a reload always applies. Verified end to end against a live 4.7.2 game: break at a line, step, read a variable change, resume, pause into_process, and a hot-swapped function returning its new value without a restart. engine.docsandengine.doc_search: the engine’s documentation prose, live from the editor’s doc cache.engine.class_infoanswers what exists; these answer what it means, with the running build’s own Help-panel text.docs --class C [--member m]walks the inheritance chain, finds annotations with or without the@, and covers pages ClassDB has never held (@GlobalScope,@GDScript, the Variant types, with an enum name likeKeyreturning its values).doc_search --query "wrap text"searches names and prose by concept and ranks name hits above prose hits, so the half-known term surfaces first. Unknown names come back with closest-first suggestions.editor.activity: poll what happened in the editor since a cursor. Selection changes, scene switches, scene and resource saves, and undo/redo history bumps are buffered in a 200-entry ring (services/activity_log.gd) and read with the same--since-seq/--clearcursor contract asruntime.errors. An agent sharing the editor with a person can now see what the person just did between commands: click a node, save a scene, hit undo.editevents include actions the MCP’s own commands commit, and the command’s result says so. Verified live: commit, undo (via an injected Ctrl+Z), selection, and save each produce exactly one event.
[0.7.2] - 2026-08-03#
Documentation only. No command, addon, or CLI behaviour changed. The agent skill and the craft guides did change, so the skill archive is worth updating even though the addon is unchanged apart from a one-word fix in its README.
Added#
- A changelog page on the docs site, at
/docs/changelog. It renders this file rather than a copy, so the published history and the file maintainers actually edit cannot disagree. Every release anchors, so deep links work, and the search index covers it: looking up a feature in the docs now also finds the release it shipped in.
Documentation#
- Every prose surface went through the writing linter, reading the matched
spans rather than the summary counts. That covers the README, INSTALL, both
addon READMEs, all 18 docs pages, this changelog, and the 28 craft guides,
which had never been linted at all despite being published as guide pages.
Most of the volume was bullet labels shaped
- **Sentence.** Body, where the period sits inside the bold; the rest were empty intensifiers and vague words swapped for the concrete thing meant, such as gapless looping in the audio guide and “things they can’t use must look unusable” in the level-design one. Every surface now reports only findings that were reviewed and deliberately kept, each with a recorded reason. - The Godot builds behind the 4.7+ claim are named in the README, the
installation page, INSTALL, and the addon README:
4.7.1-rc,4.7.2-rc, and a4.8-devbuild frommaster. That last one is a master build rather than the published dev 2 snapshot, and is described that way. The note carries the consequence worth knowing before opening a 4.7 project in 4.8: 4.8 writes aunique_idattribute into saved scenes that 4.7 does not, so the round trip is not clean. - The comparison names projects rather than org paths,
godot-aiandGodot-MCP-Native.
[0.7.1] - 2026-08-03#
Forward-compatibility work: the addon is verified on Godot 4.8-dev, and
engine search picks up 4.8’s new fuzzy matcher when it is there.
Added#
engine searchfalls back to fuzzy matching when its substring sweep finds nothing and the running build exposesFuzzySearch(Godot 4.8+). An abbreviation an agent guessed now resolves:linvelreachesRigidBody2D.linear_velocity,angvelreachesPhysicalBone3D.angular_velocity,glbposandrotdegreachNode3D.match_modein the result says which pass produced the matches. Substring stays the first pass, so a query that already worked keeps its previous speed and result, and on 4.7 the rescue is simply skipped.
Verified#
- The addon runs unmodified on Godot 4.8-dev and on 4.7.2-rc. 316 commands
across 49 groups register on both, with no parse or compile errors. 4.8 adds a
unique_idattribute to every node in saved scenes and dropsload_stepsfrom the header;fs.moveis unaffected because it rewrites quoted path tokens across whole file text rather than parsing lines. 4.8’s embedded Game View, on by default for new projects, does not affect theruntimechannel: it reparents the game’s window while the game stays a separate process.
Documentation#
- A three-way comparison in the README and on the landing page, against
godot-aiandGodot-MCP-Nativerather than one rival. Both are actively maintained and both run on Godot 4.5 and 4.6, which this project does not; Godot-MCP-Native reaches the running game through an in-game probe much as this project does, so that is no longer a dividing line. The differences that hold are structural, and each project gets its own paragraph of where it wins. discover-then-drivedocumented the callable half. The page claimed any feature surfaced as a property or a callable is reachable, then listed only the property commands;node.callandruntime.callare that missing half.
[0.7.0] - 2026-08-03#
Four commands (316 total, still 49 groups) and a craft reference, from reviewing the public GDQuest and Brackeys repositories. Writing GDScript still needs no tool beyond the editor.
Added#
node callandruntime call, a generic method-invocation primitive for the edited scene and the running game.node set/node getalready reached any property the running build exposes; calling a method meanteditor run-script, which is arbitrary code execution to invoke one named call. Arguments coerce toward the method’s declared signature, so'["Vector3(1,0,1)"]'arrives typed and a short[5]for a Vector3 is refused rather than silently zeroed; an unknown method errors with a did-you-mean. Both are audited.node callis not undoable and says so in its result, since UndoRedo cannot reverse arbitrary side effects.lighting bake,csg bake, andnavigation bake-meshare single-method wrappers that existed only because this was missing.script lint, a native GDScript style linter with no external dependency (utils/gdscript_linter.gd). All 17 rules from the official style guide: the 9 naming rules at severityerror, the rest as warnings. Findings carrypath,line,rule,severity, andmessage;--disabletakes rule names and rejects unknown ones with the full list;# gdlint-ignore[-next-line] rulesuppresses in the source itself. The scan is comment- and string-aware, soa == ainside a string literal or a#inside quotes never trips a rule. Verified identical to GDQuest’sgdscript-formatter lintacross this addon’s 61 files (22 findings, same file, line, and rule), using that tool as a test oracle; on a deliberately messy fixture it reports two more, both localvarnames the external tool misses and the style guide covers. This closes a real gap: agents wrote GDScript throughscript create/script editwith nothing ever judging the result, sogdscript-style.mdwas guidance rather than a check.script symbolsreads one script’s declared methods, properties, signals, and constants without pulling the file into context. It reaches scripts with noclass_name, whichengine class-infocannot, and that is the ordinary case of aplayer.gdattached to a node.--include-inheritedwalks the base-script chain.ai-steering.mdcraft reference. Agent movement that reads as a creature rather than a cursor: accelerating toward a desired velocity instead of assigning it, arrive/flee/pursue/separation as summable forces, blending versus priority, facing, and 3D differences. Every recipe was driven against a live 4.7 editor and verified numerically rather than by screenshot.- Input-action validation in
character-3d.md. A controller that reads an action nobody mapped does not error.Input.is_action_pressedon a missing action returnsfalseforever, so the character silently never sprints and nothing says why. The guide now takes action names as@exports and checks them in_ready.
There is deliberately no script format. Reformatting safely needs a real
parser, because a wrong linter prints a bad line while a wrong formatter
corrupts source, and the tree-sitter route would mean CGO, breaking the CGO_ENABLED=0
six-target cross-build. Linting was tractable without a parser; formatting is
not.
Fixed#
script lintreports which files do not compile. Style rules read source, so they report on a file that does not parse, and zero findings there would read as clean, which is the opposite of the truth.no-else-returnreads the last statement, not the last line. Areturn success({spanning eight lines ends on}), hiding the return; and abreaknested inside a deeperifis not the branch’s own last statement, so counting it flagged anelifthat was required.- A
constbound topreload()/load()accepts CONSTANT_CASE or PascalCase. Both are idiomatic: a type when it holds a script, an asset when it holds a scene. Requiring CONSTANT_CASE flagged nine correct lines in this addon alone. engine class-infoandscript symbolsshare one introspection helper.get_script_*_list()walks the whole script chain, so a command group extendingbase_command.gdreported base_command’s ~50 helpers as its own and listed overrides once per level.runtime eval’s description said it returns a result. The code runs inside a void function, soreturn <value>is a parse error; the description now says to callemit(value).doctor’s name column derives its width from the checks, rather than a fixed pad a longer check name silently broke.
[0.6.3] - 2026-08-01#
Two addon fixes found by driving shader work against a live editor, plus shipping guidance the install docs should always have carried.
Fixed#
shader editandshader createhot-reload the material’s cached shader in place. The old path built a freshShaderand took over the file’s path, so every material kept referencing the stale copy: edits compiled but never rendered, and the next material save embedded the detached shader into the.tres, silently disconnecting the material from its file. Results now reporthot_reload(updated,blockedwhen an open Shader Editor tab re-applies its own buffer, ornot_loaded) andcompiledfrom a uniform probe, so a broken edit is visible in the reply.editor screenshotrenders a fresh frame before capturing. A minimized or unfocused editor stops redrawing, so the viewport texture could be hours old and every capture returned the same stale image while reporting success.
Added#
- “Before you ship” guidance in README, INSTALL, and the docs site.
Disable the plugin before exporting (this removes the two injected
autoloads) and exclude
addons/godot_mcp/*in every export preset. The game-side autoloads poll IPC files each frame in release builds too, andruntime evalis an arbitrary-eval surface, so the addon must never ride into a shipped game. The shipping-export guide covers verifying a build by scanning the exported.pck.
[0.6.2] - 2026-07-28#
Another CLI-only patch, from the same corner as 0.6.1. The addon is unchanged.
Fixed#
-
installno longer copies the addon’s dev context doc into your project. It copiedaddons/godot_mcp/wholesale, soaddons/godot_mcp/CLAUDE.mdcame along: repo-internal guidance with no business in a consumer project.release.ps1had always stripped it from packaged archives, so installing from a release was fine and installing from a source checkout was not. 0.6.1 made checkout installs work without flags, which widened that path rather than narrowing it. The copy now applies the same rule the archive step does, at any depth and regardless of case, and names what it skipped rather than differing from its source in silence. -
installalso warns when an earlier install left the file in your project. It only warns: the file is in your tree by then, possibly committed, and deleting things inside someone’s project is not this command’s call.
[0.6.1] - 2026-07-28#
A CLI-only patch. The addon is unchanged from 0.6.0; its version moves in step with the bundle so Project Settings and the archive name agree.
Fixed#
-
installandinstall-assetsno longer depend on the binary sitting in its bundle. Both resolved their sources next to the executable and nowhere else. That is right for the release archive, where the binary sits besideaddons/andskills/, and wrong for the flowINSTALL.mdinvited: it said to put the binary on yourPATH, and copying it out of the bundle made the guide’s own headline command fail with exit 1 and nothing installed. Candidate layouts are now walked against the executable’s directory and its parent, so the archive behaves as before and a repo checkout resolves with no flags at all. The search stays anchored to the executable rather than the working directory on purpose: a cwd walk would resolve inside the target project and offer that project’s own addon as the source, copying a directory onto itself. -
A binary separated from its bundle still cannot install an addon it does not carry, so that case still fails. It now lists every path it tried and names both remedies, instead of naming a single guess.
Added#
install --skill-from DIR, the counterpart to--from. Only the addon had a source override, so from a repo checkout the agent skill could not be installed through the CLI at all.
Changed#
INSTALL.mdsays which commands care where the binary lives (installandinstall-assets, which copy files shipped beside it) and which do not (everything else drives a running editor over a socket, so the binary works from anywhere once the addon is in the project).
[0.6.0] - 2026-07-28#
A bug-fix release, and every one of these came out of building a game with the
tool rather than reading its source. Four were found in a single session: an
initial --properties map that dropped values on the floor, sibling ordering
that had no CLI expression at all, commands that could answer from a different
project’s editor, and a bad runtime.eval that wedged the game channel until
the game was restarted. The addon also stops rewriting the host’s
project.godot twice a session, which is the change to read the upgrade note
for.
Upgrading#
Projects that keep the plugin enabled must now commit the two autoloads.
Autoload registration moved from the editor’s tree hooks to plugin enable and
disable, so nothing re-adds MCPGameInspector / MCPGameInput at editor load
any more. Anyone who deliberately kept them out of version control, relying on
the old inject-on-launch behaviour, will find runtime.* and input.* inert
after upgrading. The failure is self-describing: runtime.* names the missing
autoload in about 0.3s instead of spending its whole timeout.
Three ways to restore them, in order of preference:
- Commit the two autoloads. That is what a project on this version should do.
- Add them from the CLI:
project add-autoload --name MCPGameInspector --path res://addons/godot_mcp/services/game_inspector.gd, then the same forMCPGameInputwithservices/game_input.gd. - Untick and re-tick the plugin in Project Settings > Plugins, which runs the enable hook and injects the pair.
Do not attempt that last one through the CLI. project disable-plugin --name godot_mcp tears down the very WebSocket server the CLI is talking to: the
disable lands, the response never arrives, and nothing remains to re-enable
with. Use project add-autoload instead.
Fixed#
-
The addon no longer rewrites the host’s
project.godoton every editor launch and shutdown. Autoload injection moved from_enter_tree/_exit_treeto_enable_plugin/_disable_plugin, which is what those virtuals are for: they fire when the user actually ticks or unticks the plugin. The tree hooks fire every time the editor starts and stops, so the addon dirtied the project file twice a session, a standing chore for anyone who does not want dev-only autoloads in their commits. Worse, the shutdown save persists whatever the in-memory settings hold after every plugin has torn itself down: observed 2026-07-27 in a consumer project, one quit dropped a DIFFERENT addon’s seven committed autoloads, which would have shipped a game that could not boot. Export is unchanged, since disabling the plugin still removes them. Projects that keep the plugin enabled should now COMMIT the two autoloads, since nothing re-adds them at load. -
A missing game-side autoload is now diagnosed instantly, where it used to take a timeout or go unreported entirely. Every editor→game entry point (
runtime.*,input.*,test.*) now checks thatMCPGameInspector/MCPGameInputare present in the on-diskproject.godotbefore it writes an IPC request, reading the same fileget_game_user_dir()already parses. The launched game loads that file itself, so an autoload the editor holds only in memory does not exist in the game; the on-disk copy can lose one after plugin-enable, most often in projects that deliberately keep these dev-only autoloads out of version control and then revert or check outproject.godotmid-session. Every editor-side command keeps working in that state (project.infostill lists both autoloads), so only the game hop breaks, which made it expensive to diagnose. Previouslyruntime.*spent its full timeout and then guessed at this cause in a suggestion string, whileinput.*, being one-way with no response to wait on, reported{"sent": true}for events nothing would ever read. The new error names the missing setting and how to restore it. An unreadableproject.godotis treated as “proceed”, so the check can never block a working call. -
An initial
--propertiesmap no longer drops a value on the floor.node.add --properties '{"texture": "res://icon.png"}'created the node, lefttexturenull, and reported success. The map was coerced againsttypeof(node.get(name)), which reads the current value, and a null Resource property reads as nil, so the path was assigned as text and discarded.node.sethad already been fixed to coerce against the declared type, which is why the same value worked there and made this look like a quirk rather than a bug. Nine commands now share one strict helper: ares://oruid://path is loaded into the real resource, a coercion that cannot be made is an error naming the literal the property wanted, and a name the type does not have comes back underproperties_ignored. A malformed--propertiesvalue is an error too; it used to be iterated as a string and drop every key without a word. Affectsnode.add,authoring.ensure,batch.add_nodes,csg.add,lighting.add,lighting.add_2d, andscene3d.add_mesh. -
A script error in
runtime.evalno longer wedges the game channel. Under a--headlesseditor a GDScript parse error broke into the remote debugger, which has no UI to resume, so the game froze and every laterruntime.*command timed out with nothing surfaced anywhere. The channel simply went dead, and the usual trigger,var x := <untyped expression>, is easy to write by accident. That break only fires for a compile on the main thread, so the wrapped source is now compiled on a worker thread first. A bad eval returns the parse message with the line number in the caller’s own code, and the next command still works. -
Commands no longer answer from another project’s editor. Port discovery falls back to the default port when the project has no discovery file, so whichever godot-mcp editor happened to be running answered, and every write landed in that project with a success envelope each time. One session was spent chasing settings that would not persist; they persisted, in the other project. A port that did not come from this project’s live discovery file is now checked against the answering editor’s
project_pathbefore the call runs: a guessed port aborts, an explicit--portorGODOT_MCP_PORTwarns.godot-mcp statusreportsproject_pathandproject_match, and a mismatch now reads as no editor at all rather than “running”, so the launch policy points at opening one. The healthy case costs nothing, since a live discovery file names its own editor and skips the check.
Added#
node.movereorders siblings, andnode.addtakes--index. Sibling order is draw order in 2D, so seating a node behind existing content is routine work that had no CLI expression at all:node.moveonly reparented, and the fallback waseditor.run_scriptwith a hand-writtenmove_child.--new-parent-pathis now optional when--index(0-based, negative counts from the end) or--before/--afternames a sibling to seat against, and the two combine to reparent and position in one undoable step.node.add --indexskips the round trip entirely for a new node.
[0.5.0] - 2026-07-27#
The editor stops needing a middleman: it speaks MCP itself over streamable
HTTP, so an HTTP-capable client connects with no Go process in between. Also
the release where the new endpoint’s Origin gate was added, then found
bypassable and hardened.
Added#
-
The editor is now an MCP server itself, over streamable HTTP with zero external process. The addon hosts
POST /mcpon127.0.0.1(auto port 9100-9115;GODOT_MCP_HTTP_PORTor thegodot_mcp/network/http_portsetting pins one), so any MCP client that speaks streamable HTTP connects straight to the running editor. Tools mirrorserve: the genericgodot_runplus every documented command as a typed tool with a real schema, dispatched through the same command router, with all guards applying unchanged. Thegodot_mcp/network/http_typedsetting collapses the list togodot_runalone for tool-limited clients,godot_mcp/network/mcp_httpturns the endpoint off, and the discovery file now carrieshttp_port. -
MCP prompts in
serve. The stdio MCP server now declares the prompts capability and ships four static prompts distilled from the agent skill (discover-then-drive,spatial-placementwith an optionaltargetargument,launch-recovery, andbug-hunt), embedded in the binary and served viaprompts/list/prompts/geteven when the editor is down. The playbook now rides along as first-class MCP prompts, not just theinstructionsstring. -
scripts/test-http-mcp.ps1(task test:http): a 37-check conformance sweep of the HTTP endpoint against a live editor, covering handshake and protocol negotiation, tool-surface legality, router dispatch and error mapping, HTTP framing (411/413/405/404, keep-alive, pipelining,Connection: close), and the Origin gate including lookalike hosts and the literalnullorigin. Exit 0 means all passed. Maintainer-only:scripts/does not ship in the public mirror.
Fixed#
-
Property writes no longer silently substitute a wrong value. Three bugs in
property_parser.gd, all found by driving the typed command groups to build a scene rather than scripting it:- A numeric input with too few components zeroed the property and reported
success:
node set --property area_size --value 34.0on anAreaLight3D(whosearea_sizeis aVector2) wroteVector2(0, 0). Write paths now coerce throughPropertyParser.parse_checked(), which refuses and names the expected literal. Applies to Vector2/3/4, Rect2, Quaternion and Color. - JSON arrays were stringified before parsing, so
[28,28]becameVector2(0, 28), because_numbers()stripped aVector2(prefix but not[. Arrays and dictionaries are now read element-wise. - Resource-typed properties silently dropped
res://paths.resource create --type Sky --properties '{"sky_material":"res://…"}'returnedproperties_set:["sky_material"]while savingnull, because the value was coerced againsttypeof(current)(TYPE_NILfor an unset resource). Write paths now resolve the declared type fromget_property_list()and loadres:///uid://strings into real references, erroring on a missing file or a class mismatch. Consequence:node set --property environment --value res://env.tresand other resource-by-path assignments now work.
- A numeric input with too few components zeroed the property and reported
success:
-
node.add_resource,resource.createandresource.editare atomic on failure (nothing written) and now reportproperties_setas what actually applied, plusproperties_ignoredfor names the object does not have. -
scene open --forceswitches to the scene it reloads instead of leaving a different one active.EditorInterface.reload_scene_from_pathreloads a tab without changing which tab is current, so forcing a scene that was open but not active returnedopened:truewhile the editor stayed put, and every later command silently targeted the previously-active scene. The handler switches after reloading and reportsalready_activeso a caller can tell the difference.
Security#
-
The
Origingate accepted attacker-registrable lookalike hosts. The loopback test washost.begins_with("127."), a prefix match on the host string rather than an address check, so an origin ofhttp://127.0.0.1.evil.exampleorhttps://127.evil.compassed and had its origin echoed back inAccess-Control-Allow-Origin. Anyone can register a domain in that shape and resolve it anywhere, so a page served from one could drive the editor througheditor.run_scriptand read the replies, which is exactly what the gate was added to prevent. The host is now parsed as four numeric octets (_is_loopback_ipv4). Real loopback origins,localhost,::1, and header-less native clients are unaffected. The WebSocket transport is unchanged and was never gated. Conformance suite grew lookalike-host,null-origin, and non-127.0.0.1loopback cases (37 checks, was 34). Exposure was limited to builds frommain: the HTTP endpoint itself landed after thev0.4.0tag, so no tagged release ever carried the endpoint, let alone the flawed gate. The pending Asset Library snapshot did carry it and has been refreshed. -
The HTTP MCP endpoint now validates
Origin. Binding127.0.0.1does not keep a browser out: any web page you visited while the editor was open could POST tohttp://127.0.0.1:9100/mcpand reacheditor.run_script, i.e. arbitrary code execution, and the wildcardAccess-Control-Allow-Origineven let it read the replies. Requests with noOrigin(native MCP clients, curl, the Go CLI) and loopback origins are served as before; any other origin now gets403with the connection closed, andAccess-Control-Allow-Originechoes the allowed origin instead of*. The WebSocket transport cannot be gated this way (Godot exposes no handshake headers server-side, and browser WebSockets are exempt from CORS) and that is an accepted limitation, not a pending fix. The ports are unauthenticated by design, so treat a running editor as reachable by anything local. The docs now carry a Threat model section spelling this out.
[0.4.0] - 2026-07-22#
The Unity CLI answer, shipped in two days: full nested help with per-command
param tables served live from the addon, per-project port pinning, project-local
command registration (res://mcp_commands/), a doctor preflight, --format tsv, param docs for the complete 312-command catalog, a direct-to-player
channel that drives a standalone debug game with no editor (--game), and
typed MCP tool schemas in serve built live from those docs. Plus two real
fixes the work flushed out: plugin disable now removes the addon’s autoloads,
and navigation.add_link applies navigation_layers to 2D links.
Fixed#
navigation.add_linknow applies--navigation-layersto 2D links. The handler set it only in theNavigationLink3Dbranch; aNavigationLink2Dsilently ignored the param even though the class has the property. Found by the param-docs authoring pass (docs are extracted from handler code, so the asymmetry stood out); verified live by readingnavigation_layers: 5back off a freshly created 2D link.- Plugin disable now actually removes the addon’s autoloads. Removal used
session-only provenance tracking, but injection saves
ProjectSettings, so from the second session on the autoloads read as project-owned and disable left them behind (the one manual step in every ship-the-game flow). Removal now matches by ownership: anautoload/MCPGame*entry is removed iff its value points at the addon’s own service script; unrelated autoloads a project declares itself are untouched.
Added#
-
Typed MCP tool schemas in
serve. The stdio MCP server now exposes every documented command as a first-class tool with a real JSON schema, carrying the name (node_add), a description, and per-param types andrequired, built live from the addon’sget_command_docs()on the firsttools/listand cached, so the tool surface can never drift from what’s registered.godot_runstays as the generic escape hatch (and gains an optionalgameargument);runtime_*/input_*typed tools carry an optionalgamebool that routes the call to a standalone debug game’s direct server, the MCP half of the player channel. Editor down at connect degrades togodot_runalone, and a later successful editor call upgrades the list vianotifications/tools/list_changed.serve --typed=falseopts tool-limited clients back to the single generic tool. -
Direct-to-player channel, driving a standalone running game with no editor. With the project setting
godot_mcp/runtime/direct_serveron, a debug-build game hosts its own127.0.0.1WebSocket server (ports 9200-9215,GODOT_MCP_GAME_PORTpins) servingruntime.*/input.*with identical param shapes through the same game-side handlers the editor’s file IPC uses (shared dispatch, nothing duplicated), plus auser://discovery file with the same stale-pid contract as the editor’s. The CLI’s--gameflag routes there, resolving the game’s user-data dir fromproject.godot. Hard-gated onOS.is_debug_build(), so a release export can never host it even if the setting ships enabled. Verified live: a standalone game driven (tree/eval/get/input) with zero editor processes, clean quit removes the discovery file, and the editor-brokered channel coexists unchanged. -
Per-command param docs (the Unity
[CliArg]equivalent). A command group can exposeget_command_docs(), a per-command description plus param name / type / required / one-liner, and the router serves it live:engine.commands --group Gattaches the group’s docs (--docsfor the full catalog), and the CLI renders them:godot-mcp <group> <command> --helpprints a real param table, group listings gain one-line descriptions. Authored for the entire catalog, all 49 groups and 312 commands (plus the project-local example), with every param extracted from handler code, not memory, and group gotchas folded into the descriptions; project-localmcp_commandsfiles carry docs via the same hook (the shipped example demonstrates it). A group without docs (e.g. a third-party command file) degrades to the generic dynamic-params hint. -
godot-mcp doctor, an environment preflight: godot binary + version, project resolution, addon installed/enabled, effective port source (env / per-project pin / auto, warns when env and pin disagree), editor liveness verdict, dotnet for C#.--project DIR,--json; exit 1 only when a check fails (warns don’t fail, since doctor may run before any editor is launched). -
--format tsv, a global flag rendering a success result as tab-separated text for shell pipelines: array-of-objects → header + rows, object → key/value rows, nested values as compact JSON, tabs/newlines escaped. Defaultjson(pretty) is unchanged. -
Per-project MCP port setting. A new project setting
godot_mcp/network/port(Project → Project Settings, int, default0= auto) pins the WebSocket port per project, persisted in that project’sproject.godot, so two concurrent projects listen on distinct ports deterministically. Port precedence is nowGODOT_MCP_PORTenv > the project setting if > 0 > the auto range 9080-9095. The setting registers idempotently on plugin enable and survives disable/enable;set_initial_value(0)keeps the default out ofproject.godot, so enabling the plugin never dirties the file. The bind stays127.0.0.1-only (no host setting). -
Project-local commands, extending the MCP without forking the addon. On plugin enable the router scans
res://mcp_commands/*.gdand registers each file’sget_commands()alongside the built-ins, so custom commands appear in the CLI,godot-mcp help <group>, andengine commandsautomatically (no Go changes). A valid file instantiates to a Node exposingget_commands() -> {"group.command": Callable}(extendbase_command.gdor a plain Node); a bad file is skipped with a warning and never breaks startup, and a name colliding with a built-in is skipped, since built-ins win. Ships with a committed example,custom.ping/custom.echo. -
Nested CLI help (312 commands / 49 groups):
godot-mcp <group> --help(also-h,godot-mcp <group> help, andgodot-mcp help <group> [<command>]) lists a group’s commands, andgodot-mcp help allprints the entire catalog grouped by category; an unknown group or command lists what does exist. The catalog stays out of the Go binary: help is served live by the newengine.commands [--group G]introspection command, which returns flatmethodsplus agroupsmap of group → command names, so automation built on the JSON gets the surface by category without splitting prefixes. It falls back to theavailable_methodspayload older addons return on-32601, so it needs a running editor and never goes stale. -
shipping-export.mdcraft reference, covering the release pipeline: dev-tooling exclusion (this addon must never ship), the headless export loop, PCK encryption with keyed custom templates (SCRIPT_AES256_ENCRYPTION_KEYat compile,GODOT_SCRIPT_ENCRYPTION_KEYat export, both verified on 4.7), size-optimized template builds, and receipt-based verification (pck plaintext scans; a booting exe as the key-match proof). Distilled from a real 4.7 desktop release. -
C# project support (311 commands / 49 groups): a new
csharpgroup withcsharp.info(dotnet + .NET-editor detection),csharp.setup(scaffolds<Name>.csproj/.slnwith Godot.NET.Sdk, sets the assembly name; SDK version defaults to the engine’smajor.minor.patch[-status],--sdk-versionoverrides),csharp.build(non-blockingdotnet buildwith deduped structured diagnostics, line-level and project-level; a failed build is asuccess:falsepayload, not a transport error).script.*is now C#-aware:createwrites apublic partial classtemplate for.cspaths,validate --path X.csbuilds and filters diagnostics to that file,listsniffs the declared class/base. The Go CLI andservefloor timeouts to 5 minutes for build-backed methods. Requires a Godot .NET editor build plus the dotnet SDK; E2E-verified against a 4.7.2-rc mono editor (setup → create → build → validate → attach).
[0.3.0] - 2026-07-17#
AI-client integration (read-only godot:// MCP resources, configure, Asset
Library readiness), running-game error capture and signal awaits, git-aware
batch script validation, project bootstrapping from nothing, blend-space
authoring for anim_tree, and eight new craft references. First public release.
Added#
runtime.errors: poll runtime errors/warnings the running game captured viaOS.add_logger(aLoggersubclass registered by MCPGameInspector). Entries are structured{kind, message, code, backtrace[]}with the game-script frame inbacktrace[0],--since-seqfor incremental reads,--clearto drain. Pull-based (no doorbell) and runtime errors are unambiguously real. Live- verified capture of errors + warnings with backtraces; the game survives an error storm (bounded ring buffer, re-entrancy guard). Note: a real script error under a--headlesseditor trips the debugger break and freezes the game, while push_error/warning/shader errors and windowed/standalone runs are fine.godot://MCP resources inserve: read-only introspection surfaced as MCP resources (project/info,project/tree,scene/tree,engine/singletons,editor/errors) viaresources/list/resources/read, so a client pulls context without spending a tool turn.godot-mcp configure <client>: writes an MCP-server config pointingclaude/cursor/vscode/codexatgodot-mcp serve. Project-scoped by default (--globalfor user locations), merges without clobbering other servers,--printto emit the snippet.- Asset Library readiness:
docs/ASSET_LIBRARY.mdsubmission checklist and a correctedplugin.cfg(submission is gated on a public repo mirror). - README: head-to-head comparison table vs
hi-godot/godot-ai, with the differentiation refocused on the running game, context economy, and craft. runtime.await_signal: block until a signal fires on a node in the running game, returning its serialized arguments (fired:falseon timeout, as a success payload agents can branch on). Arity-matched one-shot connect; args captured up to 6 parameters. Live-verified: 0-arg fire, timeout, and 1-arg capture (child_entered_tree).script.validate --modified/--all: batch validation.--modifiedis git-aware (modified-vs-HEAD plus untracked.gd, deleted files skipped; handles the git repo sitting above the Godot project root),--allsweeps every project.gdoutsideaddons/. Results list failures only.godot-mcp create: local subcommand that bootstraps a new Godot 4.7 project from nothing (project.godot, placeholdericon.svg,.gitignore);--install --enablewires the addon and skill in the same step. Never overwrites an existingproject.godot.- README: “How is this different from other Godot MCPs?” positioning section (editor-native co-developer vs remote control).
- Four craft docs closing the 2026-07-16 surface-audit gaps, every API claim
introspected live and flagship recipes behavior-verified in a running game:
audio-music.md(buses, SFX variation,AudioStreamInteractivescores, sidechain ducking, spectrum),menus-settings.md(pause, the settings widget family,ConfigFilepersistence, input remapping, dialogs, 9-slice, fonts,GraphEdit),mobile-touch.md(multitouch, gestures,VirtualJoystick, safe areas), plus locomotion blend spaces and anHTTPRequestleaderboard/telemetry pattern ingame-patterns.mdand particle trail meshes +TextMeshinenvironment-art.md. audio.add_bus_effect: compressor now acceptssidechain, and five new effect types:pitchshift,hardlimiter,spectrum,record,capture.- Blend-space authoring for
anim_tree(closes the last gap from the genre-doc pass):anim_tree.create --root-type blend_space_1d|blend_space_2dandanim_tree.add_state --state-type blend_space_1d|blend_space_2dbuild the node; newanim_tree.set_blend_point/anim_tree.remove_blend_pointmanage its clips;get_structurereads blend points back. What was arun-scriptworkaround ingame-patterns.mdis now first-class, verified live, including a running-game drive ofparameters/blend_position. (307 commands total.) - Four genre craft docs closing the audit’s genre axis:
character-3d.md(FPS/third-person/platformer controllers, whose movement core was built and driven live through the CLI itself: gravity, basis-relative heading, jump all verified numerically),save-systems.md(collector pattern, format tradeoffs, atomic autosave),multiplayer-patterns.md(authority, @rpc compile-probed, spawner/synchronizer wiring),shaders-vfx.md(the 2D VFX kit plus a programmatic .gdshader compile-verification loop).
[0.2.0] - 2026-07-11#
Added#
-
TileSet authoring (
tilemapgroup):tilemap.create(TileMapLayer with a fresh TileSet,--tile-size),tilemap.add_atlas_source(texture atlas with a tile auto-created per grid cell),tilemap.add_scenes_source(PackedScenes as paintable tiles, the scene-prefab blockout workflow; painted cells carry real collision), andtilemap.set_terrain(autotile painting/erasing viaset_cells_terrain_connect).get_infonow reports terrain sets and scene sources. Live-verified, including the engine gotcha that a terrain with no island tile (terrain assigned, zero peering bits) silently places nothing for isolated cells. -
2D lighting extensions (
lightinggroup):emissive_2d(exempt a CanvasItem from darkness, unshaded/light-only, with optional additive blending),normal_map_2d(wrap a sprite’s texture in aCanvasTexturewith diffuse/normal/specular so 2D lights shade it directionally),glow_2d(enablesrendering/viewport/hdr_2d, which requires a restart, and adds an additive-glowWorldEnvironment), plusoccluder_2d --sdf-collision/--occluder-light-mask. Screenshot-verified against a live scene. -
scene2d.add_animated_sprite: AnimatedSprite2D + SpriteFrames authored from a spritesheet grid in one call:--hframes/--vframesslicing, named animations via--animationsJSON (frames/fps/loop over row-major grid indices),--autoplay [name], and the built-in empty “default” animation removed when unused. -
2D cutout rigs (
skeletongroup):create_2d(Skeleton2D + Bone2D hierarchy from a JSON bone list, rests baked, owners set so bones survive save),set_rest_2d(re-bake rests from current transforms),skin_2d(bind a Polygon2D with explicit or inverse-distance auto weights,--falloff/--max-influences);list_bonesnow handles Skeleton2D. Verified with a screenshot of a skinned polygon deforming through a bone rotation. -
Nine craft references mined from shipped/production games and verified against the live 4.7 engine:
platformer-2d.md(component actor, physics-expression AnimationTree, codeless moving platforms),topdown-2d.md(TileMapLayer stacks, gameplay-as-terrain-painting, component library, day/night clock, Resource saves),ui-polish-2d.md(design tokens from comps, drawn controls, screen-builder traps),rhythm-games.md(corrected audio clock, beatmap-format reuse, windowed judging),lighting-2d.md(the full 2D lighting stack incl. SDF and glow),event-deck-games.md(Reigns-like decision-card architecture),run-based-games.md(reactive data blackboard, wave weight-budgets, seeded self-auditing worldgen), plus major additions tonarrative-game-patterns.md(the graph-dialogue family, the manifest-driven product shell),environment-art.md(paper-diorama staging, pixel-art project setup, GPU particle attractors/colliders,AreaLight3D), andgame-patterns.md(combat-VFX shader grammar, entity-family discipline, the 4.7SkeletonModifier3Dmotion stack, positional audio, offscreen lifecycle, turn-based loops with CPU personalities). -
2D and 3D surface audits: every instantiable
Node2D(46) andNode3D(106) class on 4.7 enumerated from the live ClassDB and verified covered by a command or a craft doc (XR deliberately excluded). -
install-assetssubcommand. Copies bundled CC0 asset packs into a project:godot-mcp install-assets [--pack NAME] [--dest assets/vendor] [--list] [--force]. Each pack is copied whole (itsLicense.txt/source files kept, so attribution stays intact) into<project>/assets/vendor/<pack>/by default;--destoverrides (project-relative or absolute),--packnarrows to one,--listenumerates without a project. It is a local command and does not dial the editor. Refuses to overwrite an existing pack without--force. -
Bundled pack:
kenney_prototype_textures(Kenney Prototype Textures, CC0), grid/checker greybox skins in per-colourPNG/folders, shipped in the addon zip. -
Level-design craft reference (
skills/godot-mcp/level-design.md): blockout process/strategy and in-level spatial-communication tactics, each mapped togodot-mcpbuild recipes: Big→Medium→Small risk-ordered passes, a greybox colour language, 2.5D depth/value + grayscale test, designer-vs-stakeholder presentation stages, greybox lighting stages, and the prototype-texture workflow. -
Game feel vs juice section in
skills/godot-mcp/game-patterns.md: the two as distinct layers (control-code vs signal-fired feedback) with verified 4.7 recipes (coyote time/jump buffer/accel, squash-stretch/hit-stop/screen shake) and a reusableJuiceautoload stack. -
Environment art pass craft reference (
skills/godot-mcp/environment-art.md): the art pass after the greybox is proven, covering greybox→art handoff, PBR materials, real lighting (SDFGI/LightmapGI/VoxelGI),WorldEnvironmentpost, decals/ particles/fog, set dressing +MultiMesh, occlusion/LOD, and the “don’t lose the read” through-line. Tool boundary: meshes/textures authored externally. -
editor run_script --pathruns an editor script from a file (res://,user://, or an absolute OS path) instead of only inline--code, so large scripts aren’t shoved through the shell.codestill works;pathtakes precedence when both are given. -
scene validatescans the open scene for integrity problems that don’t surface until play: AnimationPlayer tracks whose node path doesn’t resolve (“track doesn’t lead to a Node”) and exported/stored NodePath references that point nowhere. Read-only; returns{valid, issue_count, issues:[...]}. Fills the gap where the only validation wasscript.validate(scripts) andspatial.lint(geometry), witheditor errorsas a noisy global fallback.
Fixed#
- Packed-array properties parse and serialize correctly.
property_parserhad no packed-array cases, so setting e.g. aPolygon2D.polygonfrom an array of"Vector2(x,y)"strings fell through untyped and Godot’s implicit cast silently zeroed every element. All packed types now coerce per element in both directions. scene2d.add_animated_spritestart animation is deterministic. JSON params arrive orderless from the Go CLI, so “first animation in the dict” was nondeterministic; the start/autoplay animation is now chosen explicitly (“default”, else alphabetical, else the--autoplayname).editor set_cameraaccepts theVector3(x, y, z)string form. It only took a{x, y, z}dict; theVector3(...)string every other spatial command uses hit a hardDictionarycast and made the command a silent no-op (empty result). It now parses both forms viaPropertyParser.editor run_scriptno longer floodseditor errorswith its own source. The exec audit logged the script body viaprinterr, which renders red asERROR:and was then re-collected byeditor errorsas fake errors; it now logs viaprint(still visible in Output, not flagged as an error).- Object params validate via a shared
require_dicthelper.resource.edit(properties),scene3denvironment (sky), andthemecontainer (margins) now return a clear error on a present-but-malformed value instead of a generic message or a silent skip, and tolerate a JSON object passed as a string. (None could crash likeset_camera, since each already guarded the cast, but a silently ignored param gives an agent no feedback.) animation create/removeno longer emit a strayanimation_mixer.cppengine error. Both calledget_animation_library("")on a player that may have no default library, which returns null and logs an error; they now guard withhas_animation_library("")first. (The commands worked; the log was noise that polluted a subsequentscene validate/editor errors.)node add --parentnow nests instead of silently landing at the scene root. The flag is--parent-path; the generic CLI parser has no per-command schema, so a typo’d--parentwas passed through and ignored, defaulting the node to root with no error.node.addnow acceptsparentas an alias forparent_path.node getnow reports the node’sscript.get_node_properties_dictexplicitly skipped thescriptproperty, so a node with a script attached looked script-less throughnode get(scene treealready showed it). It now reportsscriptas the resource path (ornullwhen none), matching the tree.node get --properties '[...]'actually filters now. The handler only honoured--category(a prefix filter); apropertiesname list was silently ignored and the full dump returned. It now fetches exactly the named properties (any property, not just the editor-visible set;scriptas its path) and reports unknown names undermissingrather than dropping them silently.spatial lint --check-floatingno longer drowns in false positives. It flagged everyVisualInstance3Dthat wasn’t resting on something directly below, so lights, decals, fog, GI probes, particles, sprites, MultiMesh scatter (no “rests on a surface” meaning) and all mounted/hanging/attached geometry got reported. Now it only considers solid geometry (MeshInstance3D/CSGShape3D) and treats a piece as supported if it touches/overlaps another solid (5 cm contact tolerance) or rests just above one. On a fully dressed scene this drops ~60 false positives to zero while still catching an isolated float.
[0.1.0] - 2026-06-18#
First release. A Go CLI plus a Godot 4.7 GDScript addon that drive a running editor over WebSocket, with file-IPC into the running game.
Added#
- Go CLI (
godot-mcp). Connects to the editor addon over JSON-RPC 2.0 on a WebSocket; auto-discovers the port from<project>/.godot/godot-mcp.json(or--port). Maps<group> <command> [--flags]to dotted methods. Flag values accept strings (coerced engine-side),true/false, bare booleans, and JSON for[...]/{...}; command/flag names accept kebab- or snake-case. Prints JSON-RPC errors with code, message, and anydata(suggestions, available methods) to stderr. installsubcommand. Copies the addon into<project>/addons/godot_mcpand (by default) the agent skill into<project>/.claude/skills/godot-mcp;--enableadds the plugin toproject.godot. Sources default to the release-bundle layout next to the binary.- Godot 4.7 addon (
godot_mcp). WebSocket server hosted in the editor (the addon is the server; the CLI is a short-lived client). Self-installs its game-side autoloads on enable (idempotent). All editor mutations go throughEditorUndoRedoManager. - 175 commands across 26 groups, every command verified against a live 4.7
editor/game:
project,scene,node,script,editor,runtime(20, including stateful capture/monitor/record/move/watch),engine,input,animation,anim_tree,tilemap,theme,shader,particles,scene3d,physics,navigation,audio,input_map,resource,analysis,batch,profiling,export,test,android. engineintrospection group (version,classes,class_info,search,singletons) queries the liveClassDBso an agent can discover the real 4.7 API (e.g.engine search --query offset_transform) instead of relying on training knowledge.- Runtime/game bridge. Two game-side autoloads (
MCPGameInspector,MCPGameInput) broker inspection, input simulation, frame capture, property monitoring, recording/replay, and signal watching overuser://file IPC.runtime.screenshotworks even under a headless editor (the game is a separate windowed process). - Relative node paths in
scene.tree/runtime.tree/node.getoutput, so they feed straight back as--node-path. - Agent skill (
skills/godot-mcp/SKILL.md): teaches the discover-then-drive loop, Godot node/scene composition (build with composed scenes and component nodes, not monolithic scripts), command groups, workflows, and pitfalls. - Release packaging (
scripts/release.ps1,task release): CLI binaries for windows/amd64, linux/amd64, darwin/arm64, each bundled with the addon, skill, and docs; plus standalone addon and skill zips. - Docs:
README.md,INSTALL.md,CLAUDE.md; MITLICENSE.
Notes#
- Targets the Godot 4.7 dev build (
godot-dev); not validated against 4.6. android.*requires Android platform-tools/SDK and an export preset; without them it returns clean errors.