mcp-defold
This server drives Defold games through an in-game debug bridge, letting an MCP agent build, launch, playtest, inspect, and stop real game builds.
defold_run: build with the bridge enabled and launch the game detached;
skip_buildrelaunches fast.defold_stop: quit the running game cleanly.
defold_start: skip the front-end/menus and begin a run, passing game-defined start options.
defold_input: press input triggers in order and return the resulting screen frame.
defold_screen: read the current game screen as a composed text frame.
defold_state: fetch compact game-defined state as JSON.
defold_log: read the tail of the game's mirrored debug log.
defold_bridge: send raw bridge commands for game-specific functionality.
defold_test: run the project's headless test suite and return its output tail.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-defold build and launch my game, press space, then show screen"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-defold
MCP server for driving Defold games through an in-game debug bridge. An MCP client can build the game, launch it, start a run, press inputs, and read the screen, state, and logs. Enough to playtest.
I built it because tests and simulators kept passing on builds of my own games that were broken or unplayable. Making the agent play the real build caught what they missed.
Tools
Tool | What it does |
| Build with the bridge enabled and launch, detached. Returns when the bridge answers a ping from this server, not just when the run script says it should. The one exception is a ping that never leaves the per-port queue, which means another call is already connected there. Before launching it pings the port and refuses if anything accepts the dial: a bridged game already answers, the ping times out waiting for the slot (another call is connected), or the port is held by something that never answers, replies with something that is not JSON, replies with JSON that is not an object, or hangs up or resets before replying. One test each. Only a refused dial ( |
| Quit the running game cleanly, then wait for the port to be free and say if it is still held. |
| Skip the front-end (title/chargen) and begin a run. |
| Press input triggers in order, then return the screen. |
| The current screen as a composed text frame. |
| A PNG of the game's real window, captured without bringing it to the front (Windows, PrintWindow). For what the text frame cannot show: fonts, colours, layout, sprites. |
| Compact game-state JSON (fields are game-defined). |
| Tail of the game's mirrored debug log. |
| Raw passthrough for game-specific bridge commands. |
| Run the project's headless test suite, if configured. A non-zero exit is a tool error, not a result. |
Related MCP server: godot-test-mcp
Install
git clone https://github.com/haksanlulz/mcp-defold
cd mcp-defold
npm install
cp projects.example.json projects.json # register your gameRuns directly with tsx; no build step. PowerShell 7 (pwsh, cross-platform) runs the run and test scripts, and the unit suite spawns it too. Skip the projects.json step and the server still starts: the first tool call is what tells you to copy the example.
Two Node floors, and they are different numbers.
Runtime:
package.jsondeclaresengines: >=18and the single runtime dependency agrees, but that bound is declared and hasn't been measured. CI runs 20 and 22; nothing here has been run on 18.Development: higher, and measured.
npm installpulls vitest (4.1.11 as locked,engines: ^20.0.0 || ^22.0.0 || >=24.0.0), which pulls vite (8.2.2,^20.19.0 || >=22.12.0). Withoutengine-strictthat's a warning, and this repo ships no.npmrc, so an install on 18 is untested, which isn't the same as known broken.
Node 20 or newer to work on it. Below that, treat the runtime bound as unverified.
Use it from an MCP client
{
"mcpServers": {
"defold": {
"command": "npx",
"args": ["tsx", "/absolute/path/to/mcp-defold/index.ts"]
}
}
}Env: MCP_DEFOLD_PROJECTS overrides the registry path; MCP_DEFOLD_DEFAULT picks the default project.
A session
Four calls take an agent from nothing to a readable game. The frame below is real output from a private project, with its proper nouns replaced.
defold_run builds with the bridge compiled in, launches the engine detached, and returns once the bridge answers a ping from this server (with the queue exception noted in the table above):
== bob build (variant debug, bridge port 17890) ==
bob build OK
== launching dmengine (detached, log: .defold-tools/last-bridged-run.out) ==
pid 30032
== bridge LIVE on 127.0.0.1:17890 ==defold_start begins a run. It exists because injected input can't reach a GUI front-end: a title screen or character creator has to be skipped through the bridge, since it can't be driven. options is whatever your game's bridge accepts, here {"mode": "casual"}:
{"ok":true,"started":true}defold_input presses triggers in order and returns the screen. This is {"triggers": ["key_w", "key_w"]}, two steps north into a fountain:
== village | Day 1, Hour 08 ==
HP 17 / 17 | Stamina 25 / 25
...................>....................
.,,,,,,.,,,,,,,,,,.:.,,,,,.,,,,,,,,,,,,.
.,,,,,.,,,,,,,,,,,.:.,,,,,,,,,,,,,,,,,,.
.,,,,,,,,,,,,,,,,,.:..,,,,,,,,,,,,,,,,,.
.,,,,,,,,,,,,,,,::::.,,,,,,,,,,,,,,,,,,.
..........T^^^^^:######^^^^^..........T.
T..........^^^T^:#__p_#^^^^^..........T.
...........^^^^^:##+###^^^^^............
T.........T.....::::...........T........
...................:....................
...............v.v.:....................
...................:....................
...................:...::::::::.........
.........::::::%v%%%%%v%.#####:.........
.........:#####%%%%%%%%%.#___#:.........
.........:#_S_#%%%%%%%%%.+_H_#:.........
>:::::::::#___+%%%%O%%%%.#___#:::::::::>
..........#_N_#%%%%@%%%%v#___#..........
..........#####%%%%v%%%%.#####..........
...............%%%%%%%%%................
....................:%..................
.................%K%:%%M%%..............
.................%%%:%%%%%..............
.................#+#:#+#.#+#............
.................#_#:#_#.#_#............
.................###:###.###............
....................:...................
~~~~~~~~~~~~~~~~*=========~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~*=========~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~>~~~~~~~~~~~~~~~~~~
Contract: clear the west field — 0/3 — 14s
On the stone road | Silver 0 | Talents: none
The fountain bars the way.The last line is the game's answer to the second press. This server renders nothing. The frame is text the game's own bridge composes, so an agent reads whatever a player would. defold_screen returns the same frame without pressing anything, defold_state returns the fields the game chooses to expose as JSON, and defold_stop quits the game cleanly.
What your game has to provide
Two things, both on the game side (this server is just the client):
An in-game bridge: newline-delimited JSON over localhost TCP answering
ping/screen/state/input/start/log/quit. Protocol in PROTOCOL.md; working skeleton in example/bridge.lua. Gate it behind a debug flag so it never ships in release builds.A run script: builds with the bridge compiled in, launches the engine detached, prints
bridge LIVE on <port>.
The screen is text on purpose: compose whatever view carries your game's state (an ASCII map + HUD for a roguelike, a score readout for an arcade game).
Limitations
Screens are text frames composed by the game's bridge; there are no pixel screenshots.
defold_inputpresses and releases each trigger; nothing can be held down.Bridge input drives the in-game controller and can't reach the GUI front-end;
defold_startexists because injected input can't get past menus.Run and test scripts are invoked through
pwsh; there is no other runner.Bridge calls are serialized per port, because the bridge answers one client per frame. Parallel tool calls queue rather than overlap, and each one's timeout covers its wait as well as its socket time.
That serialization is per call, not per tool.
defold_inputmakes one call per trigger plus one for the screen, so adefold_screenfired in parallel can return a frame from the middle of the sequence. Drive one tool at a time when the order matters.
Testing
npm test # vitest: tool handlers against a fake in-process bridge (no Defold needed)
npm run smoke # spawn the real server over stdio and exercise its error paths (offline)
npm run typecheckTwo tiers, both offline. npm test is the unit tier: 80 tests in 2 files, run in about twenty-five seconds. Thirteen of them spawn a real pwsh to exercise the exit-code, marker and skip_build paths, so PowerShell 7 is a requirement of the unit tier as well as of the run scripts. One of the thirteen spends eight of those seconds waiting out a real queue timeout, which is the behaviour it is there to pin. npm run smoke spawns the real server as a child process over stdio and checks its error paths and the run-script marker contract; it needs no Defold install and no network. npm run typecheck reads index.ts, server.ts, smoke.ts and the tests, so the smoke harness is typechecked by the step that runs before it.
Counts, measured 2026-10-13:
app: 979 lines (
find . -maxdepth 1 -name '*.ts' -not -name '*.test.ts' -not -name 'smoke.ts' | xargs wc -l; index.ts + server.ts). smoke.ts is another 298 lines of test harness.tests: 1430 lines (
find test -name '*.test.ts' | xargs wc -l), 74it()blocks.
What they cover, by layer. The TCP bridge layer: timeout, unreachable port, non-JSON reply, a reply that parses into something other than an object (null, a number, a string, an array), a 70 KB reply split across chunks, a bridge that hangs up before replying (what a game quitting mid-request produces), a bridge that resets after accepting (its message must not send the caller to defold_run, which would refuse), a frame field that is not a string, five concurrent calls against a bridge that serves one at a time, a queued call's deadline, that a call which gives up while queued still frees the slot behind it, and which cause and which figure a timeout message carries in each of the two cases (queued, and dialed after a wait; the second is also measured as elapsed time, so the remaining-budget arithmetic can't be dropped without a test noticing). The registry loader: missing file, missing fields, non-string testScript, duplicate ports. The marker scan: each of its three verdicts, the precedence between them, and both of the paths that reach defold_run (failure marker, and an exit with no marker at all). The tool handlers: every tool driven through a real MCP client against a fake bridge that speaks the wire protocol, including input ordering, the failing-trigger message, argument validation on every argument the tools take (triggers, options, args and cmd, the wrong-typed tail and skip_build that are rejected rather than coerced, and project, where both a non-string and an empty string are errors rather than a silent fall-back to the default), a project name that is an Object.prototype key, the refuse-to-double-launch preflight in seven cases (the ping answered, a ping that never leaves the queue, and a port that accepts the dial and then never answers, sends non-JSON, sends JSON that is not an object, hangs up, or resets), the post-marker confirmation in all three of its outcomes (a bridge that answers, a port with nothing on it, and a ping still queued behind another call), the missing-root check, defold_log's tail default, defold_run's skip_build, defold_bridge's frame-or-JSON branch, and MCP_DEFOLD_DEFAULT project selection. defold_test's exit-code handling runs real pwsh scripts. A source-level guard pins the import surface to the stdio transport and the runtime dependency list to one entry.
Mutation probes, both re-run against the 80-test suite on 2026-10-13. Swapping the spread order in defold_start, so a caller's options.cmd overrides the fixed start command, turned exactly one test red: defold_start options cannot override the start command, with the other 79 green. Inverting the precedence in markerVerdict, so a failure marker outranks a success marker, turned exactly one test red: lets success outrank a failure printed earlier in the run, with the other 79 green. The source was restored after each run. Every fix in this suite's history carries its own probe in the commit that added it. These two are re-measured here because a green count is only true of the suite it was counted on.
Assertion policy: assert behavior and payloads, never that a function was called. A grep for toHaveBeenCalled finds zero sites in this suite; nothing was pruned.
AI assistance
This project was built with AI assistance (Claude). Correctness was established by the test suite (tool handlers driven end-to-end through a real MCP client against a fake in-process bridge that speaks the actual wire protocol, plus failure-path tests for the TCP layer), an offline smoke run that spawns the real server over stdio and checks its error paths (npm run smoke), and playtesting my own games with it. I review the code and am responsible for it.
License
MIT © Abishai James. Defold is a trademark of the Defold Foundation; this project is unofficial.
Available Tools
9 toolsdefold_bridgeA
Raw bridge command passthrough for anything the named tools don't cover. cmd = the bridge command string; args = an object merged into the request. Returns the frame (if any) or the JSON response.
Inputs: cmd (required), args, project.
defold_inputA
Press one or more input triggers in the running game, in order, then return the screen. Trigger ids are the game's action ids from its input bindings (e.g. key_w, key_e, key_space). NOTE: bridge input drives the in-game controller, not the GUI front-end; use defold_start to get past menus first.
Inputs: triggers (required), project.
defold_logB
Tail of the running game's mirrored debug log.
Inputs: tail, project.
defold_runA
Build the target project with the debug bridge enabled and launch it (windowed, detached). Returns when the bridge answers (~30-90s with a build). Pass skip_build=true to relaunch the last build fast. If a bridged game is already running, stop it first with defold_stop.
Inputs: project, skip_build.
defold_screenA
The running game's current screen as a composed text frame: whatever view the game's bridge composes (an ASCII map + HUD for a glyph game, a score readout for an arcade game).
Inputs: project.
defold_startA
Begin a run, skipping the game's front-end (title menu / character creation) via the bridge start command, which GUI input can't reach. options is passed through to the game's bridge; what it accepts is per-game (a new-game seed, a character build, a scenario id).
Inputs: options, project.
defold_stateB
Compact state of the running game as JSON. Fields are game-defined (position, health, score, flags, whatever the bridge reports).
Inputs: project.
defold_stopB
Quit the running bridged game cleanly (sends the bridge quit command).
Inputs: project.
defold_testA
Run the project's headless test suite (if a testScript is registered). Returns the tail of the output when the suite passes; a non-zero exit is a tool ERROR carrying the exit code and the same tail. Widen tail when the failing assertion is further up than the default window. Can take minutes.
Inputs: tail, project.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 9 tools
Each tool targets a distinct action or output channel, and the descriptions clarify the differences between launching, starting past menus, input, and observation. The only mild ambiguity is between defold_run and defold_start, since both relate to beginning a session, but their roles are clearly separated by the front-end skip distinction.
All tools share the defold_ prefix and use lowercase snake_case, which makes the set feel uniform. However, the second part mixes imperative verbs (run, stop, start, test) with nouns (input, screen, state, log, bridge), so the pattern is not perfectly consistent.
Nine tools is well-scoped for a game debugging bridge: build/launch, control, screen/state/log introspection, raw passthrough, and test execution are all covered without unnecessary duplication or bloat.
The tool surface covers the full lifecycle of a bridged Defold session: launching, stopping, advancing past menus, sending input, reading frames/state/logs, issuing arbitrary bridge commands, and running headless tests. The raw bridge passthrough fills any long-tail gaps, so there are no obvious dead ends.
Maintenance
Related MCP Connectors
MCP server to assist with JxBrowser development.
The MCP server for Azure DevOps, bringing the power of Azure DevOps directly to your agents.
The official MCP Server for the Mux API
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceThis MCP server enables real-time debugging and inspection of running React Native apps, providing access to console logs, errors, network requests, navigation state, storage, and performance profiling.1MIT
- AlicenseNot gradedqualityDmaintenanceMCP server for automated Godot 4 game testing. Enables launching games, capturing errors, and verifying changes from Claude Code or any MCP client.1MIT
- FlicenseCqualityBmaintenanceCustom MCP server for automating Godot Engine 4.x, enabling direct scene manipulation, node inspection, GDScript injection, and runtime testing via a WebSocket bridge.63-
- AlicenseNot gradedqualityDmaintenanceMCP server that gives AI agents comprehensive control over local Defold game projects, including inspecting, editing, searching, building, and running the project.10MIT