@satelliteoflove/godot-mcp 3.23.1 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -14
- package/addon/command_router.gd +0 -1
- package/addon/commands/debug_commands.gd +0 -70
- package/addon/commands/exec_commands.gd +1 -1
- package/addon/commands/input_commands.gd +1 -1
- package/addon/commands/node_commands.gd +1 -142
- package/addon/commands/project_commands.gd +1 -1
- package/addon/commands/scene_commands.gd +29 -62
- package/addon/commands/selection_commands.gd +17 -4
- package/addon/core/base_command.gd +0 -24
- package/addon/core/mcp_debugger_plugin.gd +0 -29
- package/addon/core/mcp_logger.gd +0 -26
- package/addon/core/mcp_utils.gd +2 -26
- package/addon/game_bridge/mcp_game_bridge.gd +1 -18
- package/addon/plugin.cfg +1 -1
- package/dist/__tests__/connection/wsl-diagnostic.test.d.ts +2 -0
- package/dist/__tests__/connection/wsl-diagnostic.test.d.ts.map +1 -0
- package/dist/__tests__/connection/wsl-diagnostic.test.js +36 -0
- package/dist/__tests__/connection/wsl-diagnostic.test.js.map +1 -0
- package/dist/__tests__/core/annotations.test.js +45 -11
- package/dist/__tests__/core/annotations.test.js.map +1 -1
- package/dist/__tests__/core/discriminated-union-schema.test.js +19 -22
- package/dist/__tests__/core/discriminated-union-schema.test.js.map +1 -1
- package/dist/__tests__/core/doc-examples.test.js +2 -1
- package/dist/__tests__/core/doc-examples.test.js.map +1 -1
- package/dist/__tests__/core/read-only.test.d.ts +2 -0
- package/dist/__tests__/core/read-only.test.d.ts.map +1 -0
- package/dist/__tests__/core/read-only.test.js +53 -0
- package/dist/__tests__/core/read-only.test.js.map +1 -0
- package/dist/__tests__/core/schema-fidelity.test.d.ts +2 -0
- package/dist/__tests__/core/schema-fidelity.test.d.ts.map +1 -0
- package/dist/__tests__/core/schema-fidelity.test.js +95 -0
- package/dist/__tests__/core/schema-fidelity.test.js.map +1 -0
- package/dist/__tests__/core/startup.test.d.ts +2 -0
- package/dist/__tests__/core/startup.test.d.ts.map +1 -0
- package/dist/__tests__/core/startup.test.js +58 -0
- package/dist/__tests__/core/startup.test.js.map +1 -0
- package/dist/__tests__/core/structured-output.test.js +7 -8
- package/dist/__tests__/core/structured-output.test.js.map +1 -1
- package/dist/__tests__/core/toollist-snapshot.test.d.ts +2 -0
- package/dist/__tests__/core/toollist-snapshot.test.d.ts.map +1 -0
- package/dist/__tests__/core/toollist-snapshot.test.js +34 -0
- package/dist/__tests__/core/toollist-snapshot.test.js.map +1 -0
- package/dist/__tests__/evals/score.test.d.ts +2 -0
- package/dist/__tests__/evals/score.test.d.ts.map +1 -0
- package/dist/__tests__/evals/score.test.js +68 -0
- package/dist/__tests__/evals/score.test.js.map +1 -0
- package/dist/__tests__/tools/animation.test.js +51 -20
- package/dist/__tests__/tools/animation.test.js.map +1 -1
- package/dist/__tests__/tools/docs.test.js +171 -12
- package/dist/__tests__/tools/docs.test.js.map +1 -1
- package/dist/__tests__/tools/editor.test.js +124 -81
- package/dist/__tests__/tools/editor.test.js.map +1 -1
- package/dist/__tests__/tools/input.test.js +2 -2
- package/dist/__tests__/tools/input.test.js.map +1 -1
- package/dist/__tests__/tools/node.test.js +104 -119
- package/dist/__tests__/tools/node.test.js.map +1 -1
- package/dist/__tests__/tools/project.test.js +2 -2
- package/dist/__tests__/tools/project.test.js.map +1 -1
- package/dist/__tests__/tools/scene.test.js +3 -17
- package/dist/__tests__/tools/scene.test.js.map +1 -1
- package/dist/__tests__/tools/tilemap.test.js +200 -169
- package/dist/__tests__/tools/tilemap.test.js.map +1 -1
- package/dist/__tests__/utils/logger.test.js +36 -77
- package/dist/__tests__/utils/logger.test.js.map +1 -1
- package/dist/cli.js +28 -9
- package/dist/cli.js.map +1 -1
- package/dist/connection/websocket.d.ts.map +1 -1
- package/dist/connection/websocket.js +9 -1
- package/dist/connection/websocket.js.map +1 -1
- package/dist/core/define-tool.d.ts +0 -1
- package/dist/core/define-tool.d.ts.map +1 -1
- package/dist/core/define-tool.js.map +1 -1
- package/dist/core/doc-examples.d.ts.map +1 -1
- package/dist/core/doc-examples.js +6 -1
- package/dist/core/doc-examples.js.map +1 -1
- package/dist/core/registry.d.ts +1 -15
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +5 -36
- package/dist/core/registry.js.map +1 -1
- package/dist/core/schema.d.ts +3 -1
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +164 -25
- package/dist/core/schema.js.map +1 -1
- package/dist/core/types.d.ts +0 -9
- package/dist/core/types.d.ts.map +1 -1
- package/dist/evals/score.d.ts +24 -0
- package/dist/evals/score.d.ts.map +1 -0
- package/dist/evals/score.js +37 -0
- package/dist/evals/score.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +70 -37
- package/dist/index.js.map +1 -1
- package/dist/installer/install.d.ts +0 -5
- package/dist/installer/install.d.ts.map +1 -1
- package/dist/installer/install.js +0 -13
- package/dist/installer/install.js.map +1 -1
- package/dist/tools/animation.d.ts +5 -4
- package/dist/tools/animation.d.ts.map +1 -1
- package/dist/tools/animation.js +29 -7
- package/dist/tools/animation.js.map +1 -1
- package/dist/tools/docs.d.ts +1 -8
- package/dist/tools/docs.d.ts.map +1 -1
- package/dist/tools/docs.js +6 -4
- package/dist/tools/docs.js.map +1 -1
- package/dist/tools/editor.d.ts +14 -13
- package/dist/tools/editor.d.ts.map +1 -1
- package/dist/tools/editor.js +109 -77
- package/dist/tools/editor.js.map +1 -1
- package/dist/tools/exec.js +5 -5
- package/dist/tools/exec.js.map +1 -1
- package/dist/tools/game-time.js +4 -4
- package/dist/tools/game-time.js.map +1 -1
- package/dist/tools/index.d.ts +4 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +22 -16
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/input.d.ts.map +1 -1
- package/dist/tools/input.js +10 -8
- package/dist/tools/input.js.map +1 -1
- package/dist/tools/node.d.ts +8 -26
- package/dist/tools/node.d.ts.map +1 -1
- package/dist/tools/node.js +64 -88
- package/dist/tools/node.js.map +1 -1
- package/dist/tools/profiler.js +2 -2
- package/dist/tools/profiler.js.map +1 -1
- package/dist/tools/project.d.ts.map +1 -1
- package/dist/tools/project.js +5 -5
- package/dist/tools/project.js.map +1 -1
- package/dist/tools/resource.d.ts.map +1 -1
- package/dist/tools/resource.js +2 -13
- package/dist/tools/resource.js.map +1 -1
- package/dist/tools/runtime-state.d.ts +1 -1
- package/dist/tools/runtime-state.js +1 -1
- package/dist/tools/runtime-state.js.map +1 -1
- package/dist/tools/scene.d.ts +0 -5
- package/dist/tools/scene.d.ts.map +1 -1
- package/dist/tools/scene.js +2 -16
- package/dist/tools/scene.js.map +1 -1
- package/dist/tools/scene3d.js +2 -2
- package/dist/tools/scene3d.js.map +1 -1
- package/dist/tools/tilemap.d.ts +6 -4
- package/dist/tools/tilemap.d.ts.map +1 -1
- package/dist/tools/tilemap.js +101 -65
- package/dist/tools/tilemap.js.map +1 -1
- package/dist/utils/errors.d.ts +0 -4
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/logger.d.ts +0 -10
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +12 -34
- package/dist/utils/logger.js.map +1 -1
- package/dist/{tools → utils}/project-staleness.d.ts.map +1 -1
- package/dist/{tools → utils}/project-staleness.js +1 -1
- package/dist/{tools → utils}/project-staleness.js.map +1 -1
- package/package.json +11 -5
- package/server.json +46 -0
- package/addon/commands/script_commands.gd +0 -73
- package/addon/commands/script_commands.gd.uid +0 -1
- package/dist/core/define-resource.d.ts +0 -9
- package/dist/core/define-resource.d.ts.map +0 -1
- package/dist/core/define-resource.js +0 -4
- package/dist/core/define-resource.js.map +0 -1
- package/dist/resources/index.d.ts +0 -4
- package/dist/resources/index.d.ts.map +0 -1
- package/dist/resources/index.js +0 -10
- package/dist/resources/index.js.map +0 -1
- package/dist/resources/scene.d.ts +0 -4
- package/dist/resources/scene.d.ts.map +0 -1
- package/dist/resources/scene.js +0 -32
- package/dist/resources/scene.js.map +0 -1
- package/dist/resources/script.d.ts +0 -3
- package/dist/resources/script.d.ts.map +0 -1
- package/dist/resources/script.js +0 -18
- package/dist/resources/script.js.map +0 -1
- /package/dist/{tools → utils}/project-staleness.d.ts +0 -0
package/README.md
CHANGED
|
@@ -24,12 +24,12 @@ The pieces that make that possible, and that you won't find elsewhere:
|
|
|
24
24
|
Here's what that looks like when an agent tests a boss fight:
|
|
25
25
|
|
|
26
26
|
```text
|
|
27
|
-
|
|
27
|
+
godot_editor_edit run frozen=true # boot with game time frozen at frame 0
|
|
28
28
|
godot_exec GameState.wave = 3 # set up the scenario worth testing
|
|
29
29
|
godot_game_time step_until "tree.get_nodes_in_group('boss').size() >= 1"
|
|
30
30
|
godot_runtime_state digest # exact positions and state — no pixels, no guessing
|
|
31
31
|
godot_game_time step 500ms + dodge input # play the moment that matters
|
|
32
|
-
|
|
32
|
+
godot_editor_read screenshot_game # and a screenshot when it's actually worth the tokens
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
Less copy-paste, more creating.
|
|
@@ -65,26 +65,28 @@ Open your Godot project, restart your AI assistant, and start building. If anyth
|
|
|
65
65
|
|
|
66
66
|
## What's in the box
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
21 tools, 86 actions. Full API docs in the [Tools Reference](https://github.com/satelliteoflove/godot-mcp/blob/main/docs/tools/README.md).
|
|
69
69
|
|
|
70
70
|
| Tool | What it does |
|
|
71
71
|
|------|--------------|
|
|
72
|
-
| `godot_scene` | Open
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `godot_project` | Project info and settings |
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
72
|
+
| `godot_scene` | Open and save scenes (create new scenes by writing the `.tscn` directly, then open) |
|
|
73
|
+
| `godot_node_read` / `godot_node_edit` | Inspect effective properties, the full scene tree (including instanced sub-scenes), and find nodes; update properties and reparent |
|
|
74
|
+
| `godot_editor_read` / `godot_editor_edit` | Editor state, selection, screenshots, editor error log; run/stop/restart and 2D viewport control |
|
|
75
|
+
| `godot_project` | Project info, settings, addon version skew, and stale-settings detection |
|
|
76
|
+
| `godot_animation_read` / `godot_animation_edit` | Query animations down to keyframes; author tracks and keyframes with instant editor preview |
|
|
77
|
+
| `godot_tilemap_read` / `godot_tilemap_edit` | Read and edit TileMapLayer cells (base64-encoded in `.tscn` — the bridge is the only way) |
|
|
78
|
+
| `godot_gridmap_read` / `godot_gridmap_edit` | Read and edit GridMap cells, same story |
|
|
78
79
|
| `godot_resource` | Inspect resources with type-aware output: SpriteFrames, TileSet, Materials, Textures |
|
|
79
|
-
| `godot_scene3d` | 3D transforms, bounding boxes, and visibility for spatial reasoning |
|
|
80
|
+
| `godot_scene3d` | Engine-computed 3D transforms, bounding boxes, and visibility for spatial reasoning |
|
|
80
81
|
| `godot_docs` | Fetch Godot documentation as clean markdown, version-matched to your editor |
|
|
81
82
|
| `godot_input` | Inject input into the running game: actions, joypad, raw keys, mouse-look, text |
|
|
82
83
|
| `godot_profiler` | Metric snapshots and per-frame time series with spike detection |
|
|
83
84
|
| `godot_runtime_state` | Live game state as JSON: one-shot digests, watch windows, signal timelines |
|
|
84
85
|
| `godot_game_time` | Freeze, step, and step-until on the game clock — deterministic observation |
|
|
85
86
|
| `godot_exec` | Run GDScript inside the running game for test scenario setup |
|
|
87
|
+
| `godot_validate_meshes` | Detect silently corrupt procedural mesh data that masquerades as lighting bugs |
|
|
86
88
|
|
|
87
|
-
A note on shape:
|
|
89
|
+
A note on shape: tools split along the read/write boundary, so every `godot_*_read` tool (and the other read-only tools) can be safely auto-allowed in your client's permission settings while writes stay gated. Related operations still live as actions inside one tool, so your agent's context isn't flooded with definitions it won't use. Anything an agent can do by editing project files directly — creating scenes and nodes, attaching scripts, connecting signals — is deliberately *not* duplicated as a tool; the bridge covers what files can't: editor state, verification, binary-encoded cell data, and the running game. A `--read-only` flag serves the look-but-don't-touch use case.
|
|
88
90
|
|
|
89
91
|
## Things to ask for
|
|
90
92
|
|
|
@@ -121,11 +123,11 @@ One caveat: a Godot editor serves a single godot-mcp client at a time. Extra cli
|
|
|
121
123
|
## Documentation
|
|
122
124
|
|
|
123
125
|
- [Installation Guide](https://github.com/satelliteoflove/godot-mcp/blob/main/INSTALL.md) — MCP client configs for Claude Desktop, Claude Code, VSCode/Copilot, and more
|
|
126
|
+
- [Migrating to v4](https://github.com/satelliteoflove/godot-mcp/blob/main/docs/migrating-to-v4.md) — renamed tools, removed actions, allowlist updates
|
|
124
127
|
- [Troubleshooting](https://github.com/satelliteoflove/godot-mcp/blob/main/docs/troubleshooting.md) — connection checklist, CLI smoke test, common fixes
|
|
125
128
|
- [Claude Code Setup Guide](https://github.com/satelliteoflove/godot-mcp/blob/main/docs/claude-code-setup.md) — CLAUDE.md template for Godot projects
|
|
126
129
|
- [Runtime State Guide](https://github.com/satelliteoflove/godot-mcp/blob/main/docs/runtime-state-guide.md) — expose game state to agents via `mcp_watch` and `_mcp_state()`
|
|
127
|
-
- [Tools Reference](https://github.com/satelliteoflove/godot-mcp/blob/main/docs/tools/README.md) — all
|
|
128
|
-
- [Resources Reference](https://github.com/satelliteoflove/godot-mcp/blob/main/docs/resources.md) — MCP resources for reading project data
|
|
130
|
+
- [Tools Reference](https://github.com/satelliteoflove/godot-mcp/blob/main/docs/tools/README.md) — all 21 tools with full API docs
|
|
129
131
|
- [Architecture Guide](https://github.com/satelliteoflove/godot-mcp/blob/main/docs/architecture.md) — how the server, addon, and game bridge fit together
|
|
130
132
|
- [Contributing](https://github.com/satelliteoflove/godot-mcp/blob/main/CONTRIBUTING.md) — dev setup, adding tools, release process
|
|
131
133
|
- [Changelog](https://github.com/satelliteoflove/godot-mcp/blob/main/server/CHANGELOG.md) — release history
|
|
@@ -141,10 +143,16 @@ One caveat: a Godot editor serves a single godot-mcp client at a time. Extra cli
|
|
|
141
143
|
```bash
|
|
142
144
|
cd server
|
|
143
145
|
npm install && npm run build
|
|
144
|
-
npm test
|
|
146
|
+
npm test # unit + schema-snapshot tests
|
|
147
|
+
npm run test:protocol # wire-level smoke of the built server
|
|
145
148
|
npm run generate-docs
|
|
146
149
|
```
|
|
147
150
|
|
|
151
|
+
There is also an agentic eval harness (`npm run eval`) that runs realistic
|
|
152
|
+
tasks through headless Claude Code against a real project — see
|
|
153
|
+
[server/evals/README.md](https://github.com/satelliteoflove/godot-mcp/blob/main/server/evals/README.md). It never starts Godot; you
|
|
154
|
+
bring the open editor.
|
|
155
|
+
|
|
148
156
|
Contributions welcome — this project favors tools that solve real, time-wasting problems. Read [CONTRIBUTING.md](https://github.com/satelliteoflove/godot-mcp/blob/main/CONTRIBUTING.md) before building something big, or open an issue and we'll figure out the right shape together.
|
|
149
157
|
|
|
150
158
|
## License
|
package/addon/command_router.gd
CHANGED
|
@@ -10,7 +10,6 @@ func setup(plugin: EditorPlugin) -> void:
|
|
|
10
10
|
_register_handler(MCPSystemCommands.new(), plugin)
|
|
11
11
|
_register_handler(MCPSceneCommands.new(), plugin)
|
|
12
12
|
_register_handler(MCPNodeCommands.new(), plugin)
|
|
13
|
-
_register_handler(MCPScriptCommands.new(), plugin)
|
|
14
13
|
_register_handler(MCPSelectionCommands.new(), plugin)
|
|
15
14
|
_register_handler(MCPProjectCommands.new(), plugin)
|
|
16
15
|
_register_handler(MCPDebugCommands.new(), plugin)
|
|
@@ -2,21 +2,15 @@
|
|
|
2
2
|
extends MCPBaseCommand
|
|
3
3
|
class_name MCPDebugCommands
|
|
4
4
|
|
|
5
|
-
const DEBUG_OUTPUT_TIMEOUT := 5.0
|
|
6
5
|
# Keep in sync with LAUNCH_FROZEN_ENV in mcp_game_bridge.gd.
|
|
7
6
|
const LAUNCH_FROZEN_ENV := "GODOT_MCP_LAUNCH_FROZEN"
|
|
8
7
|
|
|
9
|
-
var _debug_output_result: PackedStringArray = []
|
|
10
|
-
var _debug_output_pending: bool = false
|
|
11
|
-
|
|
12
8
|
|
|
13
9
|
func get_commands() -> Dictionary:
|
|
14
10
|
return {
|
|
15
11
|
"run_project": run_project,
|
|
16
12
|
"stop_project": stop_project,
|
|
17
|
-
"get_debug_output": get_debug_output,
|
|
18
13
|
"get_log_messages": get_log_messages,
|
|
19
|
-
"get_errors": get_errors,
|
|
20
14
|
"get_stack_trace": get_stack_trace,
|
|
21
15
|
}
|
|
22
16
|
|
|
@@ -25,8 +19,6 @@ func run_project(params: Dictionary) -> Dictionary:
|
|
|
25
19
|
var scene_path: String = params.get("scene_path", "")
|
|
26
20
|
var frozen: bool = params.get("frozen", false)
|
|
27
21
|
|
|
28
|
-
MCPLogger.clear()
|
|
29
|
-
|
|
30
22
|
# Launch-frozen: the spawned game inherits the editor's environment, so
|
|
31
23
|
# setting this before play makes the bridge freeze the tree in _ready —
|
|
32
24
|
# before the first process frame. Deterministic, unlike sending a freeze
|
|
@@ -56,64 +48,6 @@ func stop_project(_params: Dictionary) -> Dictionary:
|
|
|
56
48
|
return _success({})
|
|
57
49
|
|
|
58
50
|
|
|
59
|
-
func get_debug_output(params: Dictionary) -> Dictionary:
|
|
60
|
-
var clear: bool = params.get("clear", false)
|
|
61
|
-
var source: String = params.get("source", "")
|
|
62
|
-
|
|
63
|
-
if source == "editor":
|
|
64
|
-
var output := "\n".join(MCPLogger.get_output())
|
|
65
|
-
if clear:
|
|
66
|
-
MCPLogger.clear()
|
|
67
|
-
return _success({"output": output, "source": "editor"})
|
|
68
|
-
|
|
69
|
-
if source == "game":
|
|
70
|
-
if not EditorInterface.is_playing_scene():
|
|
71
|
-
return _error("NOT_RUNNING", "No game is currently running. Use source: 'editor' for editor output.")
|
|
72
|
-
var debugger_plugin = _plugin.get_debugger_plugin() if _plugin else null
|
|
73
|
-
if debugger_plugin == null or not debugger_plugin.has_active_session():
|
|
74
|
-
return _error("NO_SESSION", "No active debug session. Use source: 'editor' for editor output.")
|
|
75
|
-
return await _fetch_game_debug_output(debugger_plugin, clear)
|
|
76
|
-
|
|
77
|
-
if not EditorInterface.is_playing_scene():
|
|
78
|
-
var output := "\n".join(MCPLogger.get_output())
|
|
79
|
-
if clear:
|
|
80
|
-
MCPLogger.clear()
|
|
81
|
-
return _success({"output": output, "source": "editor"})
|
|
82
|
-
|
|
83
|
-
var debugger_plugin = _plugin.get_debugger_plugin() if _plugin else null
|
|
84
|
-
if debugger_plugin == null or not debugger_plugin.has_active_session():
|
|
85
|
-
var output := "\n".join(MCPLogger.get_output())
|
|
86
|
-
if clear:
|
|
87
|
-
MCPLogger.clear()
|
|
88
|
-
return _success({"output": output, "source": "editor"})
|
|
89
|
-
|
|
90
|
-
return await _fetch_game_debug_output(debugger_plugin, clear)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
func _fetch_game_debug_output(debugger_plugin: MCPDebuggerPlugin, clear: bool) -> Dictionary:
|
|
94
|
-
_debug_output_pending = true
|
|
95
|
-
_debug_output_result = PackedStringArray()
|
|
96
|
-
|
|
97
|
-
debugger_plugin.debug_output_received.connect(_on_debug_output_received, CONNECT_ONE_SHOT)
|
|
98
|
-
debugger_plugin.request_debug_output(clear)
|
|
99
|
-
|
|
100
|
-
var start_time := Time.get_ticks_msec()
|
|
101
|
-
while _debug_output_pending:
|
|
102
|
-
await Engine.get_main_loop().process_frame
|
|
103
|
-
if (Time.get_ticks_msec() - start_time) / 1000.0 > DEBUG_OUTPUT_TIMEOUT:
|
|
104
|
-
_debug_output_pending = false
|
|
105
|
-
if debugger_plugin.debug_output_received.is_connected(_on_debug_output_received):
|
|
106
|
-
debugger_plugin.debug_output_received.disconnect(_on_debug_output_received)
|
|
107
|
-
return _success({"output": "\n".join(MCPLogger.get_output()), "source": "editor"})
|
|
108
|
-
|
|
109
|
-
return _success({"output": "\n".join(_debug_output_result), "source": "game"})
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
func _on_debug_output_received(output: PackedStringArray) -> void:
|
|
113
|
-
_debug_output_pending = false
|
|
114
|
-
_debug_output_result = output
|
|
115
|
-
|
|
116
|
-
|
|
117
51
|
func get_log_messages(params: Dictionary) -> Dictionary:
|
|
118
52
|
var clear: bool = params.get("clear", false)
|
|
119
53
|
var limit: int = int(params.get("limit", 50))
|
|
@@ -137,10 +71,6 @@ func get_log_messages(params: Dictionary) -> Dictionary:
|
|
|
137
71
|
return _success(result)
|
|
138
72
|
|
|
139
73
|
|
|
140
|
-
func get_errors(params: Dictionary) -> Dictionary:
|
|
141
|
-
return get_log_messages(params)
|
|
142
|
-
|
|
143
|
-
|
|
144
74
|
func get_stack_trace(_params: Dictionary) -> Dictionary:
|
|
145
75
|
var frames := MCPLogger.get_last_stack_trace()
|
|
146
76
|
var errors := MCPLogger.get_errors()
|
|
@@ -111,7 +111,7 @@ func _send_and_wait(msg_type: String, args: Array, timeout: float, call_id: int)
|
|
|
111
111
|
debugger_plugin.clear_response(msg_type)
|
|
112
112
|
var hint := ""
|
|
113
113
|
if debugger_plugin.is_session_breaked():
|
|
114
|
-
hint = " (the game is paused in the editor debugger and did not resume; press Continue in the editor or run
|
|
114
|
+
hint = " (the game is paused in the editor debugger and did not resume; press Continue in the editor or run godot_editor_edit stop)"
|
|
115
115
|
_last_error = _error("TIMEOUT", "Timed out waiting for %s response%s" % [msg_type, hint])
|
|
116
116
|
return null
|
|
117
117
|
return null # unreachable; satisfies the parser
|
|
@@ -104,7 +104,7 @@ func _get_editor_input_map() -> Dictionary:
|
|
|
104
104
|
# This map is read from the editor's in-memory InputMap, which is loaded at
|
|
105
105
|
# startup and goes stale if project.godot's [input] section is edited on disk
|
|
106
106
|
# (#245). Flag that so the caller knows the map may be incomplete and can
|
|
107
|
-
# recover with `
|
|
107
|
+
# recover with `godot_editor_edit restart`. The game-running path above reads fresh
|
|
108
108
|
# from the bridge, so it never carries this.
|
|
109
109
|
var result := {"actions": actions, "source": "editor"}
|
|
110
110
|
var staleness := MCPUtils.detect_project_staleness()
|
|
@@ -12,11 +12,8 @@ func get_commands() -> Dictionary:
|
|
|
12
12
|
return {
|
|
13
13
|
"get_node_properties": get_node_properties,
|
|
14
14
|
"find_nodes": find_nodes,
|
|
15
|
-
"create_node": create_node,
|
|
16
15
|
"update_node": update_node,
|
|
17
|
-
"
|
|
18
|
-
"reparent_node": reparent_node,
|
|
19
|
-
"connect_signal": connect_signal
|
|
16
|
+
"reparent_node": reparent_node
|
|
20
17
|
}
|
|
21
18
|
|
|
22
19
|
|
|
@@ -120,79 +117,6 @@ func _find_recursive(node: Node, scene_root: Node, name_pattern: String, type_fi
|
|
|
120
117
|
_find_recursive(child, scene_root, name_pattern, type_filter, results)
|
|
121
118
|
|
|
122
119
|
|
|
123
|
-
func create_node(params: Dictionary) -> Dictionary:
|
|
124
|
-
var scene_check := _require_scene_open()
|
|
125
|
-
if not scene_check.is_empty():
|
|
126
|
-
return scene_check
|
|
127
|
-
|
|
128
|
-
var parent_path: String = params.get("parent_path", "")
|
|
129
|
-
var node_type: String = params.get("node_type", "")
|
|
130
|
-
var scene_path: String = params.get("scene_path", "")
|
|
131
|
-
var node_name: String = params.get("node_name", "")
|
|
132
|
-
var properties: Dictionary = params.get("properties", {})
|
|
133
|
-
|
|
134
|
-
if parent_path.is_empty():
|
|
135
|
-
return _error("INVALID_PARAMS", "parent_path is required")
|
|
136
|
-
if node_name.is_empty():
|
|
137
|
-
return _error("INVALID_PARAMS", "node_name is required")
|
|
138
|
-
if node_type.is_empty() and scene_path.is_empty():
|
|
139
|
-
return _error("INVALID_PARAMS", "Either node_type or scene_path is required")
|
|
140
|
-
if not node_type.is_empty() and not scene_path.is_empty():
|
|
141
|
-
return _error("INVALID_PARAMS", "Provide node_type OR scene_path, not both")
|
|
142
|
-
|
|
143
|
-
var parent := _get_node(parent_path)
|
|
144
|
-
if not parent:
|
|
145
|
-
return _error("NODE_NOT_FOUND", "Parent node not found: %s" % parent_path)
|
|
146
|
-
|
|
147
|
-
var node: Node
|
|
148
|
-
if not scene_path.is_empty():
|
|
149
|
-
if not ResourceLoader.exists(scene_path):
|
|
150
|
-
return _error("SCENE_NOT_FOUND", "Scene not found: %s" % scene_path)
|
|
151
|
-
var packed_scene: PackedScene = load(scene_path)
|
|
152
|
-
if not packed_scene:
|
|
153
|
-
return _error("LOAD_FAILED", "Failed to load scene: %s" % scene_path)
|
|
154
|
-
node = packed_scene.instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
|
|
155
|
-
if not node:
|
|
156
|
-
return _error("INSTANTIATE_FAILED", "Failed to instantiate: %s" % scene_path)
|
|
157
|
-
else:
|
|
158
|
-
if not ClassDB.class_exists(node_type):
|
|
159
|
-
return _error("INVALID_TYPE", "Unknown node type: %s" % node_type)
|
|
160
|
-
node = ClassDB.instantiate(node_type)
|
|
161
|
-
if not node:
|
|
162
|
-
return _error("CREATE_FAILED", "Failed to create node of type: %s" % node_type)
|
|
163
|
-
|
|
164
|
-
node.name = node_name
|
|
165
|
-
|
|
166
|
-
for key in properties:
|
|
167
|
-
if node.has_method("set") and key in node:
|
|
168
|
-
var deserialized := MCPUtils.deserialize_value(properties[key])
|
|
169
|
-
node.set(key, deserialized)
|
|
170
|
-
|
|
171
|
-
parent.add_child(node)
|
|
172
|
-
var scene_root := EditorInterface.get_edited_scene_root()
|
|
173
|
-
_set_owner_recursive(node, scene_root)
|
|
174
|
-
|
|
175
|
-
# Re-apply spatial transforms after add_child: the editor viewport may
|
|
176
|
-
# snap newly added Node3D nodes to the current 3D cursor position,
|
|
177
|
-
# overriding properties set before add_child.
|
|
178
|
-
if node is Node3D:
|
|
179
|
-
var n3d := node as Node3D
|
|
180
|
-
if "position" in properties:
|
|
181
|
-
n3d.position = MCPUtils.deserialize_value(properties["position"])
|
|
182
|
-
if "rotation" in properties:
|
|
183
|
-
n3d.rotation = MCPUtils.deserialize_value(properties["rotation"])
|
|
184
|
-
if "scale" in properties:
|
|
185
|
-
n3d.scale = MCPUtils.deserialize_value(properties["scale"])
|
|
186
|
-
|
|
187
|
-
return _success({"node_path": str(scene_root.get_path_to(node))})
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
func _set_owner_recursive(node: Node, owner: Node) -> void:
|
|
191
|
-
node.owner = owner
|
|
192
|
-
for child in node.get_children():
|
|
193
|
-
_set_owner_recursive(child, owner)
|
|
194
|
-
|
|
195
|
-
|
|
196
120
|
func update_node(params: Dictionary) -> Dictionary:
|
|
197
121
|
var node_path: String = params.get("node_path", "")
|
|
198
122
|
var properties: Dictionary = params.get("properties", {})
|
|
@@ -214,29 +138,6 @@ func update_node(params: Dictionary) -> Dictionary:
|
|
|
214
138
|
return _success({})
|
|
215
139
|
|
|
216
140
|
|
|
217
|
-
func delete_node(params: Dictionary) -> Dictionary:
|
|
218
|
-
var scene_check := _require_scene_open()
|
|
219
|
-
if not scene_check.is_empty():
|
|
220
|
-
return scene_check
|
|
221
|
-
|
|
222
|
-
var node_path: String = params.get("node_path", "")
|
|
223
|
-
if node_path.is_empty():
|
|
224
|
-
return _error("INVALID_PARAMS", "node_path is required")
|
|
225
|
-
|
|
226
|
-
var node := _get_node(node_path)
|
|
227
|
-
if not node:
|
|
228
|
-
return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
|
|
229
|
-
|
|
230
|
-
var root := EditorInterface.get_edited_scene_root()
|
|
231
|
-
if node == root:
|
|
232
|
-
return _error("CANNOT_DELETE_ROOT", "Cannot delete the root node")
|
|
233
|
-
|
|
234
|
-
node.get_parent().remove_child(node)
|
|
235
|
-
node.queue_free()
|
|
236
|
-
|
|
237
|
-
return _success({})
|
|
238
|
-
|
|
239
|
-
|
|
240
141
|
func reparent_node(params: Dictionary) -> Dictionary:
|
|
241
142
|
var scene_check := _require_scene_open()
|
|
242
143
|
if not scene_check.is_empty():
|
|
@@ -270,45 +171,3 @@ func reparent_node(params: Dictionary) -> Dictionary:
|
|
|
270
171
|
return _success({"new_path": str(root.get_path_to(node))})
|
|
271
172
|
|
|
272
173
|
|
|
273
|
-
func connect_signal(params: Dictionary) -> Dictionary:
|
|
274
|
-
var scene_check := _require_scene_open()
|
|
275
|
-
if not scene_check.is_empty():
|
|
276
|
-
return scene_check
|
|
277
|
-
|
|
278
|
-
var node_path: String = params.get("node_path", "")
|
|
279
|
-
var signal_name: String = params.get("signal_name", "")
|
|
280
|
-
var target_path: String = params.get("target_path", "")
|
|
281
|
-
var method_name: String = params.get("method_name", "")
|
|
282
|
-
|
|
283
|
-
if node_path.is_empty():
|
|
284
|
-
return _error("INVALID_PARAMS", "node_path is required")
|
|
285
|
-
if signal_name.is_empty():
|
|
286
|
-
return _error("INVALID_PARAMS", "signal_name is required")
|
|
287
|
-
if target_path.is_empty():
|
|
288
|
-
return _error("INVALID_PARAMS", "target_path is required")
|
|
289
|
-
if method_name.is_empty():
|
|
290
|
-
return _error("INVALID_PARAMS", "method_name is required")
|
|
291
|
-
|
|
292
|
-
var source_node := _get_node(node_path)
|
|
293
|
-
if not source_node:
|
|
294
|
-
return _error("NODE_NOT_FOUND", "Source node not found: %s" % node_path)
|
|
295
|
-
|
|
296
|
-
var target_node := _get_node(target_path)
|
|
297
|
-
if not target_node:
|
|
298
|
-
return _error("NODE_NOT_FOUND", "Target node not found: %s" % target_path)
|
|
299
|
-
|
|
300
|
-
if not source_node.has_signal(signal_name):
|
|
301
|
-
return _error("SIGNAL_NOT_FOUND", "Signal '%s' not found on node %s" % [signal_name, node_path])
|
|
302
|
-
|
|
303
|
-
if source_node.is_connected(signal_name, Callable(target_node, method_name)):
|
|
304
|
-
return _error("ALREADY_CONNECTED", "Signal '%s' is already connected to %s.%s()" % [signal_name, target_path, method_name])
|
|
305
|
-
|
|
306
|
-
var err := source_node.connect(signal_name, Callable(target_node, method_name), CONNECT_PERSIST)
|
|
307
|
-
if err != OK:
|
|
308
|
-
return _error("CONNECT_FAILED", "Failed to connect signal: %s" % error_string(err))
|
|
309
|
-
|
|
310
|
-
EditorInterface.mark_scene_as_unsaved()
|
|
311
|
-
|
|
312
|
-
return _success({})
|
|
313
|
-
|
|
314
|
-
|
|
@@ -13,7 +13,7 @@ func get_commands() -> Dictionary:
|
|
|
13
13
|
|
|
14
14
|
# Detect whether project.godot was edited on disk after the editor loaded it,
|
|
15
15
|
# leaving the editor's in-memory ProjectSettings / InputMap stale (#245). Always
|
|
16
|
-
# returns the full report (stale or not); recovery is `
|
|
16
|
+
# returns the full report (stale or not); recovery is `godot_editor_edit restart`.
|
|
17
17
|
func get_project_staleness(_params: Dictionary) -> Dictionary:
|
|
18
18
|
return _success(MCPUtils.detect_project_staleness())
|
|
19
19
|
|
|
@@ -5,39 +5,25 @@ class_name MCPSceneCommands
|
|
|
5
5
|
|
|
6
6
|
func get_commands() -> Dictionary:
|
|
7
7
|
return {
|
|
8
|
-
"get_current_scene": get_current_scene,
|
|
9
8
|
"get_scene_tree": get_scene_tree,
|
|
10
9
|
"open_scene": open_scene,
|
|
11
|
-
"save_scene": save_scene
|
|
12
|
-
"create_scene": create_scene
|
|
10
|
+
"save_scene": save_scene
|
|
13
11
|
}
|
|
14
12
|
|
|
15
13
|
|
|
16
|
-
func
|
|
17
|
-
var root := EditorInterface.get_edited_scene_root()
|
|
18
|
-
if not root:
|
|
19
|
-
return _success({
|
|
20
|
-
"path": null,
|
|
21
|
-
"root_name": null,
|
|
22
|
-
"root_type": null
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
return _success({
|
|
26
|
-
"path": root.scene_file_path,
|
|
27
|
-
"root_name": root.name,
|
|
28
|
-
"root_type": root.get_class()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
func get_scene_tree(_params: Dictionary) -> Dictionary:
|
|
14
|
+
func get_scene_tree(params: Dictionary) -> Dictionary:
|
|
33
15
|
var root := EditorInterface.get_edited_scene_root()
|
|
34
16
|
if not root:
|
|
35
17
|
return _error("NO_SCENE", "No scene is currently open")
|
|
36
18
|
|
|
37
|
-
|
|
19
|
+
# 0 = unlimited for both caps (the default when the param is omitted), so the
|
|
20
|
+
# full tree is unchanged unless a caller opts into trimming it.
|
|
21
|
+
var max_depth: int = int(params.get("max_depth", 0))
|
|
22
|
+
var max_children: int = int(params.get("max_children", 0))
|
|
23
|
+
return _success({"tree": _build_tree(root, 1, max_depth, max_children)})
|
|
38
24
|
|
|
39
25
|
|
|
40
|
-
func _build_tree(node: Node) -> Dictionary:
|
|
26
|
+
func _build_tree(node: Node, depth: int, max_depth: int, max_children: int) -> Dictionary:
|
|
41
27
|
var result := {
|
|
42
28
|
"name": node.name,
|
|
43
29
|
"type": node.get_class(),
|
|
@@ -50,12 +36,29 @@ func _build_tree(node: Node) -> Dictionary:
|
|
|
50
36
|
var pos: Vector3 = node.position
|
|
51
37
|
result["position"] = {"x": pos.x, "y": pos.y, "z": pos.z}
|
|
52
38
|
|
|
39
|
+
var child_nodes := node.get_children()
|
|
40
|
+
var child_count := child_nodes.size()
|
|
41
|
+
if child_count == 0:
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
# Depth cap: at the limit, stop recursing and just report how many direct
|
|
45
|
+
# children were cut off.
|
|
46
|
+
if max_depth > 0 and depth >= max_depth:
|
|
47
|
+
result["truncated_children"] = child_count
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
# Breadth cap: list the first max_children and report the remainder.
|
|
51
|
+
var limit := child_count
|
|
52
|
+
if max_children > 0 and child_count > max_children:
|
|
53
|
+
limit = max_children
|
|
54
|
+
|
|
53
55
|
var children: Array[Dictionary] = []
|
|
54
|
-
for
|
|
55
|
-
children.append(_build_tree(
|
|
56
|
+
for i in range(limit):
|
|
57
|
+
children.append(_build_tree(child_nodes[i], depth + 1, max_depth, max_children))
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
result["children"] = children
|
|
60
|
+
if limit < child_count:
|
|
61
|
+
result["truncated_children"] = child_count - limit
|
|
59
62
|
|
|
60
63
|
return result
|
|
61
64
|
|
|
@@ -95,39 +98,3 @@ func save_scene(params: Dictionary) -> Dictionary:
|
|
|
95
98
|
|
|
96
99
|
return _success({"path": path})
|
|
97
100
|
|
|
98
|
-
|
|
99
|
-
func create_scene(params: Dictionary) -> Dictionary:
|
|
100
|
-
var root_type: String = params.get("root_type", "")
|
|
101
|
-
var root_name: String = params.get("root_name", root_type)
|
|
102
|
-
var scene_path: String = params.get("scene_path", "")
|
|
103
|
-
|
|
104
|
-
if root_type.is_empty():
|
|
105
|
-
return _error("INVALID_PARAMS", "root_type is required")
|
|
106
|
-
if scene_path.is_empty():
|
|
107
|
-
return _error("INVALID_PARAMS", "scene_path is required")
|
|
108
|
-
|
|
109
|
-
if not ClassDB.class_exists(root_type):
|
|
110
|
-
return _error("INVALID_TYPE", "Unknown node type: %s" % root_type)
|
|
111
|
-
|
|
112
|
-
var root: Node = ClassDB.instantiate(root_type)
|
|
113
|
-
if not root:
|
|
114
|
-
return _error("CREATE_FAILED", "Failed to create node of type: %s" % root_type)
|
|
115
|
-
|
|
116
|
-
root.name = root_name
|
|
117
|
-
|
|
118
|
-
var packed_scene := PackedScene.new()
|
|
119
|
-
var err := packed_scene.pack(root)
|
|
120
|
-
root.free()
|
|
121
|
-
|
|
122
|
-
if err != OK:
|
|
123
|
-
return _error("PACK_FAILED", "Failed to pack scene: %s" % error_string(err))
|
|
124
|
-
|
|
125
|
-
err = ResourceSaver.save(packed_scene, scene_path)
|
|
126
|
-
if err != OK:
|
|
127
|
-
return _error("SAVE_FAILED", "Failed to save scene: %s" % error_string(err))
|
|
128
|
-
|
|
129
|
-
EditorInterface.open_scene_from_path(scene_path)
|
|
130
|
-
|
|
131
|
-
var uid := ResourceUID.id_to_text(ResourceLoader.get_resource_uid(scene_path))
|
|
132
|
-
return _success({"path": scene_path, "uid": uid})
|
|
133
|
-
|
|
@@ -90,14 +90,27 @@ func set_2d_viewport(params: Dictionary) -> Dictionary:
|
|
|
90
90
|
if not viewport:
|
|
91
91
|
return _error("NO_VIEWPORT", "Could not access 2D editor viewport")
|
|
92
92
|
|
|
93
|
-
var
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
var size := viewport.size
|
|
94
|
+
|
|
95
|
+
# Read the current view first so any omitted parameter preserves it instead of
|
|
96
|
+
# snapping to a default (a zoom-only call keeps the current center, not 0,0).
|
|
97
|
+
# This inverts the same transform math applied below — see
|
|
98
|
+
# _get_editor_2d_viewport_info().
|
|
99
|
+
var current := viewport.global_canvas_transform
|
|
100
|
+
var current_zoom: float = current.x.x
|
|
101
|
+
var current_offset: Vector2 = -current.origin / current_zoom
|
|
102
|
+
var current_center := Vector2(
|
|
103
|
+
current_offset.x + size.x / current_zoom / 2,
|
|
104
|
+
current_offset.y + size.y / current_zoom / 2
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
var center_x: float = params.get("center_x", current_center.x)
|
|
108
|
+
var center_y: float = params.get("center_y", current_center.y)
|
|
109
|
+
var zoom: float = params.get("zoom", current_zoom)
|
|
96
110
|
|
|
97
111
|
if zoom <= 0:
|
|
98
112
|
return _error("INVALID_PARAMS", "zoom must be positive")
|
|
99
113
|
|
|
100
|
-
var size := viewport.size
|
|
101
114
|
var offset := Vector2(center_x - size.x / zoom / 2, center_y - size.y / zoom / 2)
|
|
102
115
|
var origin := -offset * zoom
|
|
103
116
|
|
|
@@ -34,27 +34,3 @@ func _require_scene_open() -> Dictionary:
|
|
|
34
34
|
if not root:
|
|
35
35
|
return _error("NO_SCENE", "No scene is currently open")
|
|
36
36
|
return {}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
func _require_typed_node(path: String, type: String, type_error_code: String = "WRONG_TYPE") -> Variant:
|
|
40
|
-
var node := _get_node(path)
|
|
41
|
-
if not node:
|
|
42
|
-
return _error("NODE_NOT_FOUND", "Node not found: %s" % path)
|
|
43
|
-
if not node.is_class(type):
|
|
44
|
-
return _error(type_error_code, "Expected %s, got %s" % [type, node.get_class()])
|
|
45
|
-
return node
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
func _find_nodes_of_type(root: Node, type: String) -> Array[Dictionary]:
|
|
49
|
-
var result: Array[Dictionary] = []
|
|
50
|
-
var scene_root := EditorInterface.get_edited_scene_root()
|
|
51
|
-
if scene_root:
|
|
52
|
-
_find_nodes_recursive(root, type, result, scene_root)
|
|
53
|
-
return result
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
func _find_nodes_recursive(node: Node, type: String, result: Array[Dictionary], scene_root: Node) -> void:
|
|
57
|
-
if node.is_class(type):
|
|
58
|
-
result.append({"path": str(scene_root.get_path_to(node)), "name": node.name})
|
|
59
|
-
for child in node.get_children():
|
|
60
|
-
_find_nodes_recursive(child, type, result, scene_root)
|
|
@@ -3,14 +3,12 @@ extends EditorDebuggerPlugin
|
|
|
3
3
|
class_name MCPDebuggerPlugin
|
|
4
4
|
|
|
5
5
|
signal screenshot_received(success: bool, image_base64: String, width: int, height: int, error: String)
|
|
6
|
-
signal debug_output_received(output: PackedStringArray)
|
|
7
6
|
signal performance_metrics_received(metrics: Dictionary)
|
|
8
7
|
signal find_nodes_received(matches: Array, count: int, error: String)
|
|
9
8
|
signal input_map_received(actions: Array, error: String)
|
|
10
9
|
signal input_sequence_completed(result: Dictionary)
|
|
11
10
|
signal sequence_capture_received(requested_ms: int, actual_ms: int, ok: bool, image_base64: String, width: int, height: int, error: String)
|
|
12
11
|
signal type_text_completed(result: Dictionary)
|
|
13
|
-
signal game_response(message_type: String, data: Variant)
|
|
14
12
|
signal bridge_ready()
|
|
15
13
|
|
|
16
14
|
var _active_session_id: int = -1
|
|
@@ -23,7 +21,6 @@ var _pending_screenshot: bool = false
|
|
|
23
21
|
# (element 6 of screenshot_result; empty for bridges that predate it). Held
|
|
24
22
|
# here instead of widening the screenshot_received signal signature.
|
|
25
23
|
var last_screenshot_warnings: Array = []
|
|
26
|
-
var _pending_debug_output: bool = false
|
|
27
24
|
var _pending_performance_metrics: bool = false
|
|
28
25
|
var _pending_find_nodes: bool = false
|
|
29
26
|
var _pending_input_map: bool = false
|
|
@@ -42,9 +39,6 @@ func _capture(message: String, data: Array, session_id: int) -> bool:
|
|
|
42
39
|
"godot_mcp:screenshot_result":
|
|
43
40
|
_handle_screenshot_result(data)
|
|
44
41
|
return true
|
|
45
|
-
"godot_mcp:debug_output_result":
|
|
46
|
-
_handle_debug_output_result(data)
|
|
47
|
-
return true
|
|
48
42
|
"godot_mcp:performance_metrics_result":
|
|
49
43
|
_handle_performance_metrics_result(data)
|
|
50
44
|
return true
|
|
@@ -93,9 +87,6 @@ func _session_stopped() -> void:
|
|
|
93
87
|
if _pending_screenshot:
|
|
94
88
|
_pending_screenshot = false
|
|
95
89
|
screenshot_received.emit(false, "", 0, 0, "Game session ended")
|
|
96
|
-
if _pending_debug_output:
|
|
97
|
-
_pending_debug_output = false
|
|
98
|
-
debug_output_received.emit(PackedStringArray())
|
|
99
90
|
if _pending_performance_metrics:
|
|
100
91
|
_pending_performance_metrics = false
|
|
101
92
|
performance_metrics_received.emit({})
|
|
@@ -159,25 +150,6 @@ func _handle_screenshot_result(data: Array) -> void:
|
|
|
159
150
|
screenshot_received.emit(success, image_base64, width, height, error)
|
|
160
151
|
|
|
161
152
|
|
|
162
|
-
func request_debug_output(clear: bool = false) -> void:
|
|
163
|
-
if _active_session_id < 0:
|
|
164
|
-
debug_output_received.emit(PackedStringArray())
|
|
165
|
-
return
|
|
166
|
-
_pending_debug_output = true
|
|
167
|
-
var session := get_session(_active_session_id)
|
|
168
|
-
if session:
|
|
169
|
-
session.send_message("godot_mcp:get_debug_output", [clear])
|
|
170
|
-
else:
|
|
171
|
-
_pending_debug_output = false
|
|
172
|
-
debug_output_received.emit(PackedStringArray())
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
func _handle_debug_output_result(data: Array) -> void:
|
|
176
|
-
_pending_debug_output = false
|
|
177
|
-
var output: PackedStringArray = data[0] if data.size() > 0 else PackedStringArray()
|
|
178
|
-
debug_output_received.emit(output)
|
|
179
|
-
|
|
180
|
-
|
|
181
153
|
func request_performance_metrics() -> void:
|
|
182
154
|
if _active_session_id < 0:
|
|
183
155
|
performance_metrics_received.emit({})
|
|
@@ -322,7 +294,6 @@ func _handle_game_response(data: Array) -> void:
|
|
|
322
294
|
var response_data: Variant = data[1]
|
|
323
295
|
_pending_requests.erase(msg_type)
|
|
324
296
|
_responses[msg_type] = response_data
|
|
325
|
-
game_response.emit(msg_type, response_data)
|
|
326
297
|
|
|
327
298
|
|
|
328
299
|
func toggle_frame_profiler(enable: bool) -> void:
|