@baixfeng/godot-mcp-cli 1.0.11
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 +187 -0
- package/addons/godot_mcp/command_handler.gd +161 -0
- package/addons/godot_mcp/command_handler.gd.uid +1 -0
- package/addons/godot_mcp/commands/base_command_processor.gd +221 -0
- package/addons/godot_mcp/commands/base_command_processor.gd.uid +1 -0
- package/addons/godot_mcp/commands/debugger_commands.gd +221 -0
- package/addons/godot_mcp/commands/debugger_commands.gd.uid +1 -0
- package/addons/godot_mcp/commands/editor_commands.gd +237 -0
- package/addons/godot_mcp/commands/editor_commands.gd.uid +1 -0
- package/addons/godot_mcp/commands/editor_script_commands.gd +365 -0
- package/addons/godot_mcp/commands/editor_script_commands.gd.uid +1 -0
- package/addons/godot_mcp/commands/input_commands.gd +337 -0
- package/addons/godot_mcp/commands/input_commands.gd.uid +1 -0
- package/addons/godot_mcp/commands/node_commands.gd +222 -0
- package/addons/godot_mcp/commands/node_commands.gd.uid +1 -0
- package/addons/godot_mcp/commands/project_commands.gd +298 -0
- package/addons/godot_mcp/commands/project_commands.gd.uid +1 -0
- package/addons/godot_mcp/commands/scene_commands.gd +337 -0
- package/addons/godot_mcp/commands/scene_commands.gd.uid +1 -0
- package/addons/godot_mcp/commands/script_commands.gd +349 -0
- package/addons/godot_mcp/commands/script_commands.gd.uid +1 -0
- package/addons/godot_mcp/mcp_asset_commands.gd +153 -0
- package/addons/godot_mcp/mcp_asset_commands.gd.uid +1 -0
- package/addons/godot_mcp/mcp_debug_output_publisher.gd +1669 -0
- package/addons/godot_mcp/mcp_debug_output_publisher.gd.uid +1 -0
- package/addons/godot_mcp/mcp_debugger_bridge.gd +1455 -0
- package/addons/godot_mcp/mcp_debugger_bridge.gd.uid +1 -0
- package/addons/godot_mcp/mcp_enhanced_commands.gd +1083 -0
- package/addons/godot_mcp/mcp_enhanced_commands.gd.uid +1 -0
- package/addons/godot_mcp/mcp_input_handler.gd +545 -0
- package/addons/godot_mcp/mcp_input_handler.gd.uid +1 -0
- package/addons/godot_mcp/mcp_runtime_debugger_bridge.gd +464 -0
- package/addons/godot_mcp/mcp_runtime_debugger_bridge.gd.uid +1 -0
- package/addons/godot_mcp/mcp_script_resource_commands.gd +165 -0
- package/addons/godot_mcp/mcp_script_resource_commands.gd.uid +1 -0
- package/addons/godot_mcp/mcp_server.gd +260 -0
- package/addons/godot_mcp/mcp_server.gd.uid +1 -0
- package/addons/godot_mcp/plugin.cfg +7 -0
- package/addons/godot_mcp/runtime_debugger.gd +81 -0
- package/addons/godot_mcp/runtime_debugger.gd.uid +1 -0
- package/addons/godot_mcp/ui/mcp_panel.gd +94 -0
- package/addons/godot_mcp/ui/mcp_panel.gd.uid +1 -0
- package/addons/godot_mcp/ui/mcp_panel.tscn +96 -0
- package/addons/godot_mcp/utils/node_utils.gd +82 -0
- package/addons/godot_mcp/utils/node_utils.gd.uid +1 -0
- package/addons/godot_mcp/utils/resource_utils.gd +81 -0
- package/addons/godot_mcp/utils/resource_utils.gd.uid +1 -0
- package/addons/godot_mcp/utils/script_utils.gd +114 -0
- package/addons/godot_mcp/utils/script_utils.gd.uid +1 -0
- package/addons/godot_mcp/websocket_server.gd +197 -0
- package/addons/godot_mcp/websocket_server.gd.uid +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +561 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +156 -0
- package/dist/index.js.map +1 -0
- package/dist/resources/asset_resources.d.ts +29 -0
- package/dist/resources/asset_resources.js +145 -0
- package/dist/resources/asset_resources.js.map +1 -0
- package/dist/resources/debug_resources.d.ts +11 -0
- package/dist/resources/debug_resources.js +106 -0
- package/dist/resources/debug_resources.js.map +1 -0
- package/dist/resources/debugger_resources.d.ts +62 -0
- package/dist/resources/debugger_resources.js +201 -0
- package/dist/resources/debugger_resources.js.map +1 -0
- package/dist/resources/editor_resources.d.ts +47 -0
- package/dist/resources/editor_resources.js +155 -0
- package/dist/resources/editor_resources.js.map +1 -0
- package/dist/resources/project_resources.d.ts +33 -0
- package/dist/resources/project_resources.js +137 -0
- package/dist/resources/project_resources.js.map +1 -0
- package/dist/resources/scene_resources.d.ts +33 -0
- package/dist/resources/scene_resources.js +160 -0
- package/dist/resources/scene_resources.js.map +1 -0
- package/dist/resources/script_resources.d.ts +51 -0
- package/dist/resources/script_resources.js +203 -0
- package/dist/resources/script_resources.js.map +1 -0
- package/dist/tools/asset_tools.d.ts +5 -0
- package/dist/tools/asset_tools.js +125 -0
- package/dist/tools/asset_tools.js.map +1 -0
- package/dist/tools/debugger_tools.d.ts +2 -0
- package/dist/tools/debugger_tools.js +342 -0
- package/dist/tools/debugger_tools.js.map +1 -0
- package/dist/tools/editor_tools.d.ts +2 -0
- package/dist/tools/editor_tools.js +165 -0
- package/dist/tools/editor_tools.js.map +1 -0
- package/dist/tools/enhanced_tools.d.ts +5 -0
- package/dist/tools/enhanced_tools.js +706 -0
- package/dist/tools/enhanced_tools.js.map +1 -0
- package/dist/tools/input_tools.d.ts +2 -0
- package/dist/tools/input_tools.js +408 -0
- package/dist/tools/input_tools.js.map +1 -0
- package/dist/tools/node_tools.d.ts +5 -0
- package/dist/tools/node_tools.js +217 -0
- package/dist/tools/node_tools.js.map +1 -0
- package/dist/tools/project_tools.d.ts +5 -0
- package/dist/tools/project_tools.js +162 -0
- package/dist/tools/project_tools.js.map +1 -0
- package/dist/tools/scene_tools.d.ts +5 -0
- package/dist/tools/scene_tools.js +260 -0
- package/dist/tools/scene_tools.js.map +1 -0
- package/dist/tools/script_resource_tools.d.ts +5 -0
- package/dist/tools/script_resource_tools.js +5 -0
- package/dist/tools/script_resource_tools.js.map +1 -0
- package/dist/tools/script_tools.d.ts +5 -0
- package/dist/tools/script_tools.js +154 -0
- package/dist/tools/script_tools.js.map +1 -0
- package/dist/utils/godot_connection.d.ts +30 -0
- package/dist/utils/godot_connection.js +285 -0
- package/dist/utils/godot_connection.js.map +1 -0
- package/dist/utils/types.d.ts +16 -0
- package/dist/utils/types.js +2 -0
- package/dist/utils/types.js.map +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Godot MCP CLI
|
|
2
|
+
|
|
3
|
+
A Command Line Interface (CLI) for AI assistants to interact with Godot Engine, built on the Model Context Protocol (MCP). The CLI is the recommended way to use this tool as it saves context tokens compared to direct MCP integration.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/godot-mcp-cli)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## When to Use What
|
|
9
|
+
|
|
10
|
+
| Method | Best For | Token Usage |
|
|
11
|
+
|--------|----------|-------------|
|
|
12
|
+
| **CLI** (recommended) | AI coding assistants, scripting, automation | Low - only tool output in context |
|
|
13
|
+
| **MCP** | Direct MCP client integration | High - full protocol in context |
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
### Core Functionality
|
|
18
|
+
- **Full Godot Project Access**: AI assistants can access and modify scripts, scenes, nodes, and project resources
|
|
19
|
+
- **Flexible Scene Inspection**: Retrieve hierarchy with `get_editor_scene_structure`, including properties and scripts
|
|
20
|
+
- **Runtime Scene Inspection**: Snapshot live scene tree from running games with `get_runtime_scene_structure`
|
|
21
|
+
- **Runtime Expression Evaluation**: Execute expressions in live games using `evaluate_runtime`
|
|
22
|
+
- **Dynamic Script Access**: Read scripts via `godot://script/{path}` and metadata via `godot://script/{path}/metadata`
|
|
23
|
+
- **Script Editing Tools**: Create, edit, or template scripts directly through MCP commands
|
|
24
|
+
- **Node Management**: Create, remove, list, and inspect nodes with automatic path normalization
|
|
25
|
+
- **Scene Operations**: Create, delete, open, and save scenes; query project info and current scene state
|
|
26
|
+
- **Asset Management**: List assets by type and enumerate project files
|
|
27
|
+
- **Project Reload**: Restart editor, reload scenes, or rescan filesystem for external changes
|
|
28
|
+
- **Debug Output Access**: Snapshot logs with `get_debug_output` or tail them live via `stream_debug_output`
|
|
29
|
+
- **Stack Trace Capture**: Pull the editor's Stack Trace text or grab structured frames via `get_stack_trace_panel` / `get_stack_frames_panel`
|
|
30
|
+
- **Editor Automation**: Execute GDScript in editor context via `execute_editor_script`
|
|
31
|
+
|
|
32
|
+
### **Debugger Integration**
|
|
33
|
+
- **Breakpoint Management**: Set, remove, and list breakpoints across scripts with `debugger_set_breakpoint`
|
|
34
|
+
- **Execution Control**: Pause, resume, and step through code with `debugger_pause_execution`, `debugger_step_over`
|
|
35
|
+
- **Real-time Events**: Live notifications for breakpoint hits and execution changes
|
|
36
|
+
- **Call Stack Inspection**: Access current call stack and frame information with `debugger_get_call_stack`
|
|
37
|
+
- **Session Management**: Support for multiple debug sessions
|
|
38
|
+
- **Runtime Debugging**: Full integration with Godot's debugging system
|
|
39
|
+
- **Event-driven Architecture**: Receive breakpoint hits and execution state changes in real-time
|
|
40
|
+
|
|
41
|
+
### **Input Simulation**
|
|
42
|
+
- **Action Simulation**: Press, release, and tap input actions (`simulate_action_press`, `simulate_action_tap`)
|
|
43
|
+
- **Mouse Control**: Click, move, and drag operations (`simulate_mouse_click`, `simulate_drag`)
|
|
44
|
+
- **Keyboard Input**: Simulate key presses with modifier support (`simulate_key_press`)
|
|
45
|
+
- **Input Sequences**: Execute complex input combos with precise timing (`simulate_input_sequence`)
|
|
46
|
+
- **Action Discovery**: List all available input actions in the project (`get_input_actions`)
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
### Option 1: Install via npm (Recommended)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install -g godot-mcp-cli
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Option 2: Build from Source
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
git clone https://github.com/nguyenchiencong/godot-mcp-cli.git
|
|
60
|
+
cd godot-mcp-cli/server
|
|
61
|
+
npm install
|
|
62
|
+
npm run build
|
|
63
|
+
npm link
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Quick Setup
|
|
67
|
+
|
|
68
|
+
### 1. Install the Addon to Your Godot Project
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
godot-mcp install-addon "path/to/your/project"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Or manually copy the `addons/godot_mcp` folder to your Godot project's `addons` directory.
|
|
75
|
+
|
|
76
|
+
### 2. Enable the Plugin in Godot
|
|
77
|
+
|
|
78
|
+
1. Open your project in Godot
|
|
79
|
+
2. Go to Project > Project Settings > Plugins
|
|
80
|
+
3. Enable the "Godot MCP" plugin
|
|
81
|
+
|
|
82
|
+
## Using the CLI (Recommended)
|
|
83
|
+
|
|
84
|
+
The CLI is the most efficient way for AI assistants to interact with Godot. It consumes fewer tokens than the MCP protocol.
|
|
85
|
+
|
|
86
|
+
### Basic Commands
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# List all available tools
|
|
90
|
+
godot-mcp --list-tools
|
|
91
|
+
|
|
92
|
+
# Get help for a specific tool
|
|
93
|
+
godot-mcp --help get_debug_output
|
|
94
|
+
|
|
95
|
+
# Execute tools
|
|
96
|
+
godot-mcp get_debug_output
|
|
97
|
+
godot-mcp get_project_info
|
|
98
|
+
godot-mcp run_project
|
|
99
|
+
|
|
100
|
+
# With arguments
|
|
101
|
+
godot-mcp debugger_set_breakpoint --script-path res://test_debugger.gd --line 42
|
|
102
|
+
godot-mcp simulate_action_tap --action ui_accept
|
|
103
|
+
godot-mcp simulate_mouse_click --x 400 --y 300
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### CLI Examples
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Scene and node operations
|
|
110
|
+
godot-mcp get_current_scene
|
|
111
|
+
godot-mcp get_editor_scene_structure --include-properties true
|
|
112
|
+
godot-mcp list_nodes --parent-path "."
|
|
113
|
+
|
|
114
|
+
# Debugging
|
|
115
|
+
godot-mcp run_project
|
|
116
|
+
godot-mcp debugger_get_current_state
|
|
117
|
+
godot-mcp debugger_pause_execution
|
|
118
|
+
godot-mcp debugger_resume_execution
|
|
119
|
+
|
|
120
|
+
# Input simulation (requires running game)
|
|
121
|
+
godot-mcp get_input_actions
|
|
122
|
+
godot-mcp simulate_action_tap --action "ui_accept"
|
|
123
|
+
godot-mcp simulate_key_press --key "SPACE"
|
|
124
|
+
|
|
125
|
+
# Reload operations
|
|
126
|
+
godot-mcp rescan_filesystem
|
|
127
|
+
godot-mcp reload_scene
|
|
128
|
+
godot-mcp reload_project
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
For more CLI options, see the [CLI Documentation](docs/cli.md).
|
|
132
|
+
|
|
133
|
+
## Using the MCP Protocol
|
|
134
|
+
|
|
135
|
+
For direct MCP client integration, add this configuration:
|
|
136
|
+
|
|
137
|
+
### STDIO Transport (after npm install -g)
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"mcpServers": {
|
|
141
|
+
"godot-mcp": {
|
|
142
|
+
"command": "godot-mcp",
|
|
143
|
+
"env": { "MCP_TRANSPORT": "stdio" }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### STDIO Transport (from source)
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"mcpServers": {
|
|
153
|
+
"godot-mcp": {
|
|
154
|
+
"command": "node",
|
|
155
|
+
"args": ["path/to/godot-mcp-cli/server/dist/index.js"],
|
|
156
|
+
"env": { "MCP_TRANSPORT": "stdio" }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### SSE Transport
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"mcpServers": {
|
|
166
|
+
"godot-mcp": {
|
|
167
|
+
"url": "http://localhost:8083/sse"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Documentation
|
|
174
|
+
|
|
175
|
+
- [Installation Guide](docs/installation-guide.md)
|
|
176
|
+
- [Command Reference](docs/command-reference.md)
|
|
177
|
+
- [Architecture](docs/architecture.md)
|
|
178
|
+
- [CLI Usage](docs/cli.md)
|
|
179
|
+
- [Tool Prompt Guide](docs/tool-prompt-guide.md)
|
|
180
|
+
|
|
181
|
+
## Contributing
|
|
182
|
+
|
|
183
|
+
Contributions are welcome! Please feel free to submit a Pull Request to the [GitHub repository](https://github.com/nguyenchiencong/godot-mcp-cli).
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
class_name MCPCommandHandler
|
|
3
|
+
extends Node
|
|
4
|
+
|
|
5
|
+
var _websocket_server
|
|
6
|
+
var _command_processors = []
|
|
7
|
+
|
|
8
|
+
func _ready():
|
|
9
|
+
print("Command handler initializing...")
|
|
10
|
+
await get_tree().process_frame
|
|
11
|
+
_websocket_server = get_parent()
|
|
12
|
+
print("WebSocket server reference set: ", _websocket_server)
|
|
13
|
+
|
|
14
|
+
# Initialize command processors
|
|
15
|
+
_initialize_command_processors()
|
|
16
|
+
|
|
17
|
+
print("Command handler initialized and ready to process commands")
|
|
18
|
+
|
|
19
|
+
func _initialize_command_processors():
|
|
20
|
+
# Create and add all required command processors
|
|
21
|
+
var node_commands = MCPNodeCommands.new()
|
|
22
|
+
var script_commands = MCPScriptCommands.new()
|
|
23
|
+
var scene_commands = MCPSceneCommands.new()
|
|
24
|
+
var project_commands = MCPProjectCommands.new()
|
|
25
|
+
var editor_commands = MCPEditorCommands.new()
|
|
26
|
+
var editor_script_commands = MCPEditorScriptCommands.new()
|
|
27
|
+
var debugger_commands = MCPDebuggerCommands.new()
|
|
28
|
+
var input_commands = MCPInputCommands.new()
|
|
29
|
+
|
|
30
|
+
# Set server reference for all processors
|
|
31
|
+
node_commands._websocket_server = _websocket_server
|
|
32
|
+
script_commands._websocket_server = _websocket_server
|
|
33
|
+
scene_commands._websocket_server = _websocket_server
|
|
34
|
+
project_commands._websocket_server = _websocket_server
|
|
35
|
+
editor_commands._websocket_server = _websocket_server
|
|
36
|
+
editor_script_commands._websocket_server = _websocket_server
|
|
37
|
+
debugger_commands._websocket_server = _websocket_server
|
|
38
|
+
input_commands._websocket_server = _websocket_server
|
|
39
|
+
|
|
40
|
+
# Add them to our processor list
|
|
41
|
+
_command_processors.append(node_commands)
|
|
42
|
+
_command_processors.append(script_commands)
|
|
43
|
+
_command_processors.append(scene_commands)
|
|
44
|
+
_command_processors.append(project_commands)
|
|
45
|
+
_command_processors.append(editor_commands)
|
|
46
|
+
_command_processors.append(editor_script_commands)
|
|
47
|
+
_command_processors.append(debugger_commands)
|
|
48
|
+
_command_processors.append(input_commands)
|
|
49
|
+
|
|
50
|
+
# Try to load optional command classes
|
|
51
|
+
var script_resource_commands = _try_load_optional_command("res://addons/godot_mcp/mcp_script_resource_commands.gd")
|
|
52
|
+
var enhanced_commands = _try_load_optional_command("res://addons/godot_mcp/mcp_enhanced_commands.gd")
|
|
53
|
+
var asset_commands = _try_load_optional_command("res://addons/godot_mcp/mcp_asset_commands.gd")
|
|
54
|
+
|
|
55
|
+
# Add required processors as children for proper lifecycle management
|
|
56
|
+
add_child(node_commands)
|
|
57
|
+
add_child(script_commands)
|
|
58
|
+
add_child(scene_commands)
|
|
59
|
+
add_child(project_commands)
|
|
60
|
+
add_child(editor_commands)
|
|
61
|
+
add_child(editor_script_commands)
|
|
62
|
+
add_child(debugger_commands)
|
|
63
|
+
add_child(input_commands)
|
|
64
|
+
|
|
65
|
+
print("Command processors initialized:")
|
|
66
|
+
print("- Node Commands")
|
|
67
|
+
print("- Script Commands")
|
|
68
|
+
print("- Scene Commands")
|
|
69
|
+
print("- Project Commands")
|
|
70
|
+
print("- Editor Commands")
|
|
71
|
+
print("- Editor Script Commands")
|
|
72
|
+
print("- Debugger Commands")
|
|
73
|
+
print("- Input Commands")
|
|
74
|
+
|
|
75
|
+
if script_resource_commands:
|
|
76
|
+
print("- Script Resource Commands")
|
|
77
|
+
if enhanced_commands:
|
|
78
|
+
print("- Enhanced Commands")
|
|
79
|
+
if asset_commands:
|
|
80
|
+
print("- Asset Commands")
|
|
81
|
+
|
|
82
|
+
func _try_load_optional_command(path: String) -> Node:
|
|
83
|
+
if FileAccess.file_exists(path):
|
|
84
|
+
var script = load(path)
|
|
85
|
+
if script:
|
|
86
|
+
var command = Node.new()
|
|
87
|
+
command.set_script(script)
|
|
88
|
+
command._websocket_server = _websocket_server
|
|
89
|
+
_command_processors.append(command)
|
|
90
|
+
add_child(command)
|
|
91
|
+
return command
|
|
92
|
+
return null
|
|
93
|
+
|
|
94
|
+
func _handle_command(client_id: int, command: Dictionary) -> void:
|
|
95
|
+
var command_type = command.get("type", "")
|
|
96
|
+
var params = command.get("params", {})
|
|
97
|
+
var command_id = command.get("commandId", "")
|
|
98
|
+
|
|
99
|
+
print("Processing command: %s" % command_type)
|
|
100
|
+
|
|
101
|
+
# Special handling for enhanced commands
|
|
102
|
+
var enhanced_commands = [
|
|
103
|
+
"get_editor_scene_structure",
|
|
104
|
+
"get_runtime_scene_structure",
|
|
105
|
+
"get_debug_output",
|
|
106
|
+
"get_editor_errors",
|
|
107
|
+
"get_stack_trace_panel",
|
|
108
|
+
"get_stack_frames_panel",
|
|
109
|
+
"evaluate_runtime",
|
|
110
|
+
"clear_debug_output",
|
|
111
|
+
"clear_editor_errors",
|
|
112
|
+
"subscribe_debug_output",
|
|
113
|
+
"unsubscribe_debug_output",
|
|
114
|
+
"update_node_transform"
|
|
115
|
+
]
|
|
116
|
+
if command_type in enhanced_commands:
|
|
117
|
+
# Try to find enhanced commands processor first
|
|
118
|
+
for processor in _command_processors:
|
|
119
|
+
if processor.get_script() and processor.get_script().resource_path.ends_with("mcp_enhanced_commands.gd"):
|
|
120
|
+
var handled = await _call_processor(processor, client_id, command_type, params, command_id)
|
|
121
|
+
if handled:
|
|
122
|
+
print("Command %s handled by Enhanced Commands processor" % command_type)
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
# Try each processor until one handles the command
|
|
126
|
+
for processor in _command_processors:
|
|
127
|
+
var handled = await _call_processor(processor, client_id, command_type, params, command_id)
|
|
128
|
+
if handled:
|
|
129
|
+
print("Command %s handled by %s" % [command_type, processor.get_class()])
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
# If no processor handled the command, send an error
|
|
133
|
+
_send_error(client_id, "Unknown command: %s" % command_type, command_id)
|
|
134
|
+
|
|
135
|
+
func _send_error(client_id: int, message: String, command_id: String) -> void:
|
|
136
|
+
var response = {
|
|
137
|
+
"status": "error",
|
|
138
|
+
"message": message
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if not command_id.is_empty():
|
|
142
|
+
response["commandId"] = command_id
|
|
143
|
+
|
|
144
|
+
_websocket_server.send_response(client_id, response)
|
|
145
|
+
print("Error: %s" % message)
|
|
146
|
+
|
|
147
|
+
func _processor_requires_await(processor: Node) -> bool:
|
|
148
|
+
if processor is MCPDebuggerCommands:
|
|
149
|
+
return true
|
|
150
|
+
if processor is MCPInputCommands:
|
|
151
|
+
return true
|
|
152
|
+
if processor.get_script():
|
|
153
|
+
var path := String(processor.get_script().resource_path)
|
|
154
|
+
if path.ends_with("mcp_enhanced_commands.gd"):
|
|
155
|
+
return true
|
|
156
|
+
return false
|
|
157
|
+
|
|
158
|
+
func _call_processor(processor: Node, client_id: int, command_type: String, params: Dictionary, command_id: String) -> bool:
|
|
159
|
+
if _processor_requires_await(processor):
|
|
160
|
+
return await processor.process_command(client_id, command_type, params, command_id)
|
|
161
|
+
return processor.process_command(client_id, command_type, params, command_id)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://hcu70348uj2f
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
class_name MCPBaseCommandProcessor
|
|
3
|
+
extends Node
|
|
4
|
+
|
|
5
|
+
# Signal emitted when a command has completed processing
|
|
6
|
+
signal command_completed(client_id, command_type, result, command_id)
|
|
7
|
+
|
|
8
|
+
# Reference to the server - passed by the command handler
|
|
9
|
+
var _websocket_server = null
|
|
10
|
+
|
|
11
|
+
# Must be implemented by subclasses
|
|
12
|
+
func process_command(client_id: int, command_type: String, params: Dictionary, command_id: String) -> bool:
|
|
13
|
+
push_error("BaseCommandProcessor.process_command called directly")
|
|
14
|
+
return false
|
|
15
|
+
|
|
16
|
+
# Helper functions common to all command processors
|
|
17
|
+
func _send_success(client_id: int, result: Dictionary, command_id: String) -> void:
|
|
18
|
+
var response = {
|
|
19
|
+
"status": "success",
|
|
20
|
+
"result": result
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if not command_id.is_empty():
|
|
24
|
+
response["commandId"] = command_id
|
|
25
|
+
|
|
26
|
+
# Emit the signal for local processing (useful for testing)
|
|
27
|
+
command_completed.emit(client_id, "success", result, command_id)
|
|
28
|
+
|
|
29
|
+
# Send to websocket if available
|
|
30
|
+
if _websocket_server:
|
|
31
|
+
_websocket_server.send_response(client_id, response)
|
|
32
|
+
|
|
33
|
+
func _send_error(client_id: int, message: String, command_id: String) -> void:
|
|
34
|
+
var response = {
|
|
35
|
+
"status": "error",
|
|
36
|
+
"message": message
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if not command_id.is_empty():
|
|
40
|
+
response["commandId"] = command_id
|
|
41
|
+
|
|
42
|
+
# Emit the signal for local processing (useful for testing)
|
|
43
|
+
var error_result = {"error": message}
|
|
44
|
+
command_completed.emit(client_id, "error", error_result, command_id)
|
|
45
|
+
|
|
46
|
+
# Send to websocket if available
|
|
47
|
+
if _websocket_server:
|
|
48
|
+
_websocket_server.send_response(client_id, response)
|
|
49
|
+
print("Error: %s" % message)
|
|
50
|
+
|
|
51
|
+
# Common utility methods
|
|
52
|
+
func _get_editor_node(path: String) -> Node:
|
|
53
|
+
var plugin = Engine.get_meta("GodotMCPPlugin")
|
|
54
|
+
if not plugin:
|
|
55
|
+
print("GodotMCPPlugin not found in Engine metadata")
|
|
56
|
+
return null
|
|
57
|
+
|
|
58
|
+
var editor_interface = plugin.get_editor_interface()
|
|
59
|
+
var edited_scene_root = editor_interface.get_edited_scene_root()
|
|
60
|
+
|
|
61
|
+
if not edited_scene_root:
|
|
62
|
+
print("No edited scene found")
|
|
63
|
+
return null
|
|
64
|
+
|
|
65
|
+
var normalized_path = _normalize_node_path(path)
|
|
66
|
+
if normalized_path.is_empty():
|
|
67
|
+
return edited_scene_root
|
|
68
|
+
|
|
69
|
+
var node = edited_scene_root.get_node_or_null(normalized_path)
|
|
70
|
+
if node:
|
|
71
|
+
return node
|
|
72
|
+
|
|
73
|
+
var root_name = edited_scene_root.name
|
|
74
|
+
if normalized_path == root_name:
|
|
75
|
+
return edited_scene_root
|
|
76
|
+
|
|
77
|
+
var root_prefix = root_name + "/"
|
|
78
|
+
if normalized_path.begins_with(root_prefix):
|
|
79
|
+
var trimmed_path = normalized_path.substr(root_prefix.length())
|
|
80
|
+
if trimmed_path.is_empty():
|
|
81
|
+
return edited_scene_root
|
|
82
|
+
node = edited_scene_root.get_node_or_null(trimmed_path)
|
|
83
|
+
if node:
|
|
84
|
+
return node
|
|
85
|
+
|
|
86
|
+
return null
|
|
87
|
+
|
|
88
|
+
# Enhanced version of _get_editor_node to improve node path resolution
|
|
89
|
+
func _get_editor_node_enhanced(path: String) -> Node:
|
|
90
|
+
# First try the standard method
|
|
91
|
+
var node = _get_editor_node(path)
|
|
92
|
+
if node:
|
|
93
|
+
return node
|
|
94
|
+
|
|
95
|
+
# If not found, try additional resolution methods
|
|
96
|
+
var plugin = Engine.get_meta("GodotMCPPlugin")
|
|
97
|
+
if not plugin:
|
|
98
|
+
return null
|
|
99
|
+
|
|
100
|
+
var editor_interface = plugin.get_editor_interface()
|
|
101
|
+
var edited_scene_root = editor_interface.get_edited_scene_root()
|
|
102
|
+
|
|
103
|
+
if not edited_scene_root:
|
|
104
|
+
return null
|
|
105
|
+
|
|
106
|
+
var normalized_path = _normalize_node_path(path)
|
|
107
|
+
if normalized_path.is_empty():
|
|
108
|
+
return edited_scene_root
|
|
109
|
+
|
|
110
|
+
var lower_path = normalized_path.to_lower()
|
|
111
|
+
var root_name_lower = edited_scene_root.name.to_lower()
|
|
112
|
+
|
|
113
|
+
if lower_path == root_name_lower:
|
|
114
|
+
return edited_scene_root
|
|
115
|
+
|
|
116
|
+
var parts = normalized_path.split("/")
|
|
117
|
+
if parts.size() > 1 and parts[0].to_lower() == root_name_lower:
|
|
118
|
+
var sub_path = ""
|
|
119
|
+
for i in range(1, parts.size()):
|
|
120
|
+
if i > 1:
|
|
121
|
+
sub_path += "/"
|
|
122
|
+
sub_path += parts[i]
|
|
123
|
+
|
|
124
|
+
if sub_path.is_empty():
|
|
125
|
+
return edited_scene_root
|
|
126
|
+
|
|
127
|
+
var node_with_root_prefix = edited_scene_root.get_node_or_null(sub_path)
|
|
128
|
+
if node_with_root_prefix:
|
|
129
|
+
return node_with_root_prefix
|
|
130
|
+
|
|
131
|
+
normalized_path = sub_path
|
|
132
|
+
lower_path = normalized_path.to_lower()
|
|
133
|
+
|
|
134
|
+
if normalized_path.find("/") == -1:
|
|
135
|
+
for child in edited_scene_root.get_children():
|
|
136
|
+
if child.name.to_lower() == lower_path:
|
|
137
|
+
return child
|
|
138
|
+
|
|
139
|
+
return null
|
|
140
|
+
|
|
141
|
+
# Helper function to mark a scene as modified
|
|
142
|
+
func _mark_scene_modified() -> void:
|
|
143
|
+
var plugin = Engine.get_meta("GodotMCPPlugin")
|
|
144
|
+
if not plugin:
|
|
145
|
+
print("GodotMCPPlugin not found in Engine metadata")
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
var editor_interface = plugin.get_editor_interface()
|
|
149
|
+
var edited_scene_root = editor_interface.get_edited_scene_root()
|
|
150
|
+
|
|
151
|
+
if edited_scene_root:
|
|
152
|
+
# This internally marks the scene as modified in the editor
|
|
153
|
+
editor_interface.mark_scene_as_unsaved()
|
|
154
|
+
|
|
155
|
+
# Helper function to access the EditorUndoRedoManager
|
|
156
|
+
func _get_undo_redo():
|
|
157
|
+
var plugin = Engine.get_meta("GodotMCPPlugin")
|
|
158
|
+
if not plugin or not plugin.has_method("get_undo_redo"):
|
|
159
|
+
print("Cannot access UndoRedo from plugin")
|
|
160
|
+
return null
|
|
161
|
+
|
|
162
|
+
return plugin.get_undo_redo()
|
|
163
|
+
|
|
164
|
+
# Helper function to parse property values from string to proper Godot types
|
|
165
|
+
func _parse_property_value(value):
|
|
166
|
+
# Only try to parse strings that look like they could be Godot types
|
|
167
|
+
if typeof(value) == TYPE_STRING and (
|
|
168
|
+
value.begins_with("Vector") or
|
|
169
|
+
value.begins_with("Transform") or
|
|
170
|
+
value.begins_with("Rect") or
|
|
171
|
+
value.begins_with("Color") or
|
|
172
|
+
value.begins_with("Quat") or
|
|
173
|
+
value.begins_with("Basis") or
|
|
174
|
+
value.begins_with("Plane") or
|
|
175
|
+
value.begins_with("AABB") or
|
|
176
|
+
value.begins_with("Projection") or
|
|
177
|
+
value.begins_with("Callable") or
|
|
178
|
+
value.begins_with("Signal") or
|
|
179
|
+
value.begins_with("PackedVector") or
|
|
180
|
+
value.begins_with("PackedString") or
|
|
181
|
+
value.begins_with("PackedFloat") or
|
|
182
|
+
value.begins_with("PackedInt") or
|
|
183
|
+
value.begins_with("PackedColor") or
|
|
184
|
+
value.begins_with("PackedByteArray") or
|
|
185
|
+
value.begins_with("Dictionary") or
|
|
186
|
+
value.begins_with("Array")
|
|
187
|
+
):
|
|
188
|
+
var expression = Expression.new()
|
|
189
|
+
var error = expression.parse(value, [])
|
|
190
|
+
|
|
191
|
+
if error == OK:
|
|
192
|
+
var result = expression.execute([], null, true)
|
|
193
|
+
if not expression.has_execute_failed():
|
|
194
|
+
print("Successfully parsed %s as %s" % [value, result])
|
|
195
|
+
return result
|
|
196
|
+
else:
|
|
197
|
+
print("Failed to execute expression for: %s" % value)
|
|
198
|
+
else:
|
|
199
|
+
print("Failed to parse expression: %s (Error: %d)" % [value, error])
|
|
200
|
+
|
|
201
|
+
# Otherwise, return value as is
|
|
202
|
+
return value
|
|
203
|
+
|
|
204
|
+
func _normalize_node_path(path: String) -> String:
|
|
205
|
+
var normalized = path.strip_edges()
|
|
206
|
+
if normalized.is_empty() or normalized == "." or normalized == "/root":
|
|
207
|
+
return ""
|
|
208
|
+
|
|
209
|
+
while normalized.begins_with("/root/"):
|
|
210
|
+
normalized = normalized.substr(6)
|
|
211
|
+
|
|
212
|
+
while normalized.begins_with("./"):
|
|
213
|
+
normalized = normalized.substr(2)
|
|
214
|
+
|
|
215
|
+
if normalized.begins_with("/"):
|
|
216
|
+
normalized = normalized.substr(1)
|
|
217
|
+
|
|
218
|
+
if normalized.begins_with("."):
|
|
219
|
+
normalized = normalized.substr(1)
|
|
220
|
+
|
|
221
|
+
return normalized.strip_edges()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://c3da6achkr43o
|