gopeak 2.1.0 → 2.2.0

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 CHANGED
@@ -14,108 +14,149 @@
14
14
 
15
15
  **GoPeak is an MCP server for Godot that lets AI assistants run, inspect, modify, and debug real projects end-to-end.**
16
16
 
17
- > Includes Auto Reload: when MCP edits scenes/scripts, the Godot editor refreshes automatically.
17
+ ---
18
+
19
+ ## Quick Start (3 Minutes)
20
+
21
+ ### Requirements
22
+
23
+ - Godot 4.x
24
+ - Node.js 18+
25
+ - MCP-compatible client (Claude Desktop, Cursor, Cline, OpenCode, etc.)
26
+
27
+ ### 1) Run GoPeak
28
+
29
+ ```bash
30
+ npx -y gopeak
31
+ ```
32
+
33
+ or install globally:
34
+
35
+ ```bash
36
+ npm install -g gopeak
37
+ gopeak
38
+ ```
39
+
40
+ ### 2) Add MCP client config
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "godot": {
46
+ "command": "npx",
47
+ "args": ["-y", "gopeak"],
48
+ "env": {
49
+ "GODOT_PATH": "/path/to/godot",
50
+ "GOPEAK_TOOL_PROFILE": "compact"
51
+ }
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ > `GOPEAK_TOOL_PROFILE=compact` is the default. It keeps prompt/tool-list token usage low while preserving access to the full toolset via catalog discovery.
58
+
59
+ ### 3) First prompts to try
60
+
61
+ - "List Godot projects in `/your/projects` and show project info."
62
+ - "Create `scenes/Player.tscn` with `CharacterBody2D` root and add a movement script."
63
+ - "Run project, get debug output, then fix top error."
18
64
 
19
65
  ---
20
66
 
21
- ## Why GoPeak (Short Version)
67
+ ## Why GoPeak
22
68
 
23
- - **Real project feedback loop**: run the game, read logs, fix issues in-context.
24
- - **95+ tools** across scene, script, resource, runtime, LSP, DAP, input, and assets.
25
- - **Deep Godot integration**: ClassDB introspection, runtime inspection, debugger hooks.
26
- - **Faster iteration**: less copy-paste, more direct implementation/testing.
69
+ - **Real project feedback loop**: run the game, inspect logs, and fix in-context.
70
+ - **95+ tools available** across scene/script/resource/runtime/LSP/DAP/input/assets.
71
+ - **Token-efficient by default**: compact tool surface for lower context overhead.
72
+ - **Discoverability built-in**: use `tool.catalog` (alias of `tool_catalog`) to find hidden or legacy tools by keyword.
73
+ - **Deep Godot integration**: ClassDB queries, runtime inspection, debugger hooks, bridge-based scene/resource edits.
27
74
 
28
75
  ### Best For
29
76
 
30
- - Solo/indie developers building quickly with AI assistance
31
- - Teams that want AI to inspect real project state, not just generate snippets
32
- - Debug-heavy workflows where runtime introspection and breakpoints matter
77
+ - Solo/indie developers moving quickly with AI assistance
78
+ - Teams that need AI grounded in actual project/runtime state
79
+ - Debug-heavy workflows (breakpoints, stack traces, live runtime checks)
33
80
 
34
81
  ---
35
82
 
36
- ## GoPeak vs Upstream (Coding-Solo/godot-mcp)
83
+ ## Tool Surface Model (Important)
37
84
 
38
- | Capability | Upstream | GoPeak |
39
- |---|---|---|
40
- | GDScript LSP tools | Not available in README tool list | ✅ `lsp_get_diagnostics`, `lsp_get_completions`, `lsp_get_hover`, `lsp_get_symbols` |
41
- | DAP debugging tools | Not available in README tool list | ✅ breakpoints, step/continue/pause, stack trace, debug output |
42
- | Input injection tools | Not available in README tool list | ✅ `inject_action`, `inject_key`, `inject_mouse_click`, `inject_mouse_motion` |
43
- | Screenshot capture tools | Not available in README tool list | ✅ `capture_screenshot`, `capture_viewport` |
44
- | Auto Reload editor plugin | Not available | ✅ included `auto_reload` addon |
45
- | Tool coverage scale | Smaller documented scope | ✅ 95+ MCP tools |
85
+ GoPeak supports three exposure profiles:
46
86
 
47
- ---
87
+ - `compact` (default): exposes a curated alias set (about 20 core tools)
88
+ - `full`: exposes full legacy tool list (95+)
89
+ - `legacy`: same exposed behavior as `full`
48
90
 
49
- ## Requirements
91
+ Configure with either:
50
92
 
51
- - Godot 4.x
52
- - Node.js 18+
53
- - MCP-compatible client (Claude Desktop, Cursor, Cline, OpenCode, etc.)
93
+ - `GOPEAK_TOOL_PROFILE`
94
+ - `MCP_TOOL_PROFILE` (fallback alias)
95
+
96
+ ### How to discover hidden tools in compact mode
97
+
98
+ Call:
99
+
100
+ - `tool.catalog` (compact alias)
101
+ - `tool_catalog` (legacy name)
102
+
103
+ Example intent:
104
+
105
+ > "Use `tool.catalog` with query `animation` and show relevant tools."
106
+
107
+ This lets assistants start with a small, efficient default surface but still find and use the full capability set when needed.
54
108
 
55
109
  ---
56
110
 
57
- ## Installation
111
+ ## Installation Options
58
112
 
59
- ### 1) Fastest (recommended)
113
+ ### A) Recommended: npx
60
114
 
61
115
  ```bash
62
- npx gopeak
116
+ npx -y gopeak
63
117
  ```
64
118
 
65
- or
119
+ ### B) Global install
66
120
 
67
121
  ```bash
68
122
  npm install -g gopeak
69
123
  gopeak
70
124
  ```
71
125
 
72
- ### 2) Manual (from source)
126
+ ### C) From source
73
127
 
74
128
  ```bash
75
129
  git clone https://github.com/HaD0Yun/godot-mcp.git
76
130
  cd godot-mcp
77
131
  npm install
78
132
  npm run build
133
+ node build/index.js
79
134
  ```
80
135
 
81
- Set `GODOT_PATH` if Godot is not auto-detected.
136
+ GoPeak also exposes two CLI bin names:
82
137
 
83
- ### MCP Client Config (practical examples)
138
+ - `gopeak`
139
+ - `godot-mcp`
84
140
 
85
- Use one of these depending on your client.
141
+ ---
86
142
 
87
- **A) Global install (`npm install -g gopeak`)**
143
+ ## Addons (Recommended)
88
144
 
