Help us improve
Share bugs, ideas, or general feedback.
From tsal
Interact with running Godot games via MCP: launch projects, capture screenshots, simulate clicks/keys, inspect/manipulate scene tree and properties. For UI testing, debugging, and state verification.
npx claudepluginhub bfollington/terma --plugin tsalHow this skill is triggered — by the user, by Claude, or both
Slash command
/tsal:godot-interactiveThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Playwright-like interaction loop for Godot games via MCP. Launch, observe, click, inspect, and manipulate a running game through a bridge autoload.
Provides persistent godot-mcp and AI Bridge workflows for Godot 4.x projects, handling .tscn/.gd/.gdshader/.tres files and live editor tasks like scene inspection, node edits, refresh/run/test loops, runtime diagnostics, hybrid validation.
Routes MCP tool discovery and dispatches to domain-specific skills for Godot 4 projects: project setup, live editor, runtime test, scene authoring, and release verification.
Provides specialized guidance for Godot Engine projects: .gd, .tscn, .tres file formats, component-based patterns, signals, resources, debugging, validation tools, templates, and CLI workflows.
Share bugs, ideas, or general feedback.
Playwright-like interaction loop for Godot games via MCP. Launch, observe, click, inspect, and manipulate a running game through a bridge autoload.
This skill enables LLM-driven interactive testing of Godot games through the godot-mcp MCP server and a GodotMCPBridge autoload. You can:
This is particularly useful for:
godot-mcp server:
~/code/godot-mcp (or configurable path)npm install && npm run buildClaude Desktop configuration:
~/.claude/mcp.json (see Setup)Godot project setup:
run_project)Copy the bridge script into your project:
cp assets/templates/godot_mcp_bridge.gd res://scripts/autoload/godot_mcp_bridge.gd
Add to project.godot:
[autoload]
GodotMCPBridge="*res://scripts/autoload/godot_mcp_bridge.gd"
Or via Godot editor:
res://scripts/autoload/godot_mcp_bridge.gdGodotMCPBridgeCreate .mcp.json at project root (use template):
cp assets/templates/mcp.json.template .mcp.json
Edit paths:
{
"mcpServers": {
"godot": {
"command": "node",
"args": ["<PATH_TO_GODOT_MCP>/build/index.js"],
"env": {
"GODOT_PATH": "<PATH_TO_GODOT_BINARY>"
}
}
}
}
Example paths:
/Applications/Godot 4.5.app/Contents/MacOS/Godot/usr/bin/godot or ~/godot/bin/godotC:/Godot/godot.exeProject-local godot-mcp:
If you keep godot-mcp in your project (e.g., .tools/godot-mcp):
"args": [".tools/godot-mcp/build/index.js"]
All tools are provided by the godot-mcp server and work through the GodotMCPBridge autoload.
Launch a Godot project with remote debugger enabled.
Parameters:
projectPath (string): Absolute path to project directory containing project.godotReturns:
Example:
run_project({ projectPath: "/Users/ben/code/my-game" })
Notes:
get_debug_output() if game doesn't appear to startStop the currently running Godot project.
Parameters: None
Returns:
Example:
stop_project()
Notes:
Retrieve stdout/stderr from the running Godot process.
Parameters: None
Returns:
Example:
get_debug_output()
// Returns: ["Frame 1234", "Button pressed", "MCP command: click", ...]
Use cases:
Capture the current viewport and return as base64 image.
Parameters: None
Returns:
Example:
game_screenshot()
// Returns: base64 PNG data
Notes:
/tmp/godot_screenshot.png every 2 secondscapture_screenshot from upstream DAP)Inspect the scene tree with node types, positions, and properties.
Parameters:
path (string, optional): Root path to start inspection, default: /rootdepth (number, optional): Traversal depth, default: 3Returns:
Node properties:
name: Node nametype: Node class (Control, Button, Label, Node3D, etc.)rect: For Control nodes: {x, y, w, h} in screen coordinatesvisible: For Control nodes: visibility statetext: For Button/Label nodes: display textchildren: Array of child nodes (up to depth limit)Example:
game_scene_tree({ path: "/root/Main/UILayer", depth: 2 })
// Returns:
{
"name": "UILayer",
"type": "CanvasLayer",
"children": [
{
"name": "VictoryPopup",
"type": "CenterContainer",
"rect": {"x": 640, "y": 360, "w": 400, "h": 200},
"visible": true,
"children": [
{
"name": "OKButton",
"type": "Button",
"rect": {"x": 720, "y": 480, "w": 80, "h": 40},
"text": "OK",
"visible": true
}
]
}
]
}
Use cases:
Key gotcha: UI coordinates are full resolution (e.g., 1920x1080), not screenshot pixel coordinates. Always use game_scene_tree to get accurate rect values for clicking.
Simulate a mouse click at screen coordinates.
Parameters:
x (number): Screen X coordinatey (number): Screen Y coordinatebutton (number, optional): Mouse button index, default: 1 (left click)
Returns:
Example:
game_click({ x: 720, y: 480, button: 1 })
// Returns: {"type": "click", "success": true, "x": 720, "y": 480}
Notes:
game_scene_tree to find button rect coordinatesWorkflow:
game_screenshot() to see the gamegame_scene_tree() to find button positionsgame_click() with button's center coordinatesgame_screenshot() to verify resultSimulate a key press using Godot key names.
Parameters:
key (string): Godot key name (e.g., "Space", "Escape", "Enter", "A")Returns:
Example:
game_key({ key: "Space" })
game_key({ key: "Escape" })
game_key({ key: "Q" })
Common key names:
Notes:
OS.find_keycode_from_string() - must be valid Godot key nameTrigger a Godot input action defined in InputMap.
Parameters:
name (string): Action name from project InputMapReturns:
Example:
game_action({ name: "jump" })
game_action({ name: "ui_accept" })
game_action({ name: "shoot" })
Notes:
Use cases:
Read a node property at runtime.
Parameters:
node_path (string): Absolute node path from /root (e.g., /root/Main/Player)property (string): Property name (e.g., position, health, visible)Returns:
Example:
game_get_property({ node_path: "/root/Main/Player", property: "position" })
// Returns: {"x": 5.2, "y": 0.0, "z": 3.8}
game_get_property({ node_path: "/root/Main/UILayer/HUD", property: "visible" })
// Returns: true
Supported types:
{x, y, z}){r, g, b, a}"<ClassName>"Use cases:
Write a node property at runtime.
Parameters:
node_path (string): Absolute node path from /rootproperty (string): Property namevalue (any): New value (JSON-serializable)Returns:
Example:
game_set_property({
node_path: "/root/Main/Player",
property: "position",
value: { x: 10, y: 0, z: 5 }
})
game_set_property({
node_path: "/root/Main/UILayer/VictoryPopup",
property: "visible",
value: true
})
Notes:
{x, y} or {x, y, z}Use cases:
Test a button click and verify the result.
Steps:
Launch the game:
run_project({ projectPath: "/Users/ben/code/my-game" })
Wait for startup (check logs):
get_debug_output()
// Look for "Godot MCP Bridge initialized"
Observe initial state:
game_screenshot()
Inspect scene tree to find button:
game_scene_tree({ path: "/root/Main/UILayer", depth: 3 })
// Note the button's rect: {x: 720, y: 480, w: 80, h: 40}
Click button center:
game_click({ x: 760, y: 500 }) // Center of button
Verify result:
game_screenshot() // See visual result
get_debug_output() // Check for print statements
Verify game state after actions.
Steps:
Launch and perform action:
run_project({ projectPath: "/path/to/game" })
game_click({ x: 640, y: 360 }) // Click play button
Read game state:
game_get_property({ node_path: "/root/Main/Player", property: "health" })
// Returns: 100
game_get_property({ node_path: "/root/Main/Ball", property: "position" })
// Returns: {"x": 0, "y": 0, "z": 0}
Trigger gameplay:
game_action({ name: "select_card" })
Verify state change:
game_get_property({ node_path: "/root/Main/Ball", property: "position" })
// Returns: {"x": 5, "y": 0, "z": 3} // Ball moved!
Set up specific scenarios without manual play.
Steps:
Launch game:
run_project({ projectPath: "/path/to/game" })
Force victory state:
game_set_property({
node_path: "/root/Main",
property: "state",
value: "VICTORY"
})
Show victory popup:
game_set_property({
node_path: "/root/Main/UILayer/VictoryPopup",
property: "visible",
value: true
})
Verify UI appears correctly:
game_screenshot()
game_scene_tree({ path: "/root/Main/UILayer/VictoryPopup" })
Test OK button:
game_click({ x: 760, y: 500 })
Investigate rendering problems.
Steps:
Launch game:
run_project({ projectPath: "/path/to/game" })
Capture screenshot:
game_screenshot()
// Observe: button is not visible
Inspect button properties:
game_get_property({ node_path: "/root/Main/UILayer/PlayButton", property: "visible" })
// Returns: false // Aha! Button is hidden
Check parent visibility:
game_get_property({ node_path: "/root/Main/UILayer", property: "visible" })
// Returns: true
Fix the bug (in code):
# In your script:
$PlayButton.visible = true # Was missing this line
Restart and verify:
stop_project()
run_project({ projectPath: "/path/to/game" })
game_screenshot()
Problem: Screenshots are 640x360 (SubViewport), but UI coordinates are 1920x1080 (window resolution).
Solution: Always use game_scene_tree to get button rect values. Do not manually calculate click coordinates from screenshot pixels.
Example:
// ❌ WRONG: Clicking screenshot pixel coordinates
game_screenshot() // 640x360 image
game_click({ x: 320, y: 180 }) // Center of screenshot = WRONG
// ✅ CORRECT: Use scene_tree rect
game_scene_tree({ path: "/root/Main/UILayer" })
// Returns: { name: "PlayButton", rect: {x: 860, y: 520, w: 200, h: 80} }
game_click({ x: 960, y: 560 }) // Center of rect = CORRECT
Problem: The upstream Godot DAP capture_screenshot tool doesn't work on stock Godot builds.
Solution: Use game_screenshot() from this skill instead. It works through the file-based bridge protocol (/tmp/godot_screenshot.png).
Why it matters: Other Godot DAP tools may advertise capture_screenshot, but it requires a custom Godot build with DAP screenshot support. The bridge's game_screenshot() always works.
Problem: Calling MCP tools immediately after run_project() may fail if bridge isn't ready.
Solution: Check get_debug_output() for "Godot MCP Bridge initialized" before calling game interaction tools.
Example:
run_project({ projectPath: "/path/to/game" })
// Wait 2-3 seconds or poll debug output
get_debug_output()
// Look for: "Godot MCP Bridge initialized — screenshots: /tmp/godot_screenshot.png..."
Problem: game_scene_tree() defaults to depth 3, may not show deeply nested nodes.
Solution: Increase depth parameter for deeper inspection, or query specific subtrees.
Example:
// Shallow inspection
game_scene_tree({ path: "/root/Main", depth: 2 })
// Deep inspection of UI subtree
game_scene_tree({ path: "/root/Main/UILayer/VictoryPopup", depth: 5 })
Problem: Bridge uses file-based communication (/tmp/godot_mcp_command.json and /tmp/godot_mcp_response.json), which is polled every frame.
Solution: MCP tools handle synchronization automatically. Response is written after command completes. No manual delays needed.
How it works:
/tmp/godot_mcp_command.json/tmp/godot_mcp_response.jsonMCP_RESPONSE:{...} to stdoutYou don't need to do anything special - just call the tools normally.
The GodotMCPBridge autoload implements a file-based protocol for communication:
Command file: /tmp/godot_mcp_command.json
Response file: /tmp/godot_mcp_response.json
Screenshot file: /tmp/godot_screenshot.png
Debug output:
MCP_RESPONSE:{...} to stdoutget_debug_output()Command format:
{
"action": "click",
"x": 640,
"y": 360,
"button": 1
}
Response format:
{
"type": "click",
"success": true,
"x": 640,
"y": 360
}
Use game_scene_tree to find button positions - don't guess coordinates.
// ✅ CORRECT workflow
game_scene_tree({ path: "/root/Main/UILayer" })
// Find button rect
game_click({ x: rect.x + rect.w/2, y: rect.y + rect.h/2 })
// ❌ WRONG workflow
game_screenshot()
// Guess coordinates from image
game_click({ x: 640, y: 360 })
After interactions, check get_debug_output() for print statements and errors.
game_click({ x: 640, y: 360 })
get_debug_output()
// Look for "Button pressed" or error messages
Don't just rely on screenshots - verify internal game state.
game_click({ x: 640, y: 360 })
game_get_property({ node_path: "/root/Main/Player", property: "health" })
// Confirm health decreased as expected
Use game_set_property to skip to specific game states.
// Skip tutorial, go straight to level 5
game_set_property({ node_path: "/root/Main", property: "current_level", value: 5 })
Always stop the project when done to free resources.
stop_project()
Test that buttons still work after code changes.
run_project({ projectPath: "/path/to/game" })
game_screenshot()
game_scene_tree({ path: "/root/Main/UILayer" })
game_click({ x: 640, y: 360 }) // Play button
game_get_property({ node_path: "/root/Main", property: "state" })
// Assert: state is "PLAYING"
stop_project()
Investigate why a UI element doesn't appear.
run_project({ projectPath: "/path/to/game" })
game_screenshot() // See the issue
game_scene_tree({ path: "/root/Main/UILayer" }) // Check hierarchy
game_get_property({ node_path: "/root/Main/UILayer/MissingButton", property: "visible" })
// Debug: visible is false!
Verify gameplay logic by checking properties.
run_project({ projectPath: "/path/to/game" })
game_action({ name: "attack" })
game_get_property({ node_path: "/root/Main/Enemy", property: "health" })
// Verify: health decreased correctly
Capture screenshots of all UI states for documentation.
run_project({ projectPath: "/path/to/game" })
game_screenshot() // Main menu
game_click({ x: 640, y: 360 }) // Start game
game_screenshot() // Gameplay
game_set_property({ node_path: "/root/Main/Popup", property: "visible", value: true })
game_screenshot() // Settings popup
Symptoms: run_project() succeeds but no screenshot appears.
Check:
.mcp.json is correctget_debug_output() for errorsproject.godotSolution:
get_debug_output()
// Look for "Godot MCP Bridge initialized"
// If missing, bridge isn't loaded
Symptoms: game_click() succeeds but button doesn't respond.
Check:
game_scene_tree)game_get_property)Solution:
game_scene_tree({ path: "/root/Main/UILayer" })
// Verify button rect
game_get_property({ node_path: "/root/Main/UILayer/Button", property: "visible" })
// Verify button is visible
Symptoms: game_get_property() returns error "Node not found".
Check:
game_scene_tree to find path)Solution:
game_scene_tree({ path: "/root/Main", depth: 5 })
// Find the correct node path
Symptoms: Screenshot is solid black or shows nothing.
Check:
Solution:
run_project({ projectPath: "/path/to/game" })
// Wait 3 seconds
get_debug_output()
// Check for "Frame N" output indicating rendering
game_screenshot()
The godot-interactive skill enables automated testing and debugging of Godot games through MCP tools:
run_project() and remote debuggergame_screenshot() and game_scene_tree()game_click(), game_key(), game_action()game_get_property() and game_set_property()Key insights:
game_scene_tree for accurate UI coordinatesThis skill transforms Godot development from manual playtesting to automated, reproducible test scenarios.