| name | godot-devtool-mcp-server |
| description | MCP server for AI-assisted Godot 4 project inspection, editing, validation, and runtime automation via WebSocket bridge |
| triggers | ["inspect my Godot project structure","edit Godot scene nodes through MCP","install the godot-devtool plugin","run Godot project and capture runtime info","check Godot project settings and input actions","automate Godot scene validation","connect to Godot editor via WebSocket","simulate input in running Godot game"] |
Godot Devtool MCP Server
Skill by ara.so — Devtools Skills collection.
godot-devtool is an MCP (Model Context Protocol) server that enables AI-assisted inspection, editing, validation, and runtime automation for Godot 4 projects. It provides 234 tools across project management, scene/node manipulation, script handling, editor integration, and live runtime control.
Architecture Overview
MCP Client (Claude Code, Cursor, Cline, etc.)
↓ stdio
Node.js MCP Server (build/index.js)
↓
├─ Native routes (file inspection/editing)
├─ Headless Godot routes (scene/resource ops)
├─ WebSocket bridge (ws://127.0.0.1:8766)
│ ├─ Editor plugin (live scene editing, Inspector, UndoRedo)
│ └─ Runtime autoload (running game inspection, input simulation)
└─ Browser visualizer (local HTTP dashboard)
- stdio MCP server: Always runs over stdin/stdout, no exclusive port binding
- Native routes: Inspect/edit project files without opening Godot editor
- Headless routes: Call Godot CLI for scene/resource/script operations
- Editor routes: Live editing via bundled WebSocket plugin
- Runtime routes: Running-game scene tree, properties, input simulation, screenshots
- Shared bridge: Multiple MCP clients can use the same WebSocket port
Installation
1. Install the MCP Server
Extract the release build or build from source:
git clone https://github.com/wangdiandao/godot-devtool.git
cd godot-devtool
npm install
npm run build
2. Configure Your MCP Client
Claude Desktop / VS Code (JSON):
{
"mcpServers": {
"godot-devtool": {
"command": "node",
"args": ["E:/godot-devtool/build/index.js"],
"env": {
"GODOT_PATH": "D:/Program Files/Godot/Godot_v4.x.exe",
"GODOT_DEVTOOL_WS_PORT": "8766"
}
}
}
}
Codex Desktop (TOML):
[mcp_servers.godot-devtool]
command = "node"
args = ["E:/godot-devtool/build/index.js"]
env = { GODOT_PATH = "D:/Program Files/Godot/Godot_v4.x.exe", GODOT_DEVTOOL_WS_PORT = "8766" }
Environment variables:
GODOT_PATH: Path to Godot executable (optional if godot is in PATH)
GODOT_DEVTOOL_WS_PORT: WebSocket bridge port (default: 8766)
3. Install the Godot Plugin
The plugin enables live editor and runtime routes. Install via MCP tools:
plugin_install({
projectPath: "E:/my-godot-project",
overwrite: true,
websocketPort: 8766
})
Then in Godot:
- Open your project
- Go to Project → Project Settings → Plugins
- Enable godot-devtool
For runtime routes, the plugin also registers:
autoload/DevtoolRuntime = *res://addons/godot_devtool/runtime_bridge.gd
Core Concepts
Sessions and Context
Tools use projectPath, context, sessionId, and runId to identify targets:
- projectPath: Absolute path to Godot project directory
- context:
editor or runtime
- sessionId: Disambiguate multiple editor/runtime connections
- runId: Track specific game instances from
run_project
Tool Discovery
Use get_capabilities to discover tools and filter by workflow:
get_capabilities()
get_capabilities({
toolNames: ["plugin_install", "plugin_status", "scene_tree_inspect"],
includeSchemas: true
})
get_capabilities({
routeGroup: "scene",
includeSchemas: true
})
get_capabilities({
transport: "editor_ws",
includeSchemas: true
})
Workflow filters: project_setup, live_editor, runtime_test, multi_instance, release_verify
Bridge Lifecycle
- Bridge port opens on demand when editor/runtime tools are called
- Port stays open while:
run_project is active
- Editor or runtime client is connected
- Port releases after tool cleanup if no active sessions
- Use
plugin_status and plugin_cleanup_port to inspect/manage the bridge
Key Tool Categories
Project Management
get_project_info({
projectPath: "E:/my-godot-project"
})
project_get_settings({
projectPath: "E:/my-godot-project",
sections: ["application/config", "display/window"]
})
project_update_settings({
projectPath: "E:/my-godot-project",
settings: {
"application/config/name": "My Game",
"display/window/size/viewport_width": 1920
},
dryRun: true
})
project_input_action({
projectPath: "E:/my-godot-project",
action: "jump",
events: [
{ type: "InputEventKey", keycode: "KEY_SPACE" },
{ type: "InputEventJoypadButton", button_index: 0 }
]
})
run_project({
projectPath: "E:/my-godot-project",
scene: "res://levels/level_01.tscn",
debugCollisions: true,
position: [100, 100],
size: [, ]
})
({
:
})
({
: ,
:
})
Scene and Node Operations
scene_tree_inspect({
projectPath: "E:/my-godot-project",
scenePath: "res://player.tscn",
maxDepth: 3
})
editor_ws_scene_tree_inspect({
projectPath: "E:/my-godot-project",
maxDepth: 5
})
editor_ws_node_add({
projectPath: "E:/my-godot-project",
parentPath: "Player/Body",
nodeType: "Sprite2D",
nodeName: "WeaponSprite",
position: 1
})
editor_ws_node_set_property({
projectPath: "E:/my-godot-project",
nodePath: "Player/WeaponSprite",
property: "texture",
value: { type: "Resource", path: "res://sprites/sword.png" }
})
editor_ws_node_delete({
projectPath: "E:/my-godot-project",
nodePath: "Player/OldSprite",
undoable: true
})
editor_ws_scene_save({
:
})
Script Management
script_index({
projectPath: "E:/my-godot-project",
includeTests: true
})
script_read({
projectPath: "E:/my-godot-project",
scriptPath: "res://player.gd"
})
script_write({
projectPath: "E:/my-godot-project",
scriptPath: "res://enemy.gd",
content: `extends CharacterBody2D
var speed = 200.0
func _physics_process(delta):
var direction = Vector2.ZERO
if Input.is_action_pressed("move_right"):
direction.x += 1
velocity = direction * speed
move_and_slide()
`
})
script_check_syntax({
projectPath: "E:/my-godot-project",
scriptPath: "res://player.gd"
})
script_create({
projectPath: "E:/my-godot-project",
scriptPath: "res://powerup.gd",
template: "Node2D",
attachTo: "res://scenes/powerup.tscn"
})
Runtime Inspection & Automation
runtime_ws_scene_tree_inspect({
projectPath: "E:/my-godot-project",
rootPath: "/root/Game",
maxDepth: 4
})
runtime_ws_node_get_property({
projectPath: "E:/my-godot-project",
nodePath: "/root/Game/Player",
property: "position"
})
runtime_ws_node_set_property({
projectPath: "E:/my-godot-project",
nodePath: "/root/Game/Player",
property: "health",
value: { type: "int", value: 100 }
})
runtime_ws_input_action({
projectPath: "E:/my-godot-project",
action: "jump",
pressed: true,
strength: 1.0
})
runtime_ws_screenshot({
projectPath: "E:/my-godot-project",
outputPath: "E:/screenshots/test_jump.png"
})
runtime_ws_wait_for_node({
projectPath: "E:/my-godot-project",
: ,
:
})
({
: ,
: ,
: ,
: { : , : },
: ,
:
})
File & Resource Operations
file_list({
projectPath: "E:/my-godot-project",
directory: "res://sprites",
pattern: "*.png",
recursive: true
})
file_search({
projectPath: "E:/my-godot-project",
query: "extends CharacterBody2D",
paths: ["res://scripts"],
filePattern: "*.gd"
})
resource_load({
projectPath: "E:/my-godot-project",
resourcePath: "res://player.tscn",
shallow: true
})
resource_dependency_graph({
projectPath: "E:/my-godot-project",
resourcePath: "res://levels/level_01.tscn",
direction: "forward",
maxDepth: 3
})
Common Workflows
1. Project Setup & Inspection
get_godot_version()
get_capabilities({
routeGroup: "project",
includeSchemas: true
})
get_project_info({ projectPath: "E:/my-game" })
project_get_settings({
projectPath: "E:/my-game",
sections: ["application", "display", "input"]
})
plugin_install({
projectPath: "E:/my-game",
overwrite: true,
websocketPort: 8766
})
plugin_status({ projectPath: "E:/my-game" })
2. Live Scene Editing
editor_ws_selection_get({ projectPath: "E:/my-game" })
editor_ws_scene_tree_inspect({
projectPath: "E:/my-game",
maxDepth: 5
})
editor_ws_node_add({
projectPath: "E:/my-game",
parentPath: "Player",
nodeType: "Area2D",
nodeName: "HitBox"
})
editor_ws_node_set_property({
projectPath: "E:/my-game",
nodePath: "Player/HitBox",
property: "collision_layer",
value: { type: "int", value: 2 }
})
editor_ws_node_add({
projectPath: "E:/my-game",
parentPath: "Player/HitBox",
nodeType: "CollisionShape2D",
nodeName: "Shape"
})
editor_ws_scene_save({ projectPath: "E:/my-game" })
3. Runtime Testing & QA
run_project({
projectPath: "E:/my-game",
scene: "res://test_level.tscn",
debugCollisions: true
})
await sleep(2000)
runtime_ws_scene_tree_inspect({
projectPath: "E:/my-game",
rootPath: "/root"
})
runtime_ws_node_get_property({
projectPath: "E:/my-game",
nodePath: "/root/Game/Player",
property: "position"
})
runtime_ws_input_action({
projectPath: "E:/my-game",
action: "move_right",
pressed: true
})
await sleep(1000)
runtime_ws_input_action({
projectPath: "E:/my-game",
action: "move_right",
pressed: false
})
runtime_ws_qa_assert({
projectPath: "E:/my-game",
nodePath: "/root/Game/Player",
: ,
: { : , : },
: ,
:
})
({
: ,
:
})
({ : })
4. Batch Scene Validation
const scenes = await file_list({
projectPath: "E:/my-game",
directory: "res://",
pattern: "*.tscn",
recursive: true
})
for (const scene of scenes.files) {
const tree = await scene_tree_inspect({
projectPath: "E:/my-game",
scenePath: scene.path,
maxDepth: 10
})
const scriptsToCheck = tree.nodes
.filter(n => n.script)
.map(n => n.script)
for (const scriptPath of scriptsToCheck) {
await script_check_syntax({
projectPath: "E:/my-game",
scriptPath: scriptPath
})
}
}
Configuration
Environment Variables
GODOT_PATH: Path to Godot executable (required if not in PATH)
GODOT_DEVTOOL_WS_PORT: WebSocket bridge port (default: 8766)
Project Settings Integration
The MCP server reads and writes project.godot using native Godot syntax:
add_autoload({
projectPath: "E:/my-game",
name: "GameManager",
path: "res://singletons/game_manager.gd",
enabled: true
})
project_update_settings({
projectPath: "E:/my-game",
settings: {
"display/window/size/viewport_width": 1920,
"display/window/size/viewport_height": 1080,
"display/window/size/mode": 3,
"display/window/vsync/vsync_mode": 1
}
})
Input Actions
Use native Godot event syntax:
project_input_action({
projectPath: "E:/my-game",
action: "attack",
events: [
{
type: "InputEventKey",
keycode: "KEY_CTRL"
},
{
type: "InputEventMouseButton",
button_index: 1
},
{
type: "InputEventJoypadButton",
button_index: 0,
device: -1
}
],
deadzone: 0.5
})
Property Value Syntax
Use structured Variant types for node properties:
{ type: "int", value: 42 }
{ type: "float", value: 3.14 }
{ type: "bool", value: true }
{ type: "String", value: "Hello" }
{ type: "Vector2", value: [10, 20] }
{ type: "Vector3", value: [1, 2, 3] }
{ type: "Color", value: [1.0, 0.5, 0.0, 1.0] }
{ type: "Resource", path: "res://sprites/player.png" }
{ type: "NodePath", value: "../OtherNode" }
{ type: "Array", value: [1, 2, 3] }
{ type: "Dictionary", : { : , : } }
Troubleshooting
Plugin Not Connecting
plugin_status({ projectPath: "E:/my-game" })
plugin_cleanup_port({
projectPath: "E:/my-game",
websocketPort: 8766,
kill: false
})
plugin_cleanup_port({
projectPath: "E:/my-game",
websocketPort: 8766,
kill: true
})
plugin_reload({ projectPath: "E:/my-game" })
Runtime Bridge Not Available
- Ensure the plugin is installed and enabled
- Verify autoload registration:
get_autoload({
projectPath: "E:/my-game",
name: "DevtoolRuntime"
})
- Run the project from Godot (or use
run_project)
- Check bridge status:
plugin_status({ projectPath: "E:/my-game" })
Multiple Game Instances
list_run_instances({ projectPath: "E:/my-game" })
runtime_ws_scene_tree_inspect({
projectPath: "E:/my-game",
runId: "run_12345"
})
stop_run_instance({
projectPath: "E:/my-game",
runId: "run_12345"
})
Schema Too Large
Filter get_capabilities requests:
get_capabilities({ includeSchemas: true })
get_capabilities({
routeGroup: "scene",
transport: "editor_ws",
includeSchemas: true
})
get_capabilities({
toolNames: ["editor_ws_scene_tree_inspect", "editor_ws_node_add"],
includeSchemas: true
})
Editor Changes Not Saving
editor_ws_scene_save({ projectPath: "E:/my-game" })
editor_ws_selection_get({ projectPath: "E:/my-game" })
Browser Visualizer
Start a local dashboard to monitor bridge status:
browser_visualizer_start({
projectPath: "E:/my-game",
port: 3456
})
browser_visualizer_stop({
projectPath: "E:/my-game"
})
Example: Full Integration Test
const projectPath = "E:/my-game"
await plugin_install({
projectPath,
overwrite: true,
websocketPort: 8766
})
await project_input_action({
projectPath,
action: "test_jump",
events: [{ type: "InputEventKey", keycode: "KEY_J" }]
})
await scene_create({
projectPath,
scenePath: "res://test_scenes/jump_test.tscn",
rootType: "Node2D"
})
await editor_ws_node_add({
projectPath,
parentPath: ".",
nodeType: "CharacterBody2D",
nodeName: "Player"
})
await editor_ws_node_set_property({
projectPath,
nodePath: "Player",
property: "position",
value: { type: "Vector2", value: [100, 100] }
})
await editor_ws_scene_save({ projectPath })
runResult = ({
projectPath,
:
})
()
({
projectPath,
: ,
:
})
()
({
projectPath,
: ,
:
})
finalPos = ({
projectPath,
: ,
:
})
({
projectPath,
: ,
: ,
: { : , : },
: ,
:
})
({ projectPath })
Advanced: Multi-Instance Testing
const run1 = await run_project({
projectPath: "E:/my-game",
scene: "res://multiplayer_test.tscn",
position: [0, 0]
})
const run2 = await run_project({
projectPath: "E:/my-game",
scene: "res://multiplayer_test.tscn",
position: [800, 0]
})
await runtime_ws_input_action({
projectPath: "E:/my-game",
runId: run1.runId,
action: "move_right",
pressed: true
})
await runtime_ws_input_action({
projectPath: "E:/my-game",
runId: run2.runId,
action: "move_left",
pressed: true
})
const pos1 = await runtime_ws_node_get_property({
projectPath: "E:/my-game",
runId: run1.runId,
nodePath: ,
:
})
pos2 = ({
: ,
: run2.,
: ,
:
})
({ : , : run1. })
({ : , : run2. })
Important Notes:
- Always call
get_capabilities first to discover available tools for your workflow
- Use
dryRun: true for project_update_settings and other mutation operations
- Filter
get_capabilities by routeGroup, transport, or toolNames before requesting schemas
- The WebSocket bridge opens on-demand and releases when idle (unless runtime is active)
- Use
projectPath, context, sessionId, and runId to disambiguate targets
- Runtime routes require the project to be running with the autoload registered
- Editor routes require the Godot editor to be open with the plugin enabled
- For production use, test changes in a separate branch/backup before applying to main project