89
- ```json
90
- {
91
- "mcpServers": {
92
- "godot": {
93
- "command": "gopeak",
94
- "env": {
95
- "GODOT_PATH": "/path/to/godot"
96
- }
97
- }
98
- }
99
- }
145
+ ### Auto Reload + Runtime Addon installer
146
+
147
+ Install in your Godot project folder:
148
+
149
+ ```bash
150
+ curl -sL https://raw.githubusercontent.com/HaD0Yun/godot-mcp/main/install-addon.sh | bash
100
151
  ```
101
152
 
102
- **B) No global install (npx mode)**
153
+ PowerShell:
103
154
 
104
- ```json
105
- {
106
- "mcpServers": {
107
- "godot": {
108
- "command": "npx",
109
- "args": ["-y", "gopeak"],
110
- "env": {
111
- "GODOT_PATH": "/path/to/godot"
112
- }
113
- }
114
- }
115
- }
155
+ ```powershell
156
+ iwr https://raw.githubusercontent.com/HaD0Yun/godot-mcp/main/install-addon.ps1 -UseBasicParsing | iex
116
157
  ```
117
158
 
118
- > Tip: If your client uses `args` array style only, use `"command": "npx"` + `"args": ["-y", "gopeak"]`.
159
+ Then enable plugin in **Project Settings Plugins**.
119
160
 
120
161
  ---
121
162
 
@@ -131,18 +172,27 @@ Use one of these depending on your client.
131
172
  - **Input + screenshots**: keyboard/mouse/action injection and viewport capture
