mcp server
construct3-mcp
MCP server that lets Claude, Cursor and other AI assistants read, analyze and safely edit Construct 3 projects — editor-faithful writes with backups and validation.
Set up
Runs on your computer. Pick your agent and operating system.
Before you start
- Node.js 20 or newer
{
"mcpServers": {
"construct3-mcp": {
"command": "npx",
"args": [
"-y",
"construct3-mcp-server"
]
}
}
}same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12
Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs
README
synced Oct 17, 2026Construct3 MCP Server
An MCP server that lets Claude, Cursor and other AI assistants read, analyze and safely edit Construct 3 projects — editor-faithful writes with backups and validation. New here? Start with the User Guide.
v1.10.0 — The server follows the project on disk (no reconnect after an editor save or
git restore), drives a running game over the Chrome DevTools Protocol with 12 new runtime tools, checks event sheet edits the way the editor does and keeps layers, instance variables and hierarchy links consistent. See What's new and the CHANGELOG.
What's new in 1.10.0
- Follows the project on disk — each tool call checks
project.c3projand the files the server has cached and reads again what changed, so there is no reconnect after a save in the Construct 3 editor orgit restore. A write over an event sheet, layout, object type or family file changed since the call read it is refused, and the call's earlier writes are put back; each file the writer changes is backed up once per tool call (#51). - Drives a running game —
serve_previewserves an exported game and launches a browser on it;connect_to_game,call_bridge,wait_for_condition,subscribe_events,simulate_input,screenshot_gameand five more tools drive it over the Chrome DevTools Protocol (connecting needs Node.js 22) (#8–#13).inject_runtime_bridgenow imports the bridge from the main script, so Construct loads it (not yet checked in the real editor). - Event sheet guards —
delete_event_sheetrefuses while other sheets use its functions or global variables,move_events_between_sheetskeeps event variables in scope, andadd_event_to_sheetchecks function names as the editor does and returns the SIDs it creates (#58, #38). - Layers, instances and objects — layer renames update event sheets, instance variables follow the object and its families, hierarchy links go with their instances, family effects are checked, and
delete_objectkeeps its images as.bak.validate_projectaddsduplicate-function-name,hierarchy-linkandorphaned-image(#38). - Large and damaged projects — the UID/SID scan of files over the 10MB read limit streams them, so a 608MB layout no longer blocks new UIDs elsewhere, and the analysis tools list objects whose own file cannot be parsed in
unanalysedObjects(#59, #60). - Stricter — some calls that worked in 1.9.2 are refused now: a sheet name in another letter case, a function name the editor refuses, a copied function block, a move that takes a variable out of its scope, an instance variable that neither the object nor its families declare, and deleting an event sheet, object or family that is still used in ways 1.9.2 did not check. See Changed and Fixed in the CHANGELOG.
What's new in 1.9.2
- Safer deletes next to unparsed files —
delete_object,delete_family,delete_layout,delete_event_sheet,delete_event_from_sheet,update_object_properties,update_familyandrename_animationsearch registered files over the 10MB read limit or with invalid JSON as text and refuse withoutforceon a possible use; results list the unchecked files inunscannedFiles. - Usage analysis says "possibly used" —
find_orphaned_objects,get_object_dependenciesandanalyze_performanceno longer report objects as unused that such files may use.
What's new in 1.9.1
- Frame images stay with their frames —
add_frame_to_animationwithindexanddelete_frame_from_animationmove the image files with the frames, keep replaced or deleted images as.bak, refuse an index past the end and roll back on failure;validate_projectreports frames without an image file (frame-image). - No duplicate UIDs next to files over 10MB — such layouts and object types are scanned as text for their IDs;
validate_projectreports them asunscanned-filewithcomplete: false, andvalidisfalsewhenevercompleteis. - Function options —
add_event_to_sheettakesfunctionReturnType,functionIsAsyncandfunctionCopyPicked. - Per-project index cache — tools no longer mix up two projects opened in one process.
- User Guide — install, connect your AI tool, first session, safe editing workflow, runtime bridge.
Full list: CHANGELOG.
What's new in 1.9.0
- Editor load-time checks —
validate_projectchecks a set of rules the Construct 3 editor enforces when it opens a project, and event sheet writes that would add such an error are refused. - Behavior conditions and actions the editor reads — written with
behaviorType;fix_legacy_behavior_keysrepairs sheets from older versions (behavior-typeis still accepted as a deprecated input alias). - Find events by editor number —
locate_eventandget_eventsheet_outlineturn "sheet, event N, action M" into the event's JSON path. - Runtime traps —
find_runtime_traps, theconstruct3://docs/pitfallsresource and thedebug_stuck_gameprompt for logic that loads but hangs or fails silently. - Editor-faithful event shapes — else-if and OR blocks, events without conditions, positional function calls, script lines and the Functions object as the editor saves them;
fix_legacy_event_shapesconverts older sheets. - Safer deletes and editor name rules — deletes and removals of used behaviors, instance variables and family members are refused, ambiguous SIDs are refused (pick one with
eventPath), case-only name clashes are refused, and nested sub-layers are supported everywhere. - Accurate reports —
validate_project,find_orphaned_objects,get_asset_usageandanalyze_performanceread editor-saved projects correctly (Transitions folder, animation folders, file assets, non-world instances, sub-layers) instead of reporting false problems. - Byte-faithful writes — line endings, trailing newline, BOM and file-name case are kept; lowercase image file names and per-instance behavior entries match the editor; every write result carries an
editorNote.
Full list: CHANGELOG.
Quick Start
New to this server? Read the User Guide. It covers requirements, setup for each AI tool, a first session, the safe editing workflow (close the project in Construct 3 while the AI edits it) and runtime testing.
The server is not published on npm. Clone it and build it once (Node.js 18 or newer):
git clone https://github.com/liauw-media/construct3-mcp.git
cd construct3-mcp
npm ci # installs dependencies and builds dist/ (prepare script)
Connect it to Claude Code with absolute paths to dist/index.js and to your project folder (the folder that contains project.c3proj; .c3p files cannot be opened):
claude mcp add construct3 --scope user -- node /absolute/path/to/construct3-mcp/dist/index.js "/absolute/path/to/My Game"
claude mcp list # construct3: ... - ✔ Connected
Claude Desktop, Cursor, VS Code and others use a JSON config instead, see Usage.
Table of Contents
Why This Exists
The Problem: When you ask Claude Code to work on Construct 3 projects, it directly edits JSON files and often breaks:
- Object references and unique IDs (SIDs/UIDs)
- Event sheet dependencies and includes
- Layout and instance relationships
- Plugin and behavior configurations
- The
usedAddonsregistry
The Solution: This MCP server provides a structured, validated interface that:
- Understands Construct 3's internal file format and ID system
- Provides structured access to project data via resources and query tools
- Enables deep analysis (dependency graphs, orphan detection, performance audits)
- Safely creates, updates, and deletes project entities with automatic backup, ID generation, and validation
- Links to the official Construct 3 manual and includes a curated list of Construct 3 pitfalls
Features
Resources (Read-Only Data Access)
| Resource | Description |
|---|---|
construct3://project/info | Project metadata and basic info |
construct3://project/structure | Complete project structure overview |
construct3://project/addons | All plugins, behaviors, and effects |
construct3://objects/{name} | Specific object type details |
construct3://eventsheets/{name} | Specific event sheet details |
construct3://layouts/{name} | Specific layout details |
construct3://docs/index | Index of documentation categories and popular topics |
construct3://docs/manual/{topic} | Link to the construct.net manual page for a topic (the page text is not fetched) |
construct3://docs/pitfalls | Curated Construct 3 pitfalls (signals, scripts, picking, expressions), each tagged with its source |
Query Tools (Read-Only)
| Tool | Description |
|---|---|
list_objects | List all object types with optional name filtering |
list_eventsheets | List all event sheets |
list_layouts | List all layouts |
list_families | List all object families |
list_timelines | List all timelines (root and subfolders); transitions are listed separately |
list_addons | List addons in usedAddons, optionally filtered by type |
get_object_details | Get detailed info about a specific object |
get_eventsheet_details | Get detailed info about an event sheet |
get_layout_details | Get detailed info about a layout |
get_timeline_details | Get a timeline's full JSON (tracks and settings) |
search_objects | Search objects by name pattern |
get_project_summary | Get comprehensive project summary |
Analysis Tools
| Tool | Description |
|---|---|
get_eventsheet_flow | Event sheet include hierarchy and layout bindings (Mermaid or JSON) |
get_function_map | Function definitions and call sites across event sheets (Call function actions, Functions.Name(...) expression calls, function map registrations) |
get_object_dependencies | Where objects are used (event sheets, layouts including sub-layers, families); objects that files it could not parse possibly use are marked (possiblyReferencedIn, unscannedFiles), and objects whose own object type file it could not parse are marked unanalysed (listed in unanalysedObjects when no use was found) |
find_orphaned_objects | Find objects not used by any event (including object parameters, expressions, script actions and Create object (by name) with a literal name) or layout (including sub-layers, non-world instances and object properties of other instances); objects that files it could not parse possibly use are listed as possiblyUsed instead, and objects whose own file it could not parse as unanalysedObjects |
get_asset_usage | Track sound, image, font, video and project file usage (used, unused or not analysed) |
analyze_performance | Heuristic performance audit with categorized issues |
validate_project | Integrity checks: missing files, required fields, duplicate SIDs/UIDs, repeated layer names, function names shared by several function blocks, broken references and includes, behaviors and instance variables that events use but their object lacks, missing addons, legacy "behavior-type" keys, layout instances without behavior entries, hierarchy links to missing instances or one-sided ones, event shapes older versions wrote that the editor never writes, scripts in the one-string shape of older Construct 3 releases, orphaned and backup files, image files named after no object type, plus the rules the C3 editor enforces at load (trigger and else placement, expression syntax limited to unterminated string literals and backslashes outside them, empty expressions, duplicate names/SIDs, family plugins). Rules verified only in part are reported as warnings (details in API.md). valid means no errors and every registered file was checked; complete (and so valid) is false when a registered file exists but was not checked (over the 10MB read limit, invalid JSON, unreadable; listed in unscannedFiles) |
get_group_settings | Event group settings (isActiveOnStart, disabled) across sheets, filterable by sheet and active state |
locate_event | Map an editor event number ("es_game, event 72, action 1") to its JSON path, sid, content and neighbouring events |
get_eventsheet_outline | Readable, paged event sheet outline with editor event numbers (IF/DO/CALL/SCRIPT/GROUP/FUNCTION/VAR) |
find_runtime_traps | Runtime traps: Wait for signal tags nothing signals, waits that start after a call already raised their tag, unused/dynamic signal tags, scripts using function parameters without localVars |
Mutation Tools (Safe Write Operations)
Objects and families
| Tool | Description |
|---|---|
create_object | Create a new object type (Sprite, Text, TiledBg, global plugins, etc.); refuses names that clash with an object type, a family, System or the Functions object, ignoring case |
update_object_properties | Add/remove instance variables and behaviors, change global status; removing one that events still use is refused, listing the uses, unless forced |
delete_object | Delete an object; refused while anything uses it (events, instances on any layer or sub-layer, families), listing where, unless forced; its image files are kept as .bak |
create_family | Create a family; refuses name clashes and members of mixed plugins (load-time checked) |
update_family | Add/remove members and shared instance variables; refuses member changes that mix plugins (load-time checked), and removing an instance variable or member through which events still use the family's instance variables, behaviors or effects, unless forced |
delete_family | Delete a family; refused while events or object properties name it or events use its instance variables, behaviors or effects through a member, listing where, unless forced |
Event sheets
| Tool | Description |
|---|---|
create_event_sheet | Create a new event sheet with optional includes; refuses names that differ from an existing sheet only in case |
add_event_to_sheet | Add a group, function, variable, include, or comment (with colours) to a sheet (load-time checked); functions take the editor's return type, Asynchronous and Copy picked options; the names of a new (global) variable, of a function and of its parameters are checked like in the editor (no second function with the same name, ignoring case); returns the new SIDs (generatedSid, functionParameterSids), eventPath and backupFile |
add_event_block | Add a block event with conditions + actions (gameplay logic), written in the editor's own shapes: sub-events (also without conditions), else/else-if blocks, OR blocks, function calls, script actions, comment rows; refuses writes that break the checked editor load-time rules (unterminated string literals and backslashes outside them, empty expressions, trigger placement) and warns where Else cannot stand (after a triggered event) |
update_event_block | Update an existing block: modify/add/remove actions and conditions, edit comment rows (text and colours) in place, make it an else or OR block (load-time checked) |
update_event_block_action | Replace the parameters of one action in a block (by block SID and action index; function call arguments as an array; load-time checked) |
update_event_variable | Rename a variable or change its type, initial value, static or constant flag; a new name is checked like in the editor |
move_events_between_sheets | Copy or move top-level events between sheets by SID (optionally into a group); load-time checked, so copying an event that breaks a load-time rule is refused; a copy or move that would clash event variable names (e.g. a copied global variable) is refused, and so is (without force) a move that takes a used variable out of scope, e.g. a global variable moved into a group |
delete_event_from_sheet | Delete an event from a sheet by SID or include name (dry-run, force); refuses while functions or event variables it removes are still referenced elsewhere (by calls, function maps, System variable ACEs or by name in expressions); warns about an else block the delete leaves behind |
remove_event_from_sheet | Remove an include from a sheet by included sheet name |
delete_event_sheet | Delete an event sheet (with reference checking and optional force): includes, layout bindings, and uses in other sheets of the functions and global variables it defines |
fix_legacy_behavior_keys | Rename legacy "behavior-type" keys (written by older versions) to "behaviorType" in all event sheets, checking each name against the object's behaviors (dry-run by default) |
fix_legacy_event_shapes | Convert event shapes written by older versions into the editor's own (block isElse to a System else condition, condition isOr to isOrBlock, old-shape function calls to positional arguments, one-string scripts, as older Construct 3 releases also saved them, to lines) where the result is unambiguous; reports the rest and which conversions can change how an event runs (dry-run by default) |
Event SIDs are not always unique in editor-saved sheets. The tools that find an event by SID refuse a SID shared by several events in the sheet and list the candidates; pass eventPath (the JSON path that locate_event returns, e.g. events[3].children[1]) to pick one. move_events_between_sheets keeps SIDs and warns when a copy leaves such a shared SID in the target sheet. See API.md.
Layouts, layers and instances
| Tool | Description |
|---|---|
create_layout | Create a new layout with configurable layers; refuses names that differ from an existing layout only in case |
update_layout | Update layout event sheet binding and dimensions |
delete_layout | Delete a layout (blocks startup layout, checks references) |
add_layer | Add a layer (position, visibility, transparency, parallax, blend mode); refuses a name any layer or sub-layer of the layout uses, ignoring case |
update_layer | Rename a layer or sub-layer or change visibility, interactivity, parallax, blend mode, scale rate, Z elevation; refuses a new name used by another layer or sub-layer, ignoring case; a rename points the event parameters that name the layer at the new name (unless another layout has a layer of that name) and lists other strings that may name it |
delete_layer | Delete a layer or sub-layer with its sub-layers (never the last top-level one; blocked while they hold instances unless forced); hierarchy links of other instances to the deleted ones are removed |
add_instance_to_layout | Place an object instance on a layout layer or sub-layer with full property control; writes a value for every instance variable of the object and its families and refuses names they do not have |
update_instance | Update a placed instance by UID on any layer or sub-layer (position, size, angle, color, visibility, tags, instance variables of its object and families) |
delete_instance_from_layout | Remove a placed instance by UID (layers, sub-layers and non-world instances); its hierarchy children stay without a parent, and its parent no longer lists it |
Sprite animations
| Tool | Description |
|---|---|
add_animation_to_sprite | Add a new animation to a Sprite object |
update_animation_properties | Update animation speed, looping, ping-pong, repeat count |
rename_animation | Rename an animation with its frame image files and the layout instances starting with it |
delete_animation | Delete an animation (never the last one) |
add_frame_to_animation | Add a blank frame (placeholder PNG) at an index; later frame images move up with their frames |
update_frame | Update a frame's duration, size or origin |
delete_frame_from_animation | Delete a frame by index (never the last one); its image is kept as .bak, later frame images move down |
replace_sprite_image | Replace a frame's image with base64 PNG data |
Timelines
| Tool | Description |
|---|---|
create_timeline | Create a timeline (duration, loop, ping-pong, repeat count, start-on-layout); refuses a case variant of a timeline in the same folder |
update_timeline | Update timeline settings or enable/disable it |
delete_timeline | Delete a timeline (backs up exactly the file it deletes; errors and leaves project.c3proj unchanged when the file is missing) |
Project and addons
| Tool | Description |
|---|---|
update_project_metadata | Update project name, version, author, or description |
register_addon | Add a plugin, behavior or effect to usedAddons |
unregister_addon | Remove an addon from usedAddons (built-ins need force) |
Runtime Tools (Live Game Control)
| Tool | Description |
|---|---|
inject_runtime_bridge | Add the bridge script that exposes the running game as globalThis.__c3bridge, imported by the main script (or as the main script of a project without one) so Construct loads it |
remove_runtime_bridge | Remove the bridge script, its registration and every line that only imports it (the one it added, or one typed by hand); refuses, changing nothing, while a script uses the bridge otherwise |
get_bridge_commands | List the commands call_bridge runs (callFunction, getGlobalVar, getObjectState, layerToCssPx, the subscription commands, ...) |
connect_to_game | Connect to the game's browser tab over the Chrome DevTools Protocol; finds the tab whose bridge is ready, on the page or in the runtime's worker (pageUrl or urlContains name it; several ready tabs are refused, not guessed), and brings it to the front |
disconnect_from_game | Close a connection |
call_bridge | Run a bridge command and return its result; a command that times out is withdrawn |
wait_for_condition | Wait until a global variable, object property or layout matches (page expressions only with C3MCP_ALLOW_EVAL=1) |
subscribe_events / read_events / unsubscribe_events | Buffer global-variable changes, layout changes and custom events (__c3bridge.emit) between polls |
simulate_input | Click, move, touch (tap, long press, swipe), press keys and type text, in viewport, canvas or layout coordinates |
get_canvas_size | The game canvas's position and size, for input coordinates |
screenshot_game | Save the page or the canvas as PNG or JPEG, to a new file outside the project (overwrite: true to replace one) |
serve_preview | Serve an exported game folder on 127.0.0.1 and optionally launch Chrome or Edge on it with its own debugging port and profile |
stop_preview | Stop preview servers and the browsers they launched |
generate_bridge_eval_script | Return the browser-console lines that submit a bridge command and read its result, plus a Python snippet that only prints those lines (nothing connects to the browser) |
export_for_preview | Pre-flight check: where the runtime runs (Use worker), bridge injection unless injectBridge: false, and the next steps |
clone_project | Deep-copy the project with optional bridge injection |
pack_project | Pack the project folder into a .c3p file that Construct 3 can open (injects the bridge into the project first unless injectBridge: false, and says so with a warning) |
The runtime tools test a running game from the AI tool: inject the bridge, preview the game in a browser started with --remote-debugging-port or export it and serve_preview the folder, then connect_to_game and drive it with call_bridge, wait_for_condition, simulate_input and the event tools. The connection tools need Node.js 22 or later. A game with Use worker on works too: the bridge is reached in the runtime's worker. See the User Guide.
inject_runtime_bridge (then reload the project in Construct 3, export it)
serve_preview { folder, launchBrowser: true, headless: true }
connect_to_game { cdpEndpoint: <browser.pageEndpoint> }
call_bridge { connectionId, command: "callFunction", args: { name: "StartGame" } }
wait_for_condition { connectionId, condition: { type: "globalVar", name: "GameState", operator: "eq", value: "READY" } }
simulate_input { connectionId, action: { type: "click", x: 540, y: 720 }, coordinateSpace: "canvas" }
stop_preview {}
The bridge can also be used from the browser console: globalThis.__c3bridge.submit("getGlobalVar", { name: "Score" }) returns an id, and getResult(id) returns { ok: true, value } once, after the next tick (null while pending).
Remove the bridge before exporting the game for players: while it runs, anyone with the browser console can change variables and call functions. remove_runtime_bridge deletes the script file, its entry in project.c3proj and the import line in your main script, the marked one inject_runtime_bridge added as well as one you typed by hand for v1.9.2. See the User Guide.
Prompts (Workflow Templates)
| Prompt | Purpose |
|---|---|
analyze_project | Analyze project structure and organization |
find_object_usage | Find where a specific object is used |
explain_eventsheet | Explain how an event sheet works |
review_game_logic | Review overall game logic architecture |
document_object | Generate documentation for an object |
optimize_project | Get optimization suggestions |
debug_stuck_game | Diagnose soft-locks and silently dead features (runs find_runtime_traps, uses the pitfalls doc) |
Safety Model
Mutation tools follow a strict safety protocol (exceptions below):
- Validation — Names checked for reserved words, path traversal, format. Plugin/behavior IDs validated against
usedAddons. - Backup — JSON files are backed up to
<filename>.bakbefore modification, once per tool call: a call that writes a file twice (create_objectregistering a new plugin and then the object inproject.c3proj) keeps the state from before the call. - ID Generation — SIDs (15-digit random), UIDs (sequential), and imageSpriteIds (7-digit) are collision-checked against the entire project. Layouts and object types over the 10MB read limit or with invalid JSON are scanned as text for their UIDs and SIDs; when a registered layout or object type exists but cannot be read at all, a new UID is refused instead of guessed. The project is scanned on first use and again after a change on disk the server did not make; the server's own writes add their IDs without a new scan, and an ID once handed out is never handed out again.
- Write — JSON is pre-validated (round-trip test, size limit), then written to a temp file and renamed into place. Files keep their text style (see below).
- Verify — Files are read back, compared with what was written, and re-parsed to confirm integrity.
- Cache Invalidation — All reader caches and indexes are cleared so subsequent reads see fresh data.
Changes made outside the server are picked up. Every tool call, resource read and prompt starts from the project as it is on disk: the server checks project.c3proj, and the files its caches hold the first time the call uses them, by modification time, size and file id, and reads again what changed, for example after git restore or a save in the Construct 3 editor. There is no need to reconnect. A write never replaces a change it did not read: when an event sheet, layout, object type or family file changed on disk after the tool call read it, the write is refused with a message that names the file, and the file keeps that change; run the tool again. A call refused this way after it had written other files puts those back first, so it changes nothing. The server's own updates of project.c3proj (registering or removing an entity, addon auto-registration, project metadata) read the file right before they write it and keep a change made there; the timeline, addon and runtime-bridge tools refuse a project.c3proj that changed during the call, before they write anything (the runtime-bridge tools also right before they write project.c3proj, putting back what they wrote before). Of two parallel tool calls that change the same event sheet, layout, object type or family file, the second one is refused the same way; the animation tools and the project.c3proj updates above run one after the other. Changes that keep a file's size, modification time and file id (possible on file systems with coarse timestamps) are not seen. The check costs one file status per file the server has read when a call uses cached data: about 100 ms on a project of 3,000 files.
Steps 2, 4 and 5 apply in full to writes that go through the project writer: objects, families, event sheets, layouts, animations, project metadata and addon auto-registration. The other write paths do less:
register_addonandunregister_addonreplaceproject.c3projthrough a temp file, with no.bakbackup and no read-back check.- The timeline tools back up the timeline file and
project.c3projand write through a temp file, but do not read the result back. - The runtime tools (
inject_runtime_bridge,remove_runtime_bridge, andexport_for_preview/pack_projectwhen they inject the bridge) writeproject.c3proj(only when the registration changes), the bridge script and the one import line in the main script in place, with no backup or read-back check.remove_runtime_bridgealso takes out bridge import lines typed by hand, and refuses before writing anything while a script uses the bridge in a way it cannot take out, or a script it has to check cannot be read. When one of their writes fails, both put back the files they wrote before. - PNG images are written without a backup, except that the frame tools keep a file they would replace or remove as
.bak(see Frame images move with their frames below), anddelete_objectkeeps the image files of the object it deletes as.bak.
The runtime connection tools write nothing to the project and keep to this machine:
serve_previewlistens on 127.0.0.1 only, refuses a port another program answers on, answers only requests addressed to 127.0.0.1, localhost or [::1] (DNS rebinding), and serves only files whose real path lies in the exported folder. The interface, the browser (CHROME_PATHor the usual install locations) and its debugging port are not tool parameters. The browser gets a fresh profile and picks its own debugging port, and stopping ends only that browser and removes its profile.connect_to_gamereaches browsers on this machine only, including the page endpoints their debugging port lists, unless the server runs withC3MCP_ALLOW_REMOTE_CDP=1;wait_for_conditionevaluates JavaScript in the page only withC3MCP_ALLOW_EVAL=1. Both are settings of whoever starts the server, not tool parameters, so a prompt that steers the tool calls cannot lift them. When several tabs have a ready bridge,connect_to_gamerefuses instead of picking one, so another site that defines a bridge of its own cannot take the connection (and with it the typed input). Everything else goes through the bridge's fixed commands, which still run game functions and set variables in the connected game.screenshot_gamewrites one image file you name: an absolute path outside the project folder, with the extension of its format, and an existing file only withoverwrite: true.
Close and reopen the project in Construct 3 before saving there. The editor keeps an open project in memory, so saving from a session that was opened before these edits can overwrite them. Its Project Bar reload (F9) re-reads script files only, not event sheets, layouts or project.c3proj. Every response that reports a completed write carries this reminder as editorNote. Error responses do not, even when a multi-step tool (e.g. create_object) failed after an earlier step had already written.
Additional safeguards:
- Reference checking —
delete_object,delete_family,delete_event_sheet, anddelete_layoutscan for references before deleting;delete_event_from_sheetanddelete_event_sheetcheck for calls and uses of the functions and event variables they remove (for a deleted sheet: its functions and global variables, used from other sheets);update_object_propertiesandupdate_familycheck the events before removing an instance variable, a behavior or a family member. Registered files these checks cannot parse (over the 10MB read limit, not valid JSON) are searched as text for the names they look for: a match is a possible use (the name may be in another string), and it refuses withoutforcejust like a use, as does such a file that cannot be read at all. The response lists these files inunscannedFiles; without a match the tool goes ahead and warns that the file was only searched as text.delete_object,delete_familyanddelete_event_sheetalso refuse when the object type's, family's or event sheet's own file could not be parsed, andrename_animationrefuses when such a layout possibly has instances that start with the animation.delete_event_sheet,delete_event_from_sheetandmove_events_between_sheetswalk the events of all event sheets for the uses of the functions and event variables they delete or move and stop after 100,000 events (sub-events included); in a project that large the uses beyond are unknown, so they refuse withoutforcethen as well. - Addon auto-registration — When creating objects with new plugins or adding behaviors, known Scirra addons are automatically registered in
usedAddons. Unknown/third-party addons are blocked with an error unlessregister_addonadded them tousedAddonsfirst (it does not check the ID or install anything).update_object_propertieschecks all behaviors it adds before it registers the first, so an unknown one leavesusedAddonsunchanged. - Global plugin protection — Singleglobal-inst objects (Audio, AJAX, etc.) cannot be placed on layouts.
- Plugin-specific defaults — Instances are created with correct default properties for each plugin type (Sprite, Text, TiledBg, NinePatch).
- Image generation — Sprite and TiledBg creation automatically generates valid placeholder PNGs, named like the editor names them:
images/<object>-<animation>-000.png, all lowercase. Batch writes roll back on failure. - Frame images move with their frames — The editor names a frame's image file after the frame's index, so
add_frame_to_animationwith anindexanddelete_frame_from_animationrename the image files of the later frames one index up or down (JPEG and other formats keep their extension). The deleted frame's image, and any unused file whose name a moved image or new placeholder needs, are kept as<file>.bakinstead of being deleted or replaced. If a write fails, the placeholder is removed, the object file restored and the image files renamed back; when another write replaced the object file in the meantime, its content is kept and the image files are made to match it. The animation tools run one at a time, so parallel calls on one Sprite cannot move each other's images or write its object without each other's frames.validate_projectreports frames without an image file (frame-image) and lists the.bakfiles inimages/(backup-file). - Layout instance sync — Like the editor, every layout instance carries an entry for each behavior of its object type and of the families it belongs to, with the built-in behaviors' default property values, and a value for each of their instance variables.
add_instance_to_layoutwrites these entries and values; adding or removing a behavior or instance variable (update_object_properties,update_family) or changing family membership (update_family,delete_family) updates the existing instances, and a family's effect entries go with the family.add_instance_to_layoutandupdate_instancerefuse values for instance variables the object and its families do not have. - Hierarchy links follow deletions — Hierarchy (scene graph) links name UIDs, and a new instance can get the UID of a deleted one.
delete_instance_from_layoutanddelete_layertherefore remove the links to the instances they delete (children stay in the layout without a parent), new UIDs are above every UID a link names, andvalidate_projectreports links that point at no instance or only one way (hierarchy-link). - Names compared like the editor — Create and rename tools refuse a name that differs from an existing one only in case where Construct 3 compares names ignoring case: event sheets and layouts (project-wide), object types and families, the layers of one layout (sub-layers included; the editor cannot load a layout with two such layers), the animations of one sprite (in any animation folder), and sibling project-bar folders. Timeline names are compared exactly, as the editor does, but a case variant of a timeline in the same folder is refused because both would share one file on Windows and macOS. Tools that check an entity together with the rest of the project take its name only as registered, letter case included:
update_object_properties,update_family, and the event sheet tools whose checks compare a sheet with the other sheets (add_event_to_sheet,delete_event_sheet,delete_event_from_sheet,update_event_variable,move_events_between_sheets). - Event variable names checked like the editor —
add_event_to_sheetandupdate_event_variablerefuse the event variable and function parameter names the editor's variable and parameter dialogs refuse: a name that matches, ignoring case, an event variable or function parameter in its scope (for a global variable, any in the project; for a local one or a parameter, the globals, the variables and parameters of its enclosing events and those below its parent event or function), the name of a System expression (e.g.time,random), and names with whitespace, punctuation such as-.:, a leading underscore or only digits. Names of object types and families are allowed, as in the editor.move_events_between_sheetsrefuses a copy or move that would create such a clash, e.g. a copy of a global variable (the editor renames a pasted variable instead). Function names must differ, ignoring case, from every other function in the project and from System expression names; a function with a return type also follows the character rules above.move_events_between_sheetsrefuses to copy a function block, andvalidate_projectreports names that several function blocks share (duplicate-function-name). - No overwrite on create — Create tools refuse to write an entity JSON file (object type, family, event sheet, layout) or a timeline file where one already exists, also one whose name differs only in case (an unregistered file, or one registered under another spelling). Nothing is backed up or replaced. Placeholder PNGs are not covered:
create_objectandadd_animation_to_spritewrite them over an image file of the same name inimages/, e.g. one left behind by a deleted animation (delete_objectkeeps the images of the object it deletes as.bak);add_frame_to_animationrenames such a file to<file>.bakfirst. - File names kept — Rewriting an existing file keeps its name on disk exactly, including case (e.g.
Layout1.jsonregistered aslayout1); the.bakbackup takes the same name. - Text style preserved — JSON is written the way Construct 3 saves it (tab indent). A file that already exists keeps its own line endings (e.g. CRLF from a git
core.autocrlfcheckout), exact trailing whitespace and BOM. A new file followsproject.c3proj, then the first JSON file with line breaks in its target folder, then Construct 3's own style (LF, no trailing newline, no BOM). For files in Construct 3's tab layout, diffs show only the lines that changed; files indented another way (e.g. with spaces) are re-indented with tabs in full.
Documentation
Detailed documentation is available in the /docs folder:
- User Guide - Install, connect your AI tool, first session, safe editing workflow, runtime testing
- Architecture - System design, components, and data flow
- API Reference - Complete reference for all resources, tools, and prompts
- Examples - Usage examples and workflows
- Development Guide - Contributing, adding tools, C3 format notes
- Troubleshooting - Common issues and solutions
Installation
Prerequisites
- Node.js >= 18.0.0
- npm or yarn
- A Construct 3 project saved in folder format (.c3proj, not .c3p)
Install Dependencies
The package is not on npm. Clone the repository and install:
git clone https://github.com/liauw-media/construct3-mcp.git
cd construct3-mcp
npm ci
npm install works too. Do not install with --omit=dev or --production: the build step needs the TypeScript dev dependency and fails without it.
Build
npm ci and npm install already build the server (the prepare script runs npm run build), which compiles TypeScript to JavaScript in the dist/ folder. Run the build yourself only after changing the source:
npm run build
Usage
Step-by-step instructions for each client are in the User Guide.
The server finds its project in this order: the first argument after dist/index.js (a project folder or the .c3proj file), the C3_PROJECT_PATH environment variable, the working directory it is started in. A folder must contain the .c3proj file directly; subfolders and parent folders are not searched. Most clients do not start the server in your project folder, so pass the project path.
MCP config (Claude Desktop, Antigravity, Windsurf and a hand-written Claude Code .mcp.json; Cursor and VS Code need "type": "stdio", and VS Code uses a different top-level key, see below):
{
"mcpServers": {
"construct3": {
"command": "node",
"args": ["/absolute/path/to/construct3-mcp/dist/index.js", "/absolute/path/to/your-project"]
}
}
}
On Windows, write paths in JSON with forward slashes ("C:/Games/My Game") or doubled backslashes ("C:\\Games\\My Game"); single backslashes make the file invalid JSON. Each path is one array element, also when it contains spaces. Leave out the project path only if the client starts the server in the folder that contains the .c3proj.
With Claude Code
Add the server with the CLI:
claude mcp add construct3 --scope user -- node /absolute/path/to/construct3-mcp/dist/index.js "/absolute/path/to/your-project"
claude mcp list
claude mcp list should show construct3: ... - ✔ Connected. Inside a session, /mcp shows the status and can reconnect the server.
--scope userstores the server in~/.claude.jsonfor every folder. Without--scope(local scope) it only applies whenclaudeis started in the folder where you ran the command; if that folder is in a git repository, it applies anywhere in that repository (a secondclaude mcp add construct3for another game in the same repository fails withalready exists in local config; see the User Guide for several games).--scope projectwrites a.mcp.jsoninto the current folder.- A
.mcp.jsonin the project folder (the JSON above) also works, but Claude Code does not start it until you approve it:claude mcp listshows⏸ Pending approvaluntil you startclaudein that folder, trust the folder and approve the server. - Claude Code does not read
~/.claude/mcp.json. - Without a project path, the server only starts when Claude Code is started in the folder that directly contains the
.c3proj. Started anywhere else,claude mcp listshows✘ Failed to connect.
Windows shell pitfalls (PowerShell and --, Git Bash and backslashes) and tool permissions: User Guide.
With Claude Desktop
Add the config to your Claude Desktop settings file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Note: Claude Desktop doesn't change working directory per-project, so pass the project path explicitly in args. Quit Claude Desktop completely and start it again after editing the file; closing the window is not enough.
With Cursor
Add the config to .cursor/mcp.json in your project root (project-specific) or ~/.cursor/mcp.json (global). Cursor's documentation lists "type": "stdio" as a required field of each server entry. In .cursor/mcp.json, "${workspaceFolder}" can stand for the project path:
{
"mcpServers": {
"construct3": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/construct3-mcp/dist/index.js", "${workspaceFolder}"]
}
}
}
- Restart Cursor after adding or modifying the config
- The Construct 3 tools appear in Cursor's AI agent
With VS Code
VS Code (GitHub Copilot agent mode) reads .vscode/mcp.json, whose top-level key is "servers", not "mcpServers", and each entry needs "type": "stdio":
{
"servers": {
"construct3": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/construct3-mcp/dist/index.js", "${workspaceFolder}"]
}
}
}
With Antigravity
Add the config to Antigravity's MCP configuration:
- Via UI: Click the
...menu in the Agent panel → MCP Servers → Manage MCP Servers → View raw config - Direct edit: the current Antigravity documentation names
~/.gemini/config/mcp_config.json(global) and.agents/mcp_config.json(workspace); the UI route above opens the right file
Note: Antigravity doesn't set a working directory per-project, so pass the project path explicitly in args.
With Windsurf
Windsurf (now Devin Desktop) uses the same mcpServers format. Its Cascade agent allows at most 100 tools in total, and this server alone brings 83.
Standalone Testing
# Auto-detect .c3proj in current directory
cd /path/to/project-folder
node /path/to/construct3-mcp/dist/index.js
# Or pass explicit path
node dist/index.js /path/to/project.c3proj
node dist/index.js /path/to/project-folder
On success the server prints Construct3 MCP Server ready (on stderr) and then waits silently for an MCP client on stdin; that is not a hang. Press Ctrl+C to stop it. On a wrong path it prints Failed to start server: ... and exits with code 1.
Example Queries
Once the MCP server is running, ask Claude:
Project Analysis:
- "What objects are in my Construct 3 project?"
- "Give me an overview of the project structure"
- "What plugins and behaviors are being used?"
- "Find orphaned objects that aren't used anywhere"
- "Run a performance audit on my project"
Code Understanding:
- "Explain how the MainSheet event sheet works"
- "Show me the event sheet include hierarchy"
- "Map all the functions in the project"
- "What objects depend on the Player?"
Safe Modifications:
- "Create a new Sprite object called Enemy"
- "Add a health variable to the Player object"
- "Create an event sheet for the menu logic"
- "Add a new layout called LevelSelect with two layers"
- "Place a Player instance at position 100, 200 on the Game layout"
Documentation:
- "Which Construct 3 pitfalls should I know about?" (reads
construct3://docs/pitfalls) - "Give me the manual link for the Sprite plugin" (the documentation resources return construct.net links, not the page text)
Development
Project Structure
construct3-mcp/
├── src/
│ ├── index.ts # Entry point: project path, server setup, registration, shutdown
│ ├── construct3/
│ │ ├── project-reader.ts # Project file reads, caches, checks for changes on disk
│ │ ├── project-writer.ts # Safe writes (backup/validate/write/verify), project.c3proj updates, undo of a tool call's writes
│ │ ├── id-generator.ts # SID/UID/imageSpriteId generation with collision avoidance
│ │ ├── disk-state.ts # File states on disk, tool call scope, stale-write error
│ │ ├── templates.ts # Entity templates and known addon maps
│ │ ├── event-shapes.ts # The event shapes the editor writes (else, OR, calls, scripts)
│ │ ├── instance-behaviors.ts # Behavior entries on layout instances
│ │ ├── instance-variables.ts # Instance variable values and effect entries on layout instances
│ │ ├── hierarchy.ts # Hierarchy (scene graph) links between layout instances
│ │ ├── layer-references.ts # Event sheet strings that name a layer (update_layer rename)
│ │ ├── object-images.ts # An object type's image files in images/ (kept as .bak on delete)
│ │ ├── animation-rename.ts # Frame image files and layout instances a rename_animation changes, frame image moves on frame insert/delete
│ │ ├── json-format.ts # On-disk text style (line endings, trailing newline, BOM)
│ │ ├── layers.ts # Layer trees: every layer and sub-layer, their instances, layer names
│ │ ├── atomic-write.ts # Temp-file-and-rename writes that keep file names on disk
│ │ ├── names.ts # Case-insensitive name and folder comparison
│ │ ├── event-variable-names.ts # Editor name rules for event variables, function parameters and function names
│ │ ├── path-utils.ts # Path resolution inside the project folder
│ │ ├── png-generator.ts # Zero-dep placeholder PNGs and image file names
│ │ ├── raw-text-search.ts # Streamed whole-word text search and UID/SID scan in files the reader skips
│ │ ├── timeline-folders.ts # The editor's Transitions folder in the timelines container
│ │ ├── types.ts # TypeScript type definitions
│ │ └── analyzers/
│ │ ├── index-builder.ts # Cross-reference index (cached per reader)
│ │ ├── event-flow.ts # Event sheet include hierarchy and layout bindings, function map
│ │ ├── object-deps.ts # Object dependencies, orphaned and unanalysed objects
│ │ ├── asset-usage.ts # Asset usage tracking
│ │ ├── animations.ts # Sprite animation trees (items + subfolders)
│ │ ├── event-outline.ts # Editor event numbers, event sheet outline
│ │ ├── performance.ts # Performance heuristics
│ │ ├── integrity.ts # Project integrity checks (validate_project)
│ │ ├── load-rules.ts # Editor load-time rules (validate_project, pre-write checks)
│ │ ├── legacy-behavior-keys.ts # Legacy "behavior-type" key scan and repair
│ │ ├── legacy-event-shapes.ts # Legacy isElse/isOr/function call/script shape scan and repair
│ │ ├── delete-references.ts # Function and variable names an event or sheet delete would leave dangling
│ │ ├── unscanned-uses.ts # Possible uses in registered files the bulk reads skipped
│ │ ├── behavior-refs.ts # Behavior name checks against objects and families
│ │ ├── effect-uses.ts # Conditions and actions that name an effect of their object (family effect checks)
│ │ ├── group-settings.ts # Event group settings (get_group_settings)
│ │ ├── runtime-traps.ts # Signal pairing and order, script/parameter traps
│ │ └── script-scan.ts # Lightweight JS/TS scanner for script actions
│ ├── resources/
│ │ ├── project.ts # Project data resources (6)
│ │ ├── docs.ts # Documentation resources (3)
│ │ └── pitfalls.ts # Curated pitfalls text (construct3://docs/pitfalls)
│ ├── runtime/
│ │ ├── bridge.ts # Injectable runtime bridge script generator
│ │ ├── cdp-client.ts # CDP client: game connections, bridge calls, input, screenshots
│ │ ├── preview-server.ts # Loopback server for exported games, browser launch
│ │ └── zip-writer.ts # Zero-dep ZIP writer for .c3p packing
│ ├── tools/
│ │ ├── query.ts # Query tools (9)
│ │ ├── analysis.ts # Analysis tools (11)
│ │ ├── mutations.ts # Registers the domain tool modules below
│ │ ├── shared.ts # Validation, result/error helpers, editor reload note
│ │ ├── project-sync.ts # withProjectSync: each handler in a tool call scope, after a check of project.c3proj on disk
│ │ ├── object-tools.ts # Object and family tools (6)
│ │ ├── event-tools.ts # Event sheet tools (12)
│ │ ├── event-helpers.ts # Event Zod schemas, builders, validators, load-time gate
│ │ ├── layout-tools.ts # Layout, layer and instance tools (9)
│ │ ├── animation-tools.ts # Sprite animation and frame tools (8)
│ │ ├── timeline-tools.ts # Timeline tools (5)
│ │ ├── project-tools.ts # Project metadata and addon tools (4)
│ │ └── runtime-tools.ts # Runtime control tools (19)
│ └── prompts/
│ └── workflows.ts # Workflow prompts (7)
├── test/ # Vitest suites, mocks and fixtures
├── dist/ # Compiled JavaScript (generated)
├── package.json
├── tsconfig.json
├── CHANGELOG.md
└── README.md
Development Commands
# Install dependencies
npm install
# Build (compile TypeScript)
npm run build
# Watch mode (auto-rebuild on changes)
npm run dev
# Run the test suite (vitest)
npm test
# Start the server
npm start
Building from Source
git clone https://github.com/liauw-media/construct3-mcp.git
cd construct3-mcp
npm install
npm run build
Contributing
We welcome contributions! Here's how to get started:
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes
- Build and test:
npm run build && npm start - Commit your changes:
git commit -m 'Add amazing feature' - Push to your branch:
git push origin feature/amazing-feature - Open a Pull Request
Roadmap
Phase 1: Foundation ✅
- Read-only project access
- 7 resources, 9 query tools, 6 prompts
- Project structure parsing
- Official documentation access
Phase 2: Enhanced Analysis ✅
- Event sheet flow visualization (Mermaid diagrams)
- Object dependency graph
- Performance analysis tools
- Asset usage tracking
- Orphaned object detection
- Function mapping across event sheets
Phase 3: Safe Modifications ✅
- Object creation with proper SID/UID management
- Instance variable and behavior management
- Event sheet creation and event insertion
- Layout creation and instance placement
- Project metadata updates
- Automatic backup, validation, and verification
- Reference checking before deletion
- Addon auto-registration for known plugins
Phase 4: Event Blocks & Animation ✅
- Event block creation (conditions + actions) with group path targeting
- Script action support (inline JavaScript)
- Animation management (add/update animations on Sprites)
- Object class validation against project entities
Phase 5: Event & Layout Operations ✅
- Delete events from sheets by SID or include name (dry-run, force, checks for references to the functions and event variables it removes)
- Update existing event blocks (modify/add/remove conditions and actions)
- Delete layouts (with reference checking, startup layout protection)
- Update layout properties (event sheet binding, dimensions)
- Full instance property overrides (angle, color, instanceVariables, behaviors, tags, etc.)
- 278 tests, type-safe templates, domain-split tool modules
Phase 6: Runtime Control ✅
- Injectable runtime bridge (runOnStartup, command queue, tick processing)
- Bridge commands: callFunction, get/setGlobalVar, getObjectState, evaluateExpression, etc.
- Project cloning with bridge injection
- Export-for-preview pre-flight checks (worker mode, bridge registration)
- Bridge eval script generation (browser-console lines, and a Python snippet that prints them)
- Game connection over CDP (page or worker), bridge calls, conditions, event buffers, input, screenshots, preview server (issues #8 to #13)
M1 Primitive Surface ✅ (v1.8)
- Layers, instance updates and instance removal
- Families (create, update members and variables, delete)
- Animation frames (add, update, delete, replace image) and animation rename/delete
- Timelines (create, update, delete, list, details)
- Addon registry tools (
list_addons,register_addon,unregister_addon) - Project integrity validation and event group settings
-
.c3ppacking (pack_project) and an end-to-end acceptance test
Editor Fidelity ✅ (v1.9)
- Editor load-time checks in
validate_projectand before event sheet writes - Event shapes, names, image file names and instance behavior entries as the editor writes them, with repair tools for older sheets
- Event locator and outline by editor event number, runtime trap analysis and curated pitfalls
- Reference-checked deletes, ambiguous-SID protection and nested sub-layers in every layout tool
- Byte-faithful writes (line endings, BOM, file-name case)
Phase 7: Advanced Features
- Opening .c3p (zipped) projects directly
- Rename with reference updates (dry-run preview)
- Bulk operations
- Plugin development assistance
Known Limitations
- Folder Format Only: Works with .c3proj folder projects;
pack_projectcan write a .c3p, but .c3p files cannot be opened - Editor Holds the Project in Memory: Close and reopen the project in Construct 3 after MCP edits and before saving there, or the editor can overwrite them
- No Rename Refactoring: Objects, families, event sheets, layouts and timelines cannot be renamed; renaming an event variable (
update_event_variable) does not update the events that use the old name, and renaming a layer (update_layer) updates only the"layer"parameters that are exactly the quoted old name (not while another layout has a layer of that name) and lists other strings that name it in a warning (issue #38).rename_animationrenames the frame images and the layout instances' start animation, but not text in events. Renaming with reference updates is planned (Phase 7) - Runtime Testing Needs an Export or the Editor's Preview: Only the Construct editor exports or previews a project, so the runtime tools start from an exported folder (
serve_preview) or a preview in a browser with a debugging port; they drive Chromium-based browsers (Chrome, Edge) only - No ACE Validation: Event block conditions/actions are not validated against plugin schemas (the AI caller is expected to know valid ACE IDs). Only the editor load-time rules listed under
validate_projectare checked; triggers are recognised by theon-id convention, which third-party addons do not always follow, so their trigger problems are warnings only. OR blocks are created withisOrBlock
License
MIT License - see LICENSE file for details
Authors
Contributors
- Initial development and architecture
Acknowledgments
- Anthropic - For creating the Model Context Protocol
- Scirra - For Construct 3 game engine
- The MCP Community - For inspiration and examples
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Made with care for the Construct 3 community