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 tsalThis skill uses the workspace's default tool permissions.
Playwright-like interaction loop for Godot games via MCP. Launch, observe, click, inspect, and manipulate a running game through a bridge autoload.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
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.