132
173
  - **Asset library**: search/fetch CC0 assets (Poly Haven, AmbientCG, Kenney)
133
174
 
134
- ### Tool Coverage at a Glance
175
+ ### Tool families (examples)
135
176
 
136
177
  | Area | Examples |
137
178
  |---|---|
138
- | Scene/Node | `create_scene`, `add_node`, `set_node_properties` |
139
- | Script | `create_script`, `modify_script`, `get_script_info` |
140
- | Runtime | `inspect_runtime_tree`, `call_runtime_method` |
141
- | LSP/DAP | `lsp_get_diagnostics`, `dap_set_breakpoint` |
179
+ | Project | `project.list`, `project.info`, `editor.run` |
180
+ | Scene/Node | `scene.create`, `scene.node.add`, `set_node_properties` |
181
+ | Script | `script.create`, `script.modify`, `script.info` |
182
+ | Runtime | `runtime.status`, `inspect_runtime_tree`, `call_runtime_method` |
183
+ | LSP/DAP | `lsp.diagnostics`, `lsp_get_hover`, `dap_set_breakpoint`, `dap.output` |
142
184
  | Input/Visual | `inject_key`, `inject_mouse_click`, `capture_screenshot` |
143
185
 
144
186
  ---
145
187
 
188
+ ## Project Visualizer
189
+
190
+ Visualize your entire project architecture with `visualizer.map` (`map_project` legacy). Scripts are grouped by folder structure into color-coded categories.
191
+
192
+ ![Project Visualizer — AI-generated architecture map](assets/visualizer-category-map.png)
193
+
194
+ ---
195
+
146
196
  ## Quick Prompt Examples
147
197
 
148
198
  ### Build
@@ -151,27 +201,44 @@ Use one of these depending on your client.
151
201
 
152
202
  ### Debug
153
203
  - "Run the project, collect errors, and fix the top 3 issues automatically."
154
- - "Set a breakpoint at `scripts/player.gd:42`, continue execution, and show the stack trace when hit."
204
+ - "Set a breakpoint at `scripts/player.gd:42`, continue execution, and show stack trace when hit."
155
205
 
156
- ### Runtime Testing
206
+ ### Runtime testing
157
207
  - "Press `ui_accept`, move mouse to (400, 300), click, then capture a screenshot."
158
- - "Inspect the live scene tree and report nodes with missing scripts or invalid references."
208
+ - "Inspect live scene tree and report nodes with missing scripts or invalid references."
159
209
 
160
- ### Refactor / Maintenance
161
- - "Find all TODO/FIXME comments and group them by file with a priority suggestion."
162
- - "Analyze project health and list quick wins to improve stability before release."
210
+ ### Discovery in compact mode
211
+ - "Use `tool.catalog` with query `tilemap` and list the most relevant tools."
212
+ - "Find import pipeline tools with `tool.catalog` query `import` and run the best one for texture settings."
163
213
 
164
214
  ---
165
215
 
166
- ## Auto Reload Addon (Recommended)
216
+ ## Technical Reference
167
217
 
168
- Install in your Godot project folder:
218
+ ### Environment variables
169
219
 
170
- ```bash
171
- curl -sL https://raw.githubusercontent.com/HaD0Yun/godot-mcp/main/install-addon.sh | bash
172
- ```
220
+ | Name | Purpose | Default |
221
+ |---|---|---|
222
+ | `GOPEAK_TOOL_PROFILE` | Tool exposure profile: `compact`, `full`, `legacy` | `compact` |
223
+ | `MCP_TOOL_PROFILE` | Fallback profile env alias | `compact` |
224
+ | `GODOT_PATH` | Explicit Godot executable path | auto-detect |
225
+ | `DEBUG` | Enable server debug logs (`true`/`false`) | `false` |
226
+ | `LOG_MODE` | Recording mode: `lite` or `full` | `lite` |
173
227
 
