| name | godot-interactive |
| description | Interact with a running Godot game via MCP — launch, screenshot, click, inspect scene tree, get/set properties. Powered by godot-mcp + GodotMCPBridge autoload. |
Godot Interactive Testing Skill
Playwright-like interaction loop for Godot games via MCP. Launch, observe, click, inspect, and manipulate a running game through a bridge autoload.
Overview
This skill enables LLM-driven interactive testing of Godot games through the godot-mcp MCP server and a GodotMCPBridge autoload. You can:
- Launch and stop Godot projects via MCP
- Capture viewport screenshots automatically or on-demand
- Inspect the scene tree with Control node positions and text
- Simulate clicks, key presses, and input actions
- Read and write node properties at runtime
- Implement iterative test/debug loops (observe → inspect → interact → observe)
This is particularly useful for:
- Automated UI testing
- Debugging visual issues
- Verifying game state without manual playtesting
- Creating reproducible test scenarios
- Validating UI layouts and interactions
Prerequisites
godot-mcp server:
Claude Desktop configuration:
- MCP server configured in
~/.claude/mcp.json (see Setup)
Godot project setup:
- GodotMCPBridge autoload installed
- Remote debugger enabled (automatic when running via
run_project)
Setup for New Projects
1. Copy the Bridge Autoload
Copy the bridge script into your project:
cp assets/templates/godot_mcp_bridge.gd res://scripts/autoload/godot_mcp_bridge.gd
2. Register the Autoload
Add to project.godot:
[autoload]
GodotMCPBridge="*res://scripts/autoload/godot_mcp_bridge.gd"
Or via Godot editor:
- Project → Project Settings → Autoload
- Path:
res://scripts/autoload/godot_mcp_bridge.gd
- Name:
GodotMCPBridge
- Enable: Yes
3. Create MCP Configuration
Create .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:
- macOS:
/Applications/Godot 4.5.app/Contents/MacOS/Godot
- Linux:
/usr/bin/godot or ~/godot/bin/godot
- Windows:
C:/Godot/godot.exe
Project-local godot-mcp:
If you keep godot-mcp in your project (e.g., .tools/godot-mcp):
"args": [".tools/godot-mcp/build/index.js"]
MCP Tools Reference
All tools are provided by the godot-mcp server and work through the GodotMCPBridge autoload.
run_project(projectPath)
Launch a Godot project with remote debugger enabled.
Parameters:
projectPath (string): Absolute path to project directory containing project.godot
Returns:
- Success confirmation with process ID
Example:
run_project({ projectPath: "/Users/ben/code/my-game" })
Notes:
- Automatically enables remote debugger on port 6007
- Takes 2-3 seconds for project to fully start
- Bridge begins auto-capturing screenshots every 2 seconds
- Check
get_debug_output() if game doesn't appear to start
stop_project()
Stop the currently running Godot project.
Parameters: None
Returns:
Example:
stop_project()
Notes:
- Kills the Godot process gracefully
- Cleans up debugger connection
- Safe to call even if no project is running
get_debug_output()
Retrieve stdout/stderr from the running Godot process.
Parameters: None
Returns:
- Array of log lines (most recent first)
Example:
get_debug_output()
Use cases:
- Check if game started successfully
- Debug print statements from your game
- Verify bridge initialization ("Godot MCP Bridge initialized")
- Inspect error messages
game_screenshot()
Capture the current viewport and return as base64 image.
Parameters: None
Returns:
- Base64-encoded PNG image of the game viewport
Example:
game_screenshot()
Notes:
- Captures the main viewport at full resolution
- Bridge auto-saves screenshots to
/tmp/godot_screenshot.png every 2 seconds
- This tool provides immediate capture via MCP response
- Works on stock Godot (unlike
capture_screenshot from upstream DAP)
game_scene_tree(path, depth)
Inspect the scene tree with node types, positions, and properties.
Parameters:
path (string, optional): Root path to start inspection, default: /root
depth (number, optional): Traversal depth, default: 3
Returns:
- JSON tree structure with nodes
Node properties:
name: Node name
type: Node class (Control, Button, Label, Node3D, etc.)
rect: For Control nodes: {x, y, w, h} in screen coordinates
visible: For Control nodes: visibility state
text: For Button/Label nodes: display text
children: Array of child nodes (up to depth limit)
Example:
game_scene_tree({ path: "/root/Main/UILayer", depth: 2 })
{
"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:
- Find clickable UI elements (buttons, controls)
- Verify UI layout and positioning
- Check visibility states
- Locate nodes by name/type for property access
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.
game_click(x, y, button)
Simulate a mouse click at screen coordinates.
Parameters:
x (number): Screen X coordinate
y (number): Screen Y coordinate
button (number, optional): Mouse button index, default: 1 (left click)
- 1: Left button (MOUSE_BUTTON_LEFT)
- 2: Right button (MOUSE_BUTTON_RIGHT)
- 3: Middle button (MOUSE_BUTTON_MIDDLE)
Returns:
- Success confirmation with coordinates
- Automatically captures screenshot after click
Example:
game_click({ x: 720, y: 480, button: 1 })
Notes:
- Simulates press + release with 50ms delay
- Takes a screenshot 200ms after release
- Use
game_scene_tree to find button rect coordinates
- Coordinates are in full window resolution, not SubViewport pixels
Workflow:
- Call
game_screenshot() to see the game
- Call
game_scene_tree() to find button positions
- Call
game_click() with button's center coordinates
- Call
game_screenshot() to verify result
game_key(key)
Simulate a key press using Godot key names.
Parameters:
key (string): Godot key name (e.g., "Space", "Escape", "Enter", "A")
Returns:
- Success confirmation
- Automatically captures screenshot after key press
Example:
game_key({ key: "Space" })
game_key({ key: "Escape" })
game_key({ key: "Q" })
Common key names:
- Letters: "A", "B", "C", etc.
- Numbers: "0", "1", "2", etc.
- Special: "Space", "Enter", "Escape", "Tab"
- Arrows: "Up", "Down", "Left", "Right"
- Function: "F1", "F2", etc.
Notes:
- Uses
OS.find_keycode_from_string() - must be valid Godot key name
- Simulates press + release with 50ms delay
- Takes screenshot 200ms after release
- Case-sensitive for letters (use uppercase "A", not "a")
game_action(name)
Trigger a Godot input action defined in InputMap.
Parameters:
name (string): Action name from project InputMap
Returns:
- Success confirmation
- Automatically captures screenshot after action
Example:
game_action({ name: "jump" })
game_action({ name: "ui_accept" })
game_action({ name: "shoot" })
Notes:
- Action must be defined in project's InputMap (project.godot)
- Simulates action press + release with 50ms delay
- Takes screenshot 200ms after release
- Works with multi-key bindings (triggers the action, not specific keys)
Use cases:
- Test gameplay actions without knowing exact key bindings
- Trigger complex input combinations (e.g., "dash" = Shift+W)
- Test controller/gamepad actions
game_get_property(node_path, property)
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:
- Property value (JSON-serialized)
Example:
game_get_property({ node_path: "/root/Main/Player", property: "position" })
game_get_property({ node_path: "/root/Main/UILayer/HUD", property: "visible" })
Supported types:
- Primitives: bool, int, float, String
- Vectors: Vector2, Vector2i, Vector3, Vector3i (as
{x, y, z})
- Color:
{r, g, b, a}
- Arrays/Dictionaries: recursively converted
- Objects:
"<ClassName>"
- NodePath: string representation
Use cases:
- Verify game state (player health, position, score)
- Check UI visibility and text
- Validate object properties after interactions
game_set_property(node_path, property, value)
Write a node property at runtime.
Parameters:
node_path (string): Absolute node path from /root
property (string): Property name
value (any): New value (JSON-serializable)
Returns:
- Success confirmation
- Automatically captures screenshot after change
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:
- Takes screenshot 100ms after property change
- Use JSON object syntax for Vector2/Vector3:
{x, y} or {x, y, z}
- For testing, you can force game states (show popups, move player, etc.)
Use cases:
- Force UI visibility for testing
- Teleport player to test positions
- Set health/score to test edge cases
- Trigger game states without playing through
Workflow Patterns
Pattern 1: Basic UI Test Loop
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()
-
Observe initial state:
game_screenshot()
-
Inspect scene tree to find button:
game_scene_tree({ path: "/root/Main/UILayer", depth: 3 })
-
Click button center:
game_click({ x: 760, y: 500 })
-
Verify result:
game_screenshot()
get_debug_output()
Pattern 2: Property Inspection and Validation
Verify game state after actions.
Steps:
-
Launch and perform action:
run_project({ projectPath: "/path/to/game" })
game_click({ x: 640, y: 360 })
-
Read game state:
game_get_property({ node_path: "/root/Main/Player", property: "health" })
game_get_property({ node_path: "/root/Main/Ball", property: "position" })
-
Trigger gameplay:
game_action({ name: "select_card" })
-
Verify state change:
game_get_property({ node_path: "/root/Main/Ball", property: "position" })
Pattern 3: Forcing Game States for Testing
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 })
Pattern 4: Debugging Visual Issues
Investigate rendering problems.
Steps:
-
Launch game:
run_project({ projectPath: "/path/to/game" })
-
Capture screenshot:
game_screenshot()
-
Inspect button properties:
game_get_property({ node_path: "/root/Main/UILayer/PlayButton", property: "visible" })
-
Check parent visibility:
game_get_property({ node_path: "/root/Main/UILayer", property: "visible" })
-
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()
Key Gotchas
Gotcha 1: UI Coordinates Are Full Resolution
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:
game_screenshot()
game_click({ x: 320, y: 180 })
game_scene_tree({ path: "/root/Main/UILayer" })
game_click({ x: 960, y: 560 })
Gotcha 2: Stock Godot DAP Limitation
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.
Gotcha 3: Bridge Initialization Delay
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" })
get_debug_output()
Gotcha 4: Scene Tree Depth Limit
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:
game_scene_tree({ path: "/root/Main", depth: 2 })
game_scene_tree({ path: "/root/Main/UILayer/VictoryPopup", depth: 5 })
Gotcha 5: File-Based Protocol Synchronization
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:
- MCP tool writes command to
/tmp/godot_mcp_command.json
- Bridge polls the file every frame
- Bridge executes command and deletes command file
- Bridge writes response to
/tmp/godot_mcp_response.json
- Bridge prints
MCP_RESPONSE:{...} to stdout
- MCP tool reads response and returns to caller
You don't need to do anything special - just call the tools normally.
Bridge Communication Protocol
The GodotMCPBridge autoload implements a file-based protocol for communication:
Command file: /tmp/godot_mcp_command.json
- MCP server writes JSON commands here
- Bridge polls every frame
- Bridge deletes after reading
Response file: /tmp/godot_mcp_response.json
- Bridge writes JSON responses here
- MCP server reads responses
Screenshot file: /tmp/godot_screenshot.png
- Bridge auto-saves every 2 seconds
- Bridge updates after clicks/keys (200ms delay)
- MCP tools can read this file directly
Debug output:
- Bridge prints
MCP_RESPONSE:{...} to stdout
- MCP server captures via
get_debug_output()
Command format:
{
"action": "click",
"x": 640,
"y": 360,
"button": 1
}
Response format:
{
"type": "click",
"success": true,
"x": 640,
"y": 360
}
Best Practices
1. Always Inspect Before Clicking
Use game_scene_tree to find button positions - don't guess coordinates.
game_scene_tree({ path: "/root/Main/UILayer" })
game_click({ x: rect.x + rect.w/2, y: rect.y + rect.h/2 })
game_screenshot()
game_click({ x: 640, y: 360 })
2. Check Debug Output for Errors
After interactions, check get_debug_output() for print statements and errors.
game_click({ x: 640, y: 360 })
get_debug_output()
3. Use Property Inspection for Validation
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" })
4. Force States for Faster Testing
Use game_set_property to skip to specific game states.
game_set_property({ node_path: "/root/Main", property: "current_level", value: 5 })
5. Clean Up After Tests
Always stop the project when done to free resources.
stop_project()
Common Use Cases
Use Case 1: Automated UI Regression Testing
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 })
game_get_property({ node_path: "/root/Main", property: "state" })
stop_project()
Use Case 2: Visual Debugging
Investigate why a UI element doesn't appear.
run_project({ projectPath: "/path/to/game" })
game_screenshot()
game_scene_tree({ path: "/root/Main/UILayer" })
game_get_property({ node_path: "/root/Main/UILayer/MissingButton", property: "visible" })
Use Case 3: Game State Validation
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" })
Use Case 4: Screenshot Documentation
Capture screenshots of all UI states for documentation.
run_project({ projectPath: "/path/to/game" })
game_screenshot()
game_click({ x: 640, y: 360 })
game_screenshot()
game_set_property({ node_path: "/root/Main/Popup", property: "visible", value: true })
game_screenshot()
Troubleshooting
Game Won't Start
Symptoms: run_project() succeeds but no screenshot appears.
Check:
- Verify Godot path in
.mcp.json is correct
- Check
get_debug_output() for errors
- Ensure bridge autoload is registered in
project.godot
Solution:
get_debug_output()
Click Doesn't Work
Symptoms: game_click() succeeds but button doesn't respond.
Check:
- Coordinates are correct (use
game_scene_tree)
- Button is visible (
game_get_property)
- UI is not blocked by another node
Solution:
game_scene_tree({ path: "/root/Main/UILayer" })
game_get_property({ node_path: "/root/Main/UILayer/Button", property: "visible" })
Property Not Found
Symptoms: game_get_property() returns error "Node not found".
Check:
- Node path is correct (use
game_scene_tree to find path)
- Node exists at runtime (may be dynamically created)
Solution:
game_scene_tree({ path: "/root/Main", depth: 5 })
Screenshot Is Black/Empty
Symptoms: Screenshot is solid black or shows nothing.
Check:
- Game has finished rendering (wait 2-3 seconds after launch)
- Camera is active and positioned correctly
- Scene has visible nodes
Solution:
run_project({ projectPath: "/path/to/game" })
get_debug_output()
game_screenshot()
Summary
The godot-interactive skill enables automated testing and debugging of Godot games through MCP tools:
- Launch games with
run_project() and remote debugger
- Observe state with
game_screenshot() and game_scene_tree()
- Interact with
game_click(), game_key(), game_action()
- Inspect/modify with
game_get_property() and game_set_property()
- Iterate in a test loop until behavior is verified
Key insights:
- File-based protocol is reliable and works on stock Godot
- Always use
game_scene_tree for accurate UI coordinates
- Bridge autoload handles all synchronization automatically
- Property inspection validates internal state, not just visuals
This skill transforms Godot development from manual playtesting to automated, reproducible test scenarios.