gopeak 2.1.0 → 2.2.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 +224 -75
- package/build/addon/godot_mcp_editor/mcp_client.gd +178 -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/cli/check.js +77 -0
- package/build/cli/notify.js +88 -0
- package/build/cli/setup.js +115 -0
- package/build/cli/star.js +51 -0
- package/build/cli/uninstall.js +26 -0
- package/build/cli/utils.js +149 -0
- package/build/cli.js +91 -0
- package/build/gdscript_parser.js +828 -0
- package/build/godot-bridge.js +556 -0
- package/build/index.js +2761 -2064
- package/build/prompts.js +163 -0
- 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 +15 -6
package/README.md
CHANGED
|
@@ -14,108 +14,221 @@
|
|
|
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 exposes 33 core tools with 22 dynamic tool groups (78 additional tools) that activate on demand — keeping token usage low while preserving full capability.
|
|
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
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
69
|
+
- **Real project feedback loop**: run the game, inspect logs, and fix in-context.
|
|
70
|
+
- **110+ tools available** across scene/script/resource/runtime/LSP/DAP/input/assets.
|
|
71
|
+
- **Token-efficient by default**: compact tool surface (33 tools) + dynamic tool groups. Only activate what you need — no more 110-tool context bombs.
|
|
72
|
+
- **Dynamic tool groups**: search with `tool.catalog` and matching groups auto-activate. Or manually activate with `tool.groups`.
|
|
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): 33 core tools + **22 dynamic tool groups** (78 additional tools activated on demand)
|
|
88
|
+
- `full`: exposes full legacy tool list (110+)
|
|
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
|
+
### Dynamic Tool Groups (compact mode)
|
|
97
|
+
|
|
98
|
+
In `compact` mode, 78 additional tools are organized into **22 groups** that activate automatically when needed:
|
|
99
|
+
|
|
100
|
+
| Group | Tools | Description |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| `scene_advanced` | 3 | Duplicate, reparent nodes, load sprites |
|
|
103
|
+
| `uid` | 2 | UID management for resources |
|
|
104
|
+
| `import_export` | 5 | Import pipeline, reimport, validate project |
|
|
105
|
+
| `autoload` | 4 | Autoload singletons, main scene |
|
|
106
|
+
| `signal` | 2 | Disconnect signals, list connections |
|
|
107
|
+
| `runtime` | 4 | Live scene inspection, runtime properties, metrics |
|
|
108
|
+
| `resource` | 4 | Create/modify materials, shaders, resources |
|
|
109
|
+
| `animation` | 5 | Animations, tracks, animation tree, state machine |
|
|
110
|
+
| `plugin` | 3 | Enable/disable/list editor plugins |
|
|
111
|
+
| `input` | 1 | Input action mapping |
|
|
112
|
+
| `tilemap` | 2 | TileSet and TileMap cell painting |
|
|
113
|
+
| `audio` | 4 | Audio buses, effects, volume |
|
|
114
|
+
| `navigation` | 2 | Navigation regions and agents |
|
|
115
|
+
| `theme_ui` | 3 | Theme colors, font sizes, shaders |
|
|
116
|
+
| `asset_store` | 3 | Search/download CC0 assets |
|
|
117
|
+
| `testing` | 6 | Screenshots, viewport capture, input injection |
|
|
118
|
+
| `dx_tools` | 4 | Error log, project health, find usages, scaffold |
|
|
119
|
+
| `intent_tracking` | 9 | Intent capture, decision logs, handoff briefs |
|
|
120
|
+
| `class_advanced` | 1 | Class inheritance inspection |
|
|
121
|
+
| `lsp` | 3 | GDScript completions, hover, symbols |
|
|
122
|
+
| `dap` | 6 | Breakpoints, stepping, stack traces |
|
|
123
|
+
| `version_gate` | 2 | Version validation, patch verification |
|
|
124
|
+
|
|
125
|
+
**How it works:**
|
|
126
|
+
|
|
127
|
+
1. **Auto-activation via catalog**: Search with `tool.catalog` and matching groups activate automatically.
|
|
128
|
+
> "Use `tool.catalog` with query `animation` and show relevant tools."
|
|
129
|
+
|
|
130
|
+
2. **Manual activation**: Directly activate a group with `tool.groups`.
|
|
131
|
+
> "Use `tool.groups` to activate the `dap` group for debugging."
|
|
132
|
+
|
|
133
|
+
3. **Deactivation**: Remove groups when done to reduce context.
|
|
134
|
+
> "Use `tool.groups` to reset all active groups."
|
|
135
|
+
|
|
136
|
+
The server sends `notifications/tools/list_changed` so MCP clients (Claude Code, Claude Desktop) automatically refresh the tool list.
|
|
137
|
+
|
|
138
|
+
### Don't worry about tokens
|
|
139
|
+
|
|
140
|
+
GoPeak uses **cursor-based pagination** for `tools/list` — even in `full` profile, tools are delivered in pages (default 33) instead of dumping all 110+ definitions at once. Your AI client fetches the next page only when it needs more.
|
|
141
|
+
|
|
142
|
+
Set page size with `GOPEAK_TOOLS_PAGE_SIZE`:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"env": {
|
|
147
|
+
"GOPEAK_TOOLS_PAGE_SIZE": "25"
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
54
151
|
|
|
55
152
|
---
|
|
56
153
|
|
|
57
|
-
## Installation
|
|
154
|
+
## Installation Options
|
|
58
155
|
|
|
59
|
-
###
|
|
156
|
+
### A) Recommended: npx
|
|
60
157
|
|
|
61
158
|
```bash
|
|
62
|
-
npx gopeak
|
|
159
|
+
npx -y gopeak
|
|
63
160
|
```
|
|
64
161
|
|
|
65
|
-
|
|
162
|
+
### B) Global install
|
|
66
163
|
|
|
67
164
|
```bash
|
|
68
165
|
npm install -g gopeak
|
|
69
166
|
gopeak
|
|
70
167
|
```
|
|
71
168
|
|
|
72
|
-
###
|
|
169
|
+
### C) From source
|
|
73
170
|
|
|
74
171
|
```bash
|
|
75
172
|
git clone https://github.com/HaD0Yun/godot-mcp.git
|
|
76
173
|
cd godot-mcp
|
|
77
174
|
npm install
|
|
78
175
|
npm run build
|
|
176
|
+
node build/index.js
|
|
79
177
|
```
|
|
80
178
|
|
|
81
|
-
|
|
179
|
+
GoPeak also exposes two CLI bin names:
|
|
82
180
|
|
|
83
|
-
|
|
181
|
+
- `gopeak`
|
|
182
|
+
- `godot-mcp`
|
|
84
183
|
|
|
85
|
-
|
|
184
|
+
---
|
|
86
185
|
|
|
87
|
-
|
|
186
|
+
## CI
|
|
88
187
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
188
|
+
GitHub Actions runs on push/PR and executes:
|
|
189
|
+
|
|
190
|
+
1. `npm run build`
|
|
191
|
+
2. `npx tsc --noEmit`
|
|
192
|
+
3. `npm run test:ci`
|
|
193
|
+
|
|
194
|
+
Run the same checks locally:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
npm run ci
|
|
100
198
|
```
|
|
101
199
|
|
|
102
|
-
|
|
200
|
+
---
|
|
103
201
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
"GODOT_PATH": "/path/to/godot"
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
202
|
+
## Versioning & Release
|
|
203
|
+
|
|
204
|
+
Use the built-in bump script to keep `package.json` and `server.json` in sync:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
node scripts/bump-version.mjs patch
|
|
208
|
+
node scripts/bump-version.mjs minor --dry-run
|
|
116
209
|
```
|
|
117
210
|
|
|
118
|
-
|
|
211
|
+
Full release checklist: [`docs/release-process.md`](docs/release-process.md).
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Addons (Recommended)
|
|
216
|
+
|
|
217
|
+
### Auto Reload + Editor Bridge + Runtime Addon installer
|
|
218
|
+
|
|
219
|
+
Install in your Godot project folder:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
curl -sL https://raw.githubusercontent.com/HaD0Yun/godot-mcp/main/install-addon.sh | bash
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
PowerShell:
|
|
226
|
+
|
|
227
|
+
```powershell
|
|
228
|
+
iwr https://raw.githubusercontent.com/HaD0Yun/godot-mcp/main/install-addon.ps1 -UseBasicParsing | iex
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Then enable plugins in **Project Settings → Plugins** (especially `godot_mcp_editor` for bridge-backed scene/resource tools).
|
|
119
232
|
|
|
120
233
|
---
|
|
121
234
|
|
|
@@ -131,18 +244,27 @@ Use one of these depending on your client.
|
|
|
131
244
|
- **Input + screenshots**: keyboard/mouse/action injection and viewport capture
|
|
132
245
|
- **Asset library**: search/fetch CC0 assets (Poly Haven, AmbientCG, Kenney)
|
|
133
246
|
|
|
134
|
-
### Tool
|
|
247
|
+
### Tool families (examples)
|
|
135
248
|
|
|
136
249
|
| Area | Examples |
|
|
137
250
|
|---|---|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
251
|
+
| Project | `project.list`, `project.info`, `editor.run` |
|
|
252
|
+
| Scene/Node | `scene.create`, `scene.node.add`, `set_node_properties` |
|
|
253
|
+
| Script | `script.create`, `script.modify`, `script.info` |
|
|
254
|
+
| Runtime | `runtime.status`, `inspect_runtime_tree`, `call_runtime_method` |
|
|
255
|
+
| LSP/DAP | `lsp.diagnostics`, `lsp_get_hover`, `dap_set_breakpoint`, `dap.output` |
|
|
142
256
|
| Input/Visual | `inject_key`, `inject_mouse_click`, `capture_screenshot` |
|
|
143
257
|
|
|
144
258
|
---
|
|
145
259
|
|
|
260
|
+
## Project Visualizer
|
|
261
|
+
|
|
262
|
+
Visualize your entire project architecture with `visualizer.map` (`map_project` legacy). Scripts are grouped by folder structure into color-coded categories.
|
|
263
|
+
|
|
264
|
+

|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
146
268
|
## Quick Prompt Examples
|
|
147
269
|
|
|
148
270
|
### Build
|
|
@@ -151,27 +273,50 @@ Use one of these depending on your client.
|
|
|
151
273
|
|
|
152
274
|
### Debug
|
|
153
275
|
- "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
|
|
276
|
+
- "Set a breakpoint at `scripts/player.gd:42`, continue execution, and show stack trace when hit."
|
|
155
277
|
|
|
156
|
-
### Runtime
|
|
278
|
+
### Runtime testing
|
|
157
279
|
- "Press `ui_accept`, move mouse to (400, 300), click, then capture a screenshot."
|
|
158
|
-
- "Inspect
|
|
280
|
+
- "Inspect live scene tree and report nodes with missing scripts or invalid references."
|
|
159
281
|
|
|
160
|
-
###
|
|
161
|
-
- "
|
|
162
|
-
- "
|
|
282
|
+
### Discovery & dynamic groups
|
|
283
|
+
- "Use `tool.catalog` with query `tilemap` and list the most relevant tools."
|
|
284
|
+
- "Activate the `dap` tool group for breakpoint debugging with `tool.groups`."
|
|
285
|
+
- "Find import pipeline tools with `tool.catalog` query `import` and run the best one for texture settings."
|
|
286
|
+
- "Reset all active tool groups with `tool.groups` to reduce context."
|
|
163
287
|
|
|
164
288
|
---
|
|
165
289
|
|
|
166
|
-
##
|
|
290
|
+
## Technical Reference
|
|
167
291
|
|
|
168
|
-
|
|
292
|
+
### Environment variables
|
|
169
293
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
294
|
+
| Name | Purpose | Default |
|
|
295
|
+
|---|---|---|
|
|
296
|
+
| `GOPEAK_TOOL_PROFILE` | Tool exposure profile: `compact`, `full`, `legacy` | `compact` |
|
|
297
|
+
| `MCP_TOOL_PROFILE` | Fallback profile env alias | `compact` |
|
|
298
|
+
| `GODOT_PATH` | Explicit Godot executable path | auto-detect |
|
|
299
|
+
| `GODOT_BRIDGE_PORT` | Bridge/Visualizer HTTP+WS port override (aliases: `MCP_BRIDGE_PORT`, `GOPEAK_BRIDGE_PORT`) | `6505` |
|
|
300
|
+
| `DEBUG` | Enable server debug logs (`true`/`false`) | `false` |
|
|
301
|
+
| `LOG_MODE` | Recording mode: `lite` or `full` | `lite` |
|
|
302
|
+
| `GOPEAK_TOOLS_PAGE_SIZE` | Number of tools per `tools/list` page (pagination) | `33` |
|
|
303
|
+
| `GOPEAK_BRIDGE_PORT` | Port for unified bridge/visualizer server | `6505` |
|
|
304
|
+
| `GOPEAK_BRIDGE_HOST` | Bind host for bridge/visualizer server | `127.0.0.1` |
|
|
305
|
+
|
|
306
|
+
### Ports
|
|
307
|
+
|
|
308
|
+
| Port | Service |
|
|
309
|
+
|---|---|
|
|
310
|
+
| `6505` (default) | Unified Godot Bridge + Visualizer server (+ `/health`, `/mcp`) on loopback by default |
|
|
311
|
+
| `6005` | Godot LSP |
|
|
312
|
+
| `6006` | Godot DAP |
|
|
313
|
+
| `7777` | Runtime addon command socket (only needed for runtime tools) |
|
|
314
|
+
|
|
315
|
+
### Minimal port profiles
|
|
173
316
|
|
|
174
|
-
|
|
317
|
+
- **Core editing only**: bridge port (`GODOT_BRIDGE_PORT`, default `6505`)
|
|
318
|
+
- **Core + runtime actions (screenshots/input/runtime inspect)**: bridge port + `7777`
|
|
319
|
+
- **Full debugging + diagnostics**: bridge port + `6005` + `6006` + `7777`
|
|
175
320
|
|
|
176
321
|
---
|
|
177
322
|
|
|
@@ -180,12 +325,15 @@ Then enable plugin in **Project Settings → Plugins**.
|
|
|
180
325
|
- **Godot not found** → set `GODOT_PATH`
|
|
181
326
|
- **No MCP tools visible** → restart your MCP client
|
|
182
327
|
- **Project path invalid** → confirm `project.godot` exists
|
|
183
|
-
- **Runtime tools not working** → install/enable runtime addon
|
|
328
|
+
- **Runtime tools not working** → install/enable runtime addon plugin
|
|
329
|
+
- **Need a tool that is not visible** → run `tool.catalog` to search and auto-activate matching groups, or use `tool.groups` to activate a specific group
|
|
184
330
|
|
|
185
331
|
---
|
|
186
332
|
|
|
187
333
|
## Docs & Project Links
|
|
188
334
|
|
|
335
|
+
- [Architecture (MCP Platform Direction)](docs/architecture.md)
|
|
336
|
+
- [Platform Roadmap (P1/P2/P3)](docs/platform-roadmap.md)
|
|
189
337
|
- [CHANGELOG](CHANGELOG.md)
|
|
190
338
|
- [ROADMAP](ROADMAP.md)
|
|
191
339
|
- [CONTRIBUTING](CONTRIBUTING.md)
|
|
@@ -200,3 +348,4 @@ MIT — see [LICENSE](LICENSE).
|
|
|
200
348
|
|
|
201
349
|
- Original MCP server by [Coding-Solo](https://github.com/Coding-Solo/godot-mcp)
|
|
202
350
|
- GoPeak enhancements by [HaD0Yun](https://github.com/HaD0Yun)
|
|
351
|
+
- Project visualizer inspired by [tomyud1/godot-mcp](https://github.com/tomyud1/godot-mcp)
|
|
@@ -0,0 +1,178 @@
|
|
|
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 = "") -> void:
|
|
64
|
+
server_url = _resolve_server_url(url)
|
|
65
|
+
_should_reconnect = true
|
|
66
|
+
_current_reconnect_delay = RECONNECT_DELAY
|
|
67
|
+
_attempt_connection()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
func _resolve_server_url(explicit_url: String) -> String:
|
|
71
|
+
if explicit_url != "":
|
|
72
|
+
return explicit_url
|
|
73
|
+
|
|
74
|
+
var env_keys := ["GODOT_BRIDGE_PORT", "MCP_BRIDGE_PORT", "GOPEAK_BRIDGE_PORT"]
|
|
75
|
+
for key in env_keys:
|
|
76
|
+
var raw := OS.get_environment(key)
|
|
77
|
+
if raw == "":
|
|
78
|
+
continue
|
|
79
|
+
if raw.is_valid_int():
|
|
80
|
+
var port := int(raw)
|
|
81
|
+
if port >= 1 and port <= 65535:
|
|
82
|
+
return "ws://127.0.0.1:%d/godot" % port
|
|
83
|
+
|
|
84
|
+
return DEFAULT_URL
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
func disconnect_from_server() -> void:
|
|
88
|
+
_should_reconnect = false
|
|
89
|
+
if _reconnect_timer:
|
|
90
|
+
_reconnect_timer.stop()
|
|
91
|
+
if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
|
92
|
+
socket.close()
|
|
93
|
+
_is_connected = false
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
func _attempt_connection() -> void:
|
|
97
|
+
if socket.get_ready_state() != WebSocketPeer.STATE_CLOSED:
|
|
98
|
+
socket.close()
|
|
99
|
+
|
|
100
|
+
var err := socket.connect_to_url(server_url)
|
|
101
|
+
if err != OK:
|
|
102
|
+
push_error("[MCP Editor] Failed to connect: %s" % err)
|
|
103
|
+
_schedule_reconnect()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
func _handle_connect() -> void:
|
|
107
|
+
_is_connected = true
|
|
108
|
+
_current_reconnect_delay = RECONNECT_DELAY
|
|
109
|
+
|
|
110
|
+
_send_message({
|
|
111
|
+
"type": "godot_ready",
|
|
112
|
+
"project_path": _project_path
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
connected.emit()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
func _handle_disconnect() -> void:
|
|
119
|
+
_is_connected = false
|
|
120
|
+
disconnected.emit()
|
|
121
|
+
|
|
122
|
+
if _should_reconnect:
|
|
123
|
+
_schedule_reconnect()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
func _schedule_reconnect() -> void:
|
|
127
|
+
if _reconnect_timer == null:
|
|
128
|
+
return
|
|
129
|
+
_reconnect_timer.start(_current_reconnect_delay)
|
|
130
|
+
_current_reconnect_delay = min(_current_reconnect_delay * 2.0, MAX_RECONNECT_DELAY)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
func _on_reconnect_timer() -> void:
|
|
134
|
+
_attempt_connection()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
func _handle_message(json_string: String) -> void:
|
|
138
|
+
var message = JSON.parse_string(json_string)
|
|
139
|
+
if message == null:
|
|
140
|
+
push_error("[MCP Editor] Failed to parse message: %s" % json_string)
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
match message.get("type", ""):
|
|
144
|
+
"ping":
|
|
145
|
+
_send_message({"type": "pong"})
|
|
146
|
+
|
|
147
|
+
"tool_invoke":
|
|
148
|
+
var request_id: String = message.get("id", "")
|
|
149
|
+
var tool_name: String = message.get("tool", "")
|
|
150
|
+
var args: Dictionary = message.get("args", {})
|
|
151
|
+
tool_requested.emit(request_id, tool_name, args)
|
|
152
|
+
|
|
153
|
+
_:
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
func send_tool_result(request_id: String, success: bool, result = null, error: String = "") -> void:
|
|
158
|
+
var response := {
|
|
159
|
+
"type": "tool_result",
|
|
160
|
+
"id": request_id,
|
|
161
|
+
"success": success
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if success:
|
|
165
|
+
response["result"] = result
|
|
166
|
+
else:
|
|
167
|
+
response["error"] = error
|
|
168
|
+
|
|
169
|
+
_send_message(response)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
func _send_message(message: Dictionary) -> void:
|
|
173
|
+
if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
|
174
|
+
socket.send_text(JSON.stringify(message))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
func is_connected_to_server() -> bool:
|
|
178
|
+
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")))
|