174
- Then enable plugin in **Project Settings → Plugins**.
228
+ ### Ports
229
+
230
+ | Port | Service |
231
+ |---|---|
232
+ | `6505` | Unified Godot Bridge + Visualizer server (+ `/health`, `/mcp`) |
233
+ | `6005` | Godot LSP |
234
+ | `6006` | Godot DAP |
235
+ | `7777` | Runtime addon command socket (only needed for runtime tools) |
236
+
237
+ ### Minimal port profiles
238
+
239
+ - **Core editing only**: `6505`
240
+ - **Core + runtime actions (screenshots/input/runtime inspect)**: `6505` + `7777`
241
+ - **Full debugging + diagnostics**: `6505` + `6005` + `6006` + `7777`
175
242
 
176
243
  ---
177
244
 
@@ -180,7 +247,8 @@ Then enable plugin in **Project Settings → Plugins**.
180
247
  - **Godot not found** → set `GODOT_PATH`
181
248
  - **No MCP tools visible** → restart your MCP client
182
249
  - **Project path invalid** → confirm `project.godot` exists
183
- - **Runtime tools not working** → install/enable runtime addon
250
+ - **Runtime tools not working** → install/enable runtime addon plugin
251
+ - **Need a tool that is not visible** → run `tool.catalog` and search by capability keyword
184
252
 
185
253
  ---
186
254
 
@@ -200,3 +268,4 @@ MIT — see [LICENSE](LICENSE).
200
268
 
