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 +144 -75
- package/build/addon/godot_mcp_editor/mcp_client.gd +161 -0
- package/build/addon/godot_mcp_editor/plugin.cfg +6 -0
- package/build/addon/godot_mcp_editor/plugin.gd +84 -0
- package/build/addon/godot_mcp_editor/tool_executor.gd +114 -0
- package/build/addon/godot_mcp_editor/tools/animation_tools.gd +502 -0
- package/build/addon/godot_mcp_editor/tools/resource_tools.gd +425 -0
- package/build/addon/godot_mcp_editor/tools/scene_tools.gd +710 -0
- package/build/gdscript_parser.js +828 -0
- package/build/godot-bridge.js +470 -0
- package/build/index.js +284 -111
- package/build/visualizer/canvas.js +832 -0
- package/build/visualizer/events.js +814 -0
- package/build/visualizer/layout.js +304 -0
- package/build/visualizer/main.js +245 -0
- package/build/visualizer/modals.js +239 -0
- package/build/visualizer/panel.js +1091 -0
- package/build/visualizer/state.js +210 -0
- package/build/visualizer/syntax.js +106 -0
- package/build/visualizer/usages.js +352 -0
- package/build/visualizer/websocket.js +85 -0
- package/build/visualizer-server.js +375 -0
- package/build/visualizer.html +6395 -0
- package/package.json +6 -3
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
|
-
|
|
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
|
|
67
|
+
## Why GoPeak
|
|
22
68
|
|
|
23
|
-
- **Real project feedback loop**: run the game,
|
|
24
|
-
- **95+ tools** across scene
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
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
|
|
31
|
-
- Teams that
|
|
32
|
-
- Debug-heavy workflows
|
|
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
|
-
##
|
|
83
|
+
## Tool Surface Model (Important)
|
|
37
84
|
|
|
38
|
-
|
|
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
|
-
|
|
91
|
+
Configure with either:
|
|
50
92
|
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
|
|
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
|
-
###
|
|
113
|
+
### A) Recommended: npx
|
|
60
114
|
|
|
61
115
|
```bash
|
|
62
|
-
npx gopeak
|
|
116
|
+
npx -y gopeak
|
|
63
117
|
```
|
|
64
118
|
|
|
65
|
-
|
|
119
|
+
### B) Global install
|
|
66
120
|
|
|
67
121
|
```bash
|
|
68
122
|
npm install -g gopeak
|
|
69
123
|
gopeak
|
|
70
124
|
```
|
|
71
125
|
|
|
72
|
-
###
|
|
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
|
-
|
|
136
|
+
GoPeak also exposes two CLI bin names:
|
|
82
137
|
|
|
83
|
-
|
|
138
|
+
- `gopeak`
|
|
139
|
+
- `godot-mcp`
|
|
84
140
|
|
|
85
|
-
|
|
141
|
+
---
|
|
86
142
|
|
|
87
|
-
|
|
143
|
+
## Addons (Recommended)
|
|
88
144
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
153
|
+
PowerShell:
|
|
103
154
|
|
|
104
|
-
```
|
|
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
|
-
|
|
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
|
|
175
|
+
### Tool families (examples)
|
|
135
176
|
|
|
136
177
|
| Area | Examples |
|
|
137
178
|
|---|---|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
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
|
+

|
|
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
|
|
204
|
+
- "Set a breakpoint at `scripts/player.gd:42`, continue execution, and show stack trace when hit."
|
|
155
205
|
|
|
156
|
-
### Runtime
|
|
206
|
+
### Runtime testing
|
|
157
207
|
- "Press `ui_accept`, move mouse to (400, 300), click, then capture a screenshot."
|
|
158
|
-
- "Inspect
|
|
208
|
+
- "Inspect live scene tree and report nodes with missing scripts or invalid references."
|
|
159
209
|
|
|
160
|
-
###
|
|
161
|
-
- "
|
|
162
|
-
- "
|
|
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
|
-
##
|
|
216
|
+
## Technical Reference
|
|
167
217
|
|
|
168
|
-
|
|
218
|
+
### Environment variables
|
|
169
219
|
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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,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}
|