201
269
  - Original MCP server by [Coding-Solo](https://github.com/Coding-Solo/godot-mcp)
202
270
  - GoPeak enhancements by [HaD0Yun](https://github.com/HaD0Yun)
271
+ - Project visualizer inspired by [tomyud1/godot-mcp](https://github.com/tomyud1/godot-mcp)
@@ -0,0 +1,161 @@
1
+ @tool
2
+ extends Node
3
+ class_name MCPEditorClient
4
+
5
+ signal connected
6
+ signal disconnected
7
+ signal tool_requested(request_id: String, tool_name: String, args: Dictionary)
8
+
9
+ const DEFAULT_URL := "ws://127.0.0.1:6505/godot"
10
+ const RECONNECT_DELAY := 3.0
11
+ const MAX_RECONNECT_DELAY := 30.0
12
+
13
+ var socket: WebSocketPeer = WebSocketPeer.new()
14
+ var server_url: String = DEFAULT_URL
15
+ var _is_connected := false
16
+ var _reconnect_timer: Timer
17
+ var _current_reconnect_delay := RECONNECT_DELAY
18
+ var _should_reconnect := true
19
+ var _project_path: String
20
+ var _initialized := false
21
+
22
+
23
+ func _ready() -> void:
24
+ _project_path = ProjectSettings.globalize_path("res://")
25
+
26
+ _reconnect_timer = Timer.new()
27
+ _reconnect_timer.one_shot = true
28
+ _reconnect_timer.timeout.connect(_on_reconnect_timer)
29
+ add_child(_reconnect_timer)
30
+
31
+ set_process(true)
32
+ _initialized = true
33
+
34
+
35
+ func _process(_delta: float) -> void:
36
+ if not _initialized:
37
+ return
38
+
39
+ if socket.get_ready_state() == WebSocketPeer.STATE_CLOSED:
40
+ if _is_connected:
41
+ _handle_disconnect()
42
+ return
43
+
44
+ socket.poll()
45
+
46
+ match socket.get_ready_state():
47
+ WebSocketPeer.STATE_OPEN:
48
+ if not _is_connected:
49
+ _handle_connect()
50
+
51
+ while socket.get_available_packet_count() > 0:
52
+ var packet := socket.get_packet()
53
+ _handle_message(packet.get_string_from_utf8())
54
+
55
+ WebSocketPeer.STATE_CLOSING:
56
+ pass
57
+
58
+ WebSocketPeer.STATE_CLOSED:
59
+ if _is_connected:
60
+ _handle_disconnect()
61
+
62
+
63
+ func connect_to_server(url: String = DEFAULT_URL) -> void:
64
+ server_url = url
65
+ _should_reconnect = true
66
+ _current_reconnect_delay = RECONNECT_DELAY
67
+ _attempt_connection()
68
+
69
+
70
+ func disconnect_from_server() -> void:
71
+ _should_reconnect = false
72
+ if _reconnect_timer:
73
+ _reconnect_timer.stop()
74
+ if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
75
+ socket.close()
76
+ _is_connected = false
77
+
78
+
79
+ func _attempt_connection() -> void:
80
+ if socket.get_ready_state() != WebSocketPeer.STATE_CLOSED:
81
+ socket.close()
82
+
83
+ var err := socket.connect_to_url(server_url)
84
+ if err != OK:
85
+ push_error("[MCP Editor] Failed to connect: %s" % err)
86
+ _schedule_reconnect()
87
+
88
+
89
+ func _handle_connect() -> void:
90
+ _is_connected = true
91
+ _current_reconnect_delay = RECONNECT_DELAY
92
+
93
+ _send_message({
94
+ "type": "godot_ready",
95
+ "project_path": _project_path
96
+ })
97
+
98
+ connected.emit()
99
+
100
+
101
+ func _handle_disconnect() -> void:
102
+ _is_connected = false
103
+ disconnected.emit()
104
+
105
+ if _should_reconnect:
106
+ _schedule_reconnect()
107
+
108
+
109
+ func _schedule_reconnect() -> void:
110
+ if _reconnect_timer == null:
111
+ return
112
+ _reconnect_timer.start(_current_reconnect_delay)
113
+ _current_reconnect_delay = min(_current_reconnect_delay * 2.0, MAX_RECONNECT_DELAY)
114
+
115
+
116
+ func _on_reconnect_timer() -> void:
117
+ _attempt_connection()
118
+
119
+
120
+ func _handle_message(json_string: String) -> void:
121
+ var message = JSON.parse_string(json_string)
122
+ if message == null:
123
+ push_error("[MCP Editor] Failed to parse message: %s" % json_string)
124
+ return
125
+
126
+ match message.get("type", ""):
127
+ "ping":
128
+ _send_message({"type": "pong"})
129
+
130
+ "tool_invoke":
131
+ var request_id: String = message.get("id", "")
132
+ var tool_name: String = message.get("tool", "")
133
+ var args: Dictionary = message.get("args", {})
134
+ tool_requested.emit(request_id, tool_name, args)
135
+
136
+ _:
137
+ pass
138
+
139
+
140
+ func send_tool_result(request_id: String, success: bool, result = null, error: String = "") -> void:
141
+ var response := {
142
+ "type": "tool_result",
143
+ "id": request_id,
144
+ "success": success
145
+ }
146
+
147
+ if success:
148
+ response["result"] = result
149
+ else:
150
+ response["error"] = error
151
+
152
+ _send_message(response)
153
+
154
+
155
+ func _send_message(message: Dictionary) -> void:
156
+ if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
157
+ socket.send_text(JSON.stringify(message))
158
+
159
+
160
+ func is_connected_to_server() -> bool:
161
+ return _is_connected
@@ -0,0 +1,6 @@
1
+ [plugin]
2
+ name="Godot MCP Editor"
3
+ description="MCP Editor Plugin - Scene manipulation via Godot API"
4
+ author="HaD0Yun"
5
+ version="1.0.0"
6
+ script="plugin.gd"
@@ -0,0 +1,84 @@
1
+ @tool
2
+ extends EditorPlugin
3
+
4
+ const MCPEditorClientScript = preload("mcp_client.gd")
5
+ const MCPToolExecutorScript = preload("tool_executor.gd")
6
+
7
+ var _mcp_client: Node
8
+ var _tool_executor: Node
9
+ var _status_label: Label
10
+
11
+
12
+ func _enter_tree() -> void:
13
+ _mcp_client = MCPEditorClientScript.new()
14
+ _mcp_client.name = "MCPEditorClient"
15
+ add_child(_mcp_client)
16
+
17
+ _tool_executor = MCPToolExecutorScript.new()
18
+ _tool_executor.name = "MCPToolExecutor"
19
+ add_child(_tool_executor)
20
+ _tool_executor.set_editor_plugin(self)
21
+
22
+ _mcp_client.connected.connect(_on_connected)
23
+ _mcp_client.disconnected.connect(_on_disconnected)
24
+ _mcp_client.tool_requested.connect(_on_tool_requested)
25
+
26
+ _setup_status_indicator()
27
+ _mcp_client.connect_to_server()
28
+
29
+
30
+ func _exit_tree() -> void:
31
+ if _mcp_client:
32
+ if _mcp_client.connected.is_connected(_on_connected):
33
+ _mcp_client.connected.disconnect(_on_connected)
34
+ if _mcp_client.disconnected.is_connected(_on_disconnected):
35
+ _mcp_client.disconnected.disconnect(_on_disconnected)
36
+ if _mcp_client.tool_requested.is_connected(_on_tool_requested):
37
+ _mcp_client.tool_requested.disconnect(_on_tool_requested)
38
+ _mcp_client.disconnect_from_server()
39
+ _mcp_client.queue_free()
40
+ _mcp_client = null
41
+
42
+ if _tool_executor:
43
+ _tool_executor.queue_free()
44
+ _tool_executor = null
45
+
46
+ if _status_label:
47
+ remove_control_from_container(CONTAINER_TOOLBAR, _status_label)
48
+ _status_label.queue_free()
49
+ _status_label = null
50
+
51
+
52
+ func _setup_status_indicator() -> void:
53
+ _status_label = Label.new()
54
+ _status_label.text = "MCP: Connecting..."
55
+ _status_label.add_theme_color_override("font_color", Color.YELLOW)
56
+ _status_label.add_theme_font_size_override("font_size", 12)
57
+ add_control_to_container(CONTAINER_TOOLBAR, _status_label)
58
+
59
+
60
+ func _on_connected() -> void:
61
+ if _status_label:
62
+ _status_label.text = "MCP: Connected"
63
+ _status_label.add_theme_color_override("font_color", Color.GREEN)
64
+
65
+
66
+ func _on_disconnected() -> void:
67
+ if _status_label:
68
+ _status_label.text = "MCP: Disconnected"
69
+ _status_label.add_theme_color_override("font_color", Color.RED)
70
+
71
+
72
+ func _on_tool_requested(request_id: String, tool_name: String, args: Dictionary) -> void:
73
+ if _tool_executor == null or _mcp_client == null:
74
+ return
75
+
76
+ var result: Dictionary = _tool_executor.execute_tool(tool_name, args)
77
+ var success: bool = result.get("ok", false)
78
+
79
+ if success:
80
+ var payload := result.duplicate(true)
81
+ payload.erase("ok")
82
+ _mcp_client.send_tool_result(request_id, true, payload, "")
83
+ else:
84
+ _mcp_client.send_tool_result(request_id, false, null, str(result.get("error", "Unknown error")))
@@ -0,0 +1,114 @@
1
+ @tool
2
+ extends Node
3
+ class_name MCPToolExecutor
4
+
5
+ var _editor_plugin: EditorPlugin = null
6
+
7
+ var _scene_tools: Node = null
8
+ var _resource_tools: Node = null
9
+ var _animation_tools: Node = null
10
+
11
+ var _tool_map: Dictionary = {}
12
+ var _initialized := false
13
+
14
+
15
+ func set_editor_plugin(plugin: EditorPlugin) -> void:
16
+ _editor_plugin = plugin
17
+ _init_tools()
18
+
19
+ if _scene_tools and _scene_tools.has_method("set_editor_plugin"):
20
+ _scene_tools.set_editor_plugin(plugin)
21
+ if _resource_tools and _resource_tools.has_method("set_editor_plugin"):
22
+ _resource_tools.set_editor_plugin(plugin)
23
+ if _animation_tools and _animation_tools.has_method("set_editor_plugin"):
24
+ _animation_tools.set_editor_plugin(plugin)
25
+
26
+
27
+ func _init_tools() -> void:
28
+ if _initialized:
29
+ return
30
+ _initialized = true
31
+
32
+ var base_path: String = get_script().resource_path.get_base_dir()
33
+ var scene_tools_path := "%s/tools/scene_tools.gd" % base_path
34
+ var resource_tools_path := "%s/tools/resource_tools.gd" % base_path
35
+ var animation_tools_path := "%s/tools/animation_tools.gd" % base_path
36
+
37
+ if ResourceLoader.exists(scene_tools_path):
38
+ var scene_script: Script = load(scene_tools_path)
39
+ if scene_script:
40
+ _scene_tools = scene_script.new()
41
+ _scene_tools.name = "SceneTools"
42
+ add_child(_scene_tools)
43
+
44
+ if ResourceLoader.exists(resource_tools_path):
45
+ var resource_script: Script = load(resource_tools_path)
46
+ if resource_script:
47
+ _resource_tools = resource_script.new()
48
+ _resource_tools.name = "ResourceTools"
49
+ add_child(_resource_tools)
50
+
51
+ if ResourceLoader.exists(animation_tools_path):
52
+ var animation_script: Script = load(animation_tools_path)
53
+ if animation_script:
54
+ _animation_tools = animation_script.new()
55
+ _animation_tools.name = "AnimationTools"
56
+ add_child(_animation_tools)
57
+
58
+ _tool_map = {
59
+ # Scene tools
60
+ "create_scene": [_scene_tools, "create_scene"],
61
+ "list_scene_nodes": [_scene_tools, "list_scene_nodes"],
62
+ "add_node": [_scene_tools, "add_node"],
63
+ "delete_node": [_scene_tools, "delete_node"],
64
+ "duplicate_node": [_scene_tools, "duplicate_node"],
65
+ "reparent_node": [_scene_tools, "reparent_node"],
66
+ "set_node_properties": [_scene_tools, "set_node_properties"],
67
+ "get_node_properties": [_scene_tools, "get_node_properties"],
68
+ "load_sprite": [_scene_tools, "load_sprite"],
69
+ "save_scene": [_scene_tools, "save_scene"],
70
+ "connect_signal": [_scene_tools, "connect_signal"],
71
+ "disconnect_signal": [_scene_tools, "disconnect_signal"],
72
+ "list_connections": [_scene_tools, "list_connections"],
73
+
74
+ # Resource tools
75
+ "create_resource": [_resource_tools, "create_resource"],
76
+ "modify_resource": [_resource_tools, "modify_resource"],
77
+ "create_material": [_resource_tools, "create_material"],
78
+ "create_shader": [_resource_tools, "create_shader"],
79
+ "create_tileset": [_resource_tools, "create_tileset"],
80
+ "set_tilemap_cells": [_resource_tools, "set_tilemap_cells"],
81
+ "set_theme_color": [_resource_tools, "set_theme_color"],
82
+ "set_theme_font_size": [_resource_tools, "set_theme_font_size"],
83
+ "apply_theme_shader": [_resource_tools, "apply_theme_shader"],
84
+
85
+ # Animation tools
86
+ "create_animation": [_animation_tools, "create_animation"],
87
+ "add_animation_track": [_animation_tools, "add_animation_track"],
88
+ "create_animation_tree": [_animation_tools, "create_animation_tree"],
89
+ "add_animation_state": [_animation_tools, "add_animation_state"],
90
+ "connect_animation_states": [_animation_tools, "connect_animation_states"],
91
+ "create_navigation_region": [_animation_tools, "create_navigation_region"],
92
+ "create_navigation_agent": [_animation_tools, "create_navigation_agent"]
93
+ }
94
+
95
+
96
+ func execute_tool(tool_name: String, args: Dictionary) -> Dictionary:
97
+ if not _tool_map.has(tool_name):
98
+ return {"ok": false, "error": "Unknown tool: " + tool_name}
99
+
100
+ var handler: Array = _tool_map[tool_name]
101
+ var node: Node = handler[0]
102
+ var method: String = handler[1]
103
+
104
+ if node == null:
105
+ return {"ok": false, "error": "Tool handler unavailable: " + tool_name}
106
+
107
+ if not node.has_method(method):
108
+ return {"ok": false, "error": "Tool method not found: %s.%s" % [node.name, method]}
109
+
110
+ var result = node.call(method, args)
111
+ if result is Dictionary:
112
+ return result
113
+
114
+ return {"ok": false, "error": "Invalid tool result from: " + tool_name}