@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
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends EditorPlugin
|
|
3
|
+
|
|
4
|
+
var websocket_server: MCPWebSocketServer
|
|
5
|
+
var command_handler = null # Command handler reference
|
|
6
|
+
var panel = null # Reference to the MCP panel
|
|
7
|
+
var runtime_debugger_bridge = null # Runtime scene inspection bridge
|
|
8
|
+
var debugger_bridge = null # Debugger control bridge
|
|
9
|
+
var debug_output_publisher = null # Live debug output broadcaster
|
|
10
|
+
var _runtime_bridge_warning_logged := false
|
|
11
|
+
var _debugger_bridge_warning_logged := false
|
|
12
|
+
const SCENE_CAPTURE_NAMES := ["scene", "limboai", "mcp_eval", "mcp_input"]
|
|
13
|
+
const STACK_CAPTURE_NAMES := ["stack", "call_stack", "callstack"]
|
|
14
|
+
|
|
15
|
+
const INPUT_HANDLER_AUTOLOAD_NAME := "MCPInputHandler"
|
|
16
|
+
const INPUT_HANDLER_SCRIPT_PATH := "res://addons/godot_mcp/mcp_input_handler.gd"
|
|
17
|
+
|
|
18
|
+
func _enter_tree():
|
|
19
|
+
# Store plugin instance for EditorInterface access
|
|
20
|
+
Engine.set_meta("GodotMCPPlugin", self)
|
|
21
|
+
_runtime_bridge_warning_logged = false
|
|
22
|
+
_debugger_bridge_warning_logged = false
|
|
23
|
+
_try_register_runtime_bridge()
|
|
24
|
+
_try_register_debugger_bridge()
|
|
25
|
+
_register_input_handler_autoload()
|
|
26
|
+
|
|
27
|
+
print("\n=== MCP SERVER STARTING ===")
|
|
28
|
+
|
|
29
|
+
# Initialize the websocket server
|
|
30
|
+
websocket_server = load("res://addons/godot_mcp/websocket_server.gd").new()
|
|
31
|
+
websocket_server.name = "WebSocketServer"
|
|
32
|
+
add_child(websocket_server)
|
|
33
|
+
websocket_server.connect("client_disconnected", Callable(self, "_on_client_disconnected"))
|
|
34
|
+
|
|
35
|
+
# Initialize the command handler
|
|
36
|
+
print("Creating command handler...")
|
|
37
|
+
var handler_script = load("res://addons/godot_mcp/command_handler.gd")
|
|
38
|
+
if handler_script:
|
|
39
|
+
command_handler = Node.new()
|
|
40
|
+
command_handler.set_script(handler_script)
|
|
41
|
+
command_handler.name = "CommandHandler"
|
|
42
|
+
websocket_server.add_child(command_handler)
|
|
43
|
+
|
|
44
|
+
# Connect signals
|
|
45
|
+
print("Connecting command handler signals...")
|
|
46
|
+
websocket_server.connect("command_received", Callable(command_handler, "_handle_command"))
|
|
47
|
+
else:
|
|
48
|
+
printerr("Failed to load command handler script!")
|
|
49
|
+
|
|
50
|
+
# Initialize the control panel
|
|
51
|
+
panel = load("res://addons/godot_mcp/ui/mcp_panel.tscn").instantiate()
|
|
52
|
+
panel.websocket_server = websocket_server
|
|
53
|
+
add_control_to_bottom_panel(panel, "MCP Server")
|
|
54
|
+
|
|
55
|
+
# Initialize live debug output publisher
|
|
56
|
+
var publisher_script = load("res://addons/godot_mcp/mcp_debug_output_publisher.gd")
|
|
57
|
+
if publisher_script:
|
|
58
|
+
debug_output_publisher = publisher_script.new()
|
|
59
|
+
debug_output_publisher.name = "DebugOutputPublisher"
|
|
60
|
+
debug_output_publisher.websocket_server = websocket_server
|
|
61
|
+
add_child(debug_output_publisher)
|
|
62
|
+
Engine.set_meta("MCPDebugOutputPublisher", debug_output_publisher)
|
|
63
|
+
|
|
64
|
+
print("MCP Server plugin initialized")
|
|
65
|
+
# Server startup will be handled in _ready() with proper timing
|
|
66
|
+
|
|
67
|
+
func _ready():
|
|
68
|
+
# Wait for Godot to fully initialize before starting the server
|
|
69
|
+
_start_server_with_improved_timing()
|
|
70
|
+
|
|
71
|
+
func _start_server_with_improved_timing(attempt: int = 0):
|
|
72
|
+
# Wait for full Godot initialization (double frame wait)
|
|
73
|
+
await get_tree().process_frame
|
|
74
|
+
await get_tree().process_frame
|
|
75
|
+
|
|
76
|
+
print("Attempting to start MCP WebSocket server...")
|
|
77
|
+
var start_result := websocket_server.start_server()
|
|
78
|
+
|
|
79
|
+
if start_result == OK:
|
|
80
|
+
print("✓ MCP WebSocket server started successfully")
|
|
81
|
+
# Verify server is actually ready
|
|
82
|
+
await get_tree().create_timer(0.5).timeout
|
|
83
|
+
if websocket_server.is_server_active():
|
|
84
|
+
print("✓ MCP WebSocket server verified and ready")
|
|
85
|
+
else:
|
|
86
|
+
print("⚠ MCP server started but not fully active, may need manual start")
|
|
87
|
+
elif start_result == ERR_ALREADY_IN_USE:
|
|
88
|
+
print("✓ MCP WebSocket server already running")
|
|
89
|
+
else:
|
|
90
|
+
if attempt < 3: # Retry up to 3 times
|
|
91
|
+
print("✗ MCP server start failed (code: %d), retrying in 1 second... (attempt %d/3)" % [start_result, attempt + 1])
|
|
92
|
+
await get_tree().create_timer(1.0).timeout
|
|
93
|
+
_start_server_with_improved_timing(attempt + 1)
|
|
94
|
+
else:
|
|
95
|
+
printerr("✗ Failed to start MCP server after 3 attempts (final code: %d)" % start_result)
|
|
96
|
+
printerr("Please use the 'Start' button in the MCP Server panel at the bottom of the editor")
|
|
97
|
+
|
|
98
|
+
func _exit_tree():
|
|
99
|
+
# Remove plugin instance from Engine metadata
|
|
100
|
+
if Engine.has_meta("GodotMCPPlugin"):
|
|
101
|
+
Engine.remove_meta("GodotMCPPlugin")
|
|
102
|
+
if Engine.has_meta("MCPRuntimeDebuggerBridge"):
|
|
103
|
+
Engine.remove_meta("MCPRuntimeDebuggerBridge")
|
|
104
|
+
if Engine.has_meta("MCPDebuggerBridge"):
|
|
105
|
+
Engine.remove_meta("MCPDebuggerBridge")
|
|
106
|
+
if Engine.has_meta("MCPDebugOutputPublisher"):
|
|
107
|
+
Engine.remove_meta("MCPDebugOutputPublisher")
|
|
108
|
+
_update_debugger_captures(false)
|
|
109
|
+
_remove_input_handler_autoload()
|
|
110
|
+
|
|
111
|
+
if runtime_debugger_bridge:
|
|
112
|
+
remove_debugger_plugin(runtime_debugger_bridge)
|
|
113
|
+
runtime_debugger_bridge = null
|
|
114
|
+
|
|
115
|
+
if debugger_bridge:
|
|
116
|
+
remove_debugger_plugin(debugger_bridge)
|
|
117
|
+
debugger_bridge = null
|
|
118
|
+
|
|
119
|
+
# Clean up the panel
|
|
120
|
+
if panel:
|
|
121
|
+
remove_control_from_bottom_panel(panel)
|
|
122
|
+
panel.queue_free()
|
|
123
|
+
panel = null
|
|
124
|
+
|
|
125
|
+
if debug_output_publisher:
|
|
126
|
+
debug_output_publisher.unsubscribe_all()
|
|
127
|
+
debug_output_publisher.queue_free()
|
|
128
|
+
debug_output_publisher = null
|
|
129
|
+
|
|
130
|
+
# Clean up the websocket server and command handler
|
|
131
|
+
if websocket_server:
|
|
132
|
+
websocket_server.stop_server()
|
|
133
|
+
websocket_server.queue_free()
|
|
134
|
+
websocket_server = null
|
|
135
|
+
|
|
136
|
+
print("=== MCP SERVER SHUTDOWN ===")
|
|
137
|
+
|
|
138
|
+
# Method to get the debugger bridge for other components
|
|
139
|
+
func get_debugger_bridge():
|
|
140
|
+
return debugger_bridge
|
|
141
|
+
|
|
142
|
+
# Helper function for command processors to access EditorInterface
|
|
143
|
+
func get_editor_interface():
|
|
144
|
+
return super.get_editor_interface()
|
|
145
|
+
|
|
146
|
+
# Helper function for command processors to get undo/redo manager
|
|
147
|
+
func get_undo_redo():
|
|
148
|
+
return super.get_undo_redo()
|
|
149
|
+
|
|
150
|
+
func _try_register_runtime_bridge() -> bool:
|
|
151
|
+
if runtime_debugger_bridge:
|
|
152
|
+
return true
|
|
153
|
+
|
|
154
|
+
var runtime_bridge_script = load("res://addons/godot_mcp/mcp_runtime_debugger_bridge.gd")
|
|
155
|
+
if not runtime_bridge_script:
|
|
156
|
+
if not _runtime_bridge_warning_logged:
|
|
157
|
+
_runtime_bridge_warning_logged = true
|
|
158
|
+
print("Godot MCP runtime scene inspection unavailable (bridge script not found).")
|
|
159
|
+
return false
|
|
160
|
+
|
|
161
|
+
if not ClassDB.class_exists("EditorDebuggerPlugin"):
|
|
162
|
+
if not _runtime_bridge_warning_logged:
|
|
163
|
+
_runtime_bridge_warning_logged = true
|
|
164
|
+
print("Godot MCP runtime scene inspection unavailable on this editor version.")
|
|
165
|
+
return false
|
|
166
|
+
|
|
167
|
+
var runtime_bridge_instance = runtime_bridge_script.new()
|
|
168
|
+
if runtime_bridge_instance == null:
|
|
169
|
+
if not _runtime_bridge_warning_logged:
|
|
170
|
+
_runtime_bridge_warning_logged = true
|
|
171
|
+
print("Godot MCP runtime scene inspection disabled (bridge instantiation failed).")
|
|
172
|
+
return false
|
|
173
|
+
|
|
174
|
+
runtime_debugger_bridge = runtime_bridge_instance
|
|
175
|
+
add_debugger_plugin(runtime_debugger_bridge)
|
|
176
|
+
Engine.set_meta("MCPRuntimeDebuggerBridge", runtime_debugger_bridge)
|
|
177
|
+
_update_debugger_captures(true)
|
|
178
|
+
_runtime_bridge_warning_logged = false
|
|
179
|
+
print("Godot MCP runtime scene inspection enabled.")
|
|
180
|
+
return true
|
|
181
|
+
|
|
182
|
+
func _try_register_debugger_bridge() -> bool:
|
|
183
|
+
if debugger_bridge:
|
|
184
|
+
return true
|
|
185
|
+
|
|
186
|
+
var debugger_bridge_script = load("res://addons/godot_mcp/mcp_debugger_bridge.gd")
|
|
187
|
+
if not debugger_bridge_script:
|
|
188
|
+
if not _debugger_bridge_warning_logged:
|
|
189
|
+
_debugger_bridge_warning_logged = true
|
|
190
|
+
print("Godot MCP debugger bridge unavailable (bridge script not found).")
|
|
191
|
+
return false
|
|
192
|
+
|
|
193
|
+
if not ClassDB.class_exists("EditorDebuggerPlugin"):
|
|
194
|
+
if not _debugger_bridge_warning_logged:
|
|
195
|
+
_debugger_bridge_warning_logged = true
|
|
196
|
+
print("Godot MCP debugger bridge unavailable on this editor version.")
|
|
197
|
+
return false
|
|
198
|
+
|
|
199
|
+
var debugger_bridge_instance = debugger_bridge_script.new()
|
|
200
|
+
if debugger_bridge_instance == null:
|
|
201
|
+
if not _debugger_bridge_warning_logged:
|
|
202
|
+
_debugger_bridge_warning_logged = true
|
|
203
|
+
print("Godot MCP debugger bridge disabled (bridge instantiation failed).")
|
|
204
|
+
return false
|
|
205
|
+
|
|
206
|
+
debugger_bridge = debugger_bridge_instance
|
|
207
|
+
add_debugger_plugin(debugger_bridge)
|
|
208
|
+
Engine.set_meta("MCPDebuggerBridge", debugger_bridge)
|
|
209
|
+
_debugger_bridge_warning_logged = false
|
|
210
|
+
print("Godot MCP debugger bridge enabled.")
|
|
211
|
+
return true
|
|
212
|
+
|
|
213
|
+
func _update_debugger_captures(enable: bool) -> void:
|
|
214
|
+
if not Engine.has_singleton("EngineDebugger"):
|
|
215
|
+
return
|
|
216
|
+
var engine_debugger = Engine.get_singleton("EngineDebugger")
|
|
217
|
+
if engine_debugger == null:
|
|
218
|
+
return
|
|
219
|
+
if not engine_debugger.has_method("set_capture"):
|
|
220
|
+
return
|
|
221
|
+
var has_query := engine_debugger.has_method("has_capture")
|
|
222
|
+
for name in SCENE_CAPTURE_NAMES + STACK_CAPTURE_NAMES:
|
|
223
|
+
if enable:
|
|
224
|
+
if not has_query or not engine_debugger.has_capture(name):
|
|
225
|
+
engine_debugger.set_capture(name, true)
|
|
226
|
+
else:
|
|
227
|
+
if not has_query or engine_debugger.has_capture(name):
|
|
228
|
+
engine_debugger.set_capture(name, false)
|
|
229
|
+
|
|
230
|
+
func _on_client_disconnected(client_id: int) -> void:
|
|
231
|
+
if debug_output_publisher:
|
|
232
|
+
debug_output_publisher.unsubscribe(client_id)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
func _register_input_handler_autoload() -> void:
|
|
236
|
+
# Check if autoload already exists
|
|
237
|
+
if ProjectSettings.has_setting("autoload/" + INPUT_HANDLER_AUTOLOAD_NAME):
|
|
238
|
+
print("MCP Input Handler autoload already registered.")
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
# Verify the script exists
|
|
242
|
+
if not FileAccess.file_exists(INPUT_HANDLER_SCRIPT_PATH):
|
|
243
|
+
printerr("MCP Input Handler script not found at: " + INPUT_HANDLER_SCRIPT_PATH)
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
# Add the autoload
|
|
247
|
+
ProjectSettings.set_setting("autoload/" + INPUT_HANDLER_AUTOLOAD_NAME, "*" + INPUT_HANDLER_SCRIPT_PATH)
|
|
248
|
+
ProjectSettings.save()
|
|
249
|
+
print("MCP Input Handler autoload registered. Restart the game for input simulation to work.")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
func _remove_input_handler_autoload() -> void:
|
|
253
|
+
# Check if autoload exists before removing
|
|
254
|
+
if not ProjectSettings.has_setting("autoload/" + INPUT_HANDLER_AUTOLOAD_NAME):
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
# Remove the autoload
|
|
258
|
+
ProjectSettings.set_setting("autoload/" + INPUT_HANDLER_AUTOLOAD_NAME, null)
|
|
259
|
+
ProjectSettings.save()
|
|
260
|
+
print("MCP Input Handler autoload removed.")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://wqy34yy6d1kr
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
extends RefCounted
|
|
2
|
+
|
|
3
|
+
# Runtime debugger helper that provides bridge functionality
|
|
4
|
+
# This is not a runtime script anymore, but a helper for the debugger bridge
|
|
5
|
+
|
|
6
|
+
const DEBUGGER_CAPTURE_NAME := "mcp_debugger"
|
|
7
|
+
|
|
8
|
+
class RuntimeBreakpointManager:
|
|
9
|
+
var _breakpoints: Dictionary = {}
|
|
10
|
+
var _engine_debugger = null
|
|
11
|
+
|
|
12
|
+
func _init():
|
|
13
|
+
_engine_debugger = Engine.get_singleton("EngineDebugger")
|
|
14
|
+
if _engine_debugger:
|
|
15
|
+
print("[MCP Runtime Debugger] EngineDebugger singleton available")
|
|
16
|
+
|
|
17
|
+
# Add a breakpoint to be tracked
|
|
18
|
+
func add_breakpoint(script_path: String, line: int) -> bool:
|
|
19
|
+
var key = "%s:%d" % [script_path, line]
|
|
20
|
+
if not _breakpoints.has(script_path):
|
|
21
|
+
_breakpoints[script_path] = []
|
|
22
|
+
|
|
23
|
+
if line not in _breakpoints[script_path]:
|
|
24
|
+
_breakpoints[script_path].append(line)
|
|
25
|
+
print("[MCP Runtime Debugger] Tracking breakpoint: %s" % key)
|
|
26
|
+
return true
|
|
27
|
+
return false
|
|
28
|
+
|
|
29
|
+
# Remove a breakpoint from tracking
|
|
30
|
+
func remove_breakpoint(script_path: String, line: int) -> bool:
|
|
31
|
+
if _breakpoints.has(script_path):
|
|
32
|
+
if line in _breakpoints[script_path]:
|
|
33
|
+
_breakpoints[script_path].erase(line)
|
|
34
|
+
print("[MCP Runtime Debugger] Removed breakpoint: %s:%d" % [script_path, line])
|
|
35
|
+
if _breakpoints[script_path].is_empty():
|
|
36
|
+
_breakpoints.erase(script_path)
|
|
37
|
+
return true
|
|
38
|
+
return false
|
|
39
|
+
|
|
40
|
+
# Get all tracked breakpoints
|
|
41
|
+
func get_breakpoints() -> Dictionary:
|
|
42
|
+
return _breakpoints.duplicate(true)
|
|
43
|
+
|
|
44
|
+
# Clear all breakpoints
|
|
45
|
+
func clear_breakpoints() -> void:
|
|
46
|
+
_breakpoints.clear()
|
|
47
|
+
print("[MCP Runtime Debugger] All breakpoints cleared")
|
|
48
|
+
|
|
49
|
+
# Check if a breakpoint exists at the given location
|
|
50
|
+
func has_breakpoint(script_path: String, line: int) -> bool:
|
|
51
|
+
return _breakpoints.has(script_path) and line in _breakpoints[script_path]
|
|
52
|
+
|
|
53
|
+
# Static instance for global access
|
|
54
|
+
static var _instance: RuntimeBreakpointManager = null
|
|
55
|
+
|
|
56
|
+
static func get_instance() -> RuntimeBreakpointManager:
|
|
57
|
+
if _instance == null:
|
|
58
|
+
_instance = RuntimeBreakpointManager.new()
|
|
59
|
+
return _instance
|
|
60
|
+
|
|
61
|
+
# Helper functions for the debugger bridge
|
|
62
|
+
static func register_breakpoint(script_path: String, line: int) -> bool:
|
|
63
|
+
var instance = get_instance()
|
|
64
|
+
return instance.add_breakpoint(script_path, line)
|
|
65
|
+
|
|
66
|
+
static func unregister_breakpoint(script_path: String, line: int) -> bool:
|
|
67
|
+
var instance = get_instance()
|
|
68
|
+
return instance.remove_breakpoint(script_path, line)
|
|
69
|
+
|
|
70
|
+
static func get_all_breakpoints() -> Dictionary:
|
|
71
|
+
var instance = get_instance()
|
|
72
|
+
return instance.get_breakpoints()
|
|
73
|
+
|
|
74
|
+
static func clear_all_breakpoints() -> void:
|
|
75
|
+
var instance = get_instance()
|
|
76
|
+
instance.clear_breakpoints()
|
|
77
|
+
|
|
78
|
+
static func is_breakpoint_active(script_path: String, line: int) -> bool:
|
|
79
|
+
var instance = get_instance()
|
|
80
|
+
return instance.has_breakpoint(script_path, line)
|
|
81
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://bt44g1v3ce6nb
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends Control
|
|
3
|
+
|
|
4
|
+
var websocket_server: MCPWebSocketServer
|
|
5
|
+
var status_label: Label
|
|
6
|
+
var port_input: SpinBox
|
|
7
|
+
var start_button: Button
|
|
8
|
+
var stop_button: Button
|
|
9
|
+
var connection_count_label: Label
|
|
10
|
+
var log_text: TextEdit
|
|
11
|
+
|
|
12
|
+
func _ready():
|
|
13
|
+
status_label = $VBoxContainer/StatusContainer/StatusLabel
|
|
14
|
+
port_input = $VBoxContainer/PortContainer/PortSpinBox
|
|
15
|
+
start_button = $VBoxContainer/ButtonsContainer/StartButton
|
|
16
|
+
stop_button = $VBoxContainer/ButtonsContainer/StopButton
|
|
17
|
+
connection_count_label = $VBoxContainer/ConnectionsContainer/CountLabel
|
|
18
|
+
log_text = $VBoxContainer/LogContainer/LogText
|
|
19
|
+
|
|
20
|
+
start_button.pressed.connect(_on_start_button_pressed)
|
|
21
|
+
stop_button.pressed.connect(_on_stop_button_pressed)
|
|
22
|
+
port_input.value_changed.connect(_on_port_changed)
|
|
23
|
+
|
|
24
|
+
# Initial UI setup
|
|
25
|
+
_update_ui()
|
|
26
|
+
|
|
27
|
+
# Setup server signals once it's available
|
|
28
|
+
await get_tree().process_frame
|
|
29
|
+
if websocket_server:
|
|
30
|
+
websocket_server.connect("client_connected", Callable(self, "_on_client_connected"))
|
|
31
|
+
websocket_server.connect("client_disconnected", Callable(self, "_on_client_disconnected"))
|
|
32
|
+
websocket_server.connect("command_received", Callable(self, "_on_command_received"))
|
|
33
|
+
|
|
34
|
+
port_input.value = websocket_server.get_port()
|
|
35
|
+
|
|
36
|
+
func _update_ui():
|
|
37
|
+
if not websocket_server:
|
|
38
|
+
status_label.text = "Server: Not initialized"
|
|
39
|
+
start_button.disabled = true
|
|
40
|
+
stop_button.disabled = true
|
|
41
|
+
port_input.editable = true
|
|
42
|
+
connection_count_label.text = "0"
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
var is_active = websocket_server.is_server_active()
|
|
46
|
+
|
|
47
|
+
status_label.text = "Server: " + ("Running" if is_active else "Stopped")
|
|
48
|
+
start_button.disabled = is_active
|
|
49
|
+
stop_button.disabled = not is_active
|
|
50
|
+
port_input.editable = not is_active
|
|
51
|
+
|
|
52
|
+
if is_active:
|
|
53
|
+
connection_count_label.text = str(websocket_server.get_client_count())
|
|
54
|
+
else:
|
|
55
|
+
connection_count_label.text = "0"
|
|
56
|
+
|
|
57
|
+
func _on_start_button_pressed():
|
|
58
|
+
if websocket_server:
|
|
59
|
+
var result = websocket_server.start_server()
|
|
60
|
+
if result == OK:
|
|
61
|
+
_log_message("Server started on port " + str(websocket_server.get_port()))
|
|
62
|
+
else:
|
|
63
|
+
_log_message("Failed to start server: " + str(result))
|
|
64
|
+
_update_ui()
|
|
65
|
+
|
|
66
|
+
func _on_stop_button_pressed():
|
|
67
|
+
if websocket_server:
|
|
68
|
+
websocket_server.stop_server()
|
|
69
|
+
_log_message("Server stopped")
|
|
70
|
+
_update_ui()
|
|
71
|
+
|
|
72
|
+
func _on_port_changed(new_port: float):
|
|
73
|
+
if websocket_server:
|
|
74
|
+
websocket_server.set_port(int(new_port))
|
|
75
|
+
_log_message("Port changed to " + str(int(new_port)))
|
|
76
|
+
|
|
77
|
+
func _on_client_connected(client_id: int):
|
|
78
|
+
_log_message("Client connected: " + str(client_id))
|
|
79
|
+
_update_ui()
|
|
80
|
+
|
|
81
|
+
func _on_client_disconnected(client_id: int):
|
|
82
|
+
_log_message("Client disconnected: " + str(client_id))
|
|
83
|
+
_update_ui()
|
|
84
|
+
|
|
85
|
+
func _on_command_received(client_id: int, command: Dictionary):
|
|
86
|
+
var command_type = command.get("type", "unknown")
|
|
87
|
+
var command_id = command.get("commandId", "no-id")
|
|
88
|
+
_log_message("Received command: " + command_type + " (ID: " + command_id + ") from client " + str(client_id))
|
|
89
|
+
|
|
90
|
+
func _log_message(message: String):
|
|
91
|
+
var timestamp = Time.get_datetime_string_from_system()
|
|
92
|
+
log_text.text += "[" + timestamp + "] " + message + "\n"
|
|
93
|
+
# Auto-scroll to bottom
|
|
94
|
+
log_text.scroll_vertical = log_text.get_line_count()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://d2c1roi0jmke1
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
[gd_scene load_steps=2 format=3 uid="uid://c2kuk5yuiage8"]
|
|
2
|
+
|
|
3
|
+
[ext_resource type="Script" path="res://addons/godot_mcp/ui/mcp_panel.gd" id="1_3cbiu"]
|
|
4
|
+
|
|
5
|
+
[node name="MCPPanel" type="Control"]
|
|
6
|
+
layout_mode = 3
|
|
7
|
+
anchors_preset = 15
|
|
8
|
+
anchor_right = 1.0
|
|
9
|
+
anchor_bottom = 1.0
|
|
10
|
+
grow_horizontal = 2
|
|
11
|
+
grow_vertical = 2
|
|
12
|
+
script = ExtResource("1_3cbiu")
|
|
13
|
+
|
|
14
|
+
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
|
15
|
+
layout_mode = 1
|
|
16
|
+
anchors_preset = 15
|
|
17
|
+
anchor_right = 1.0
|
|
18
|
+
anchor_bottom = 1.0
|
|
19
|
+
grow_horizontal = 2
|
|
20
|
+
grow_vertical = 2
|
|
21
|
+
|
|
22
|
+
[node name="TitleLabel" type="Label" parent="VBoxContainer"]
|
|
23
|
+
layout_mode = 2
|
|
24
|
+
text = "Godot MCP Server"
|
|
25
|
+
horizontal_alignment = 1
|
|
26
|
+
|
|
27
|
+
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
|
|
28
|
+
layout_mode = 2
|
|
29
|
+
|
|
30
|
+
[node name="StatusContainer" type="HBoxContainer" parent="VBoxContainer"]
|
|
31
|
+
layout_mode = 2
|
|
32
|
+
|
|
33
|
+
[node name="Label" type="Label" parent="VBoxContainer/StatusContainer"]
|
|
34
|
+
layout_mode = 2
|
|
35
|
+
text = "Status:"
|
|
36
|
+
|
|
37
|
+
[node name="StatusLabel" type="Label" parent="VBoxContainer/StatusContainer"]
|
|
38
|
+
layout_mode = 2
|
|
39
|
+
size_flags_horizontal = 3
|
|
40
|
+
text = "Server: Stopped"
|
|
41
|
+
|
|
42
|
+
[node name="PortContainer" type="HBoxContainer" parent="VBoxContainer"]
|
|
43
|
+
layout_mode = 2
|
|
44
|
+
|
|
45
|
+
[node name="Label" type="Label" parent="VBoxContainer/PortContainer"]
|
|
46
|
+
layout_mode = 2
|
|
47
|
+
text = "Port:"
|
|
48
|
+
|
|
49
|
+
[node name="PortSpinBox" type="SpinBox" parent="VBoxContainer/PortContainer"]
|
|
50
|
+
layout_mode = 2
|
|
51
|
+
size_flags_horizontal = 3
|
|
52
|
+
min_value = 1.0
|
|
53
|
+
max_value = 65535.0
|
|
54
|
+
value = 9080.0
|
|
55
|
+
|
|
56
|
+
[node name="ButtonsContainer" type="HBoxContainer" parent="VBoxContainer"]
|
|
57
|
+
layout_mode = 2
|
|
58
|
+
|
|
59
|
+
[node name="StartButton" type="Button" parent="VBoxContainer/ButtonsContainer"]
|
|
60
|
+
layout_mode = 2
|
|
61
|
+
size_flags_horizontal = 3
|
|
62
|
+
text = "Start Server"
|
|
63
|
+
|
|
64
|
+
[node name="StopButton" type="Button" parent="VBoxContainer/ButtonsContainer"]
|
|
65
|
+
layout_mode = 2
|
|
66
|
+
size_flags_horizontal = 3
|
|
67
|
+
disabled = true
|
|
68
|
+
text = "Stop Server"
|
|
69
|
+
|
|
70
|
+
[node name="ConnectionsContainer" type="HBoxContainer" parent="VBoxContainer"]
|
|
71
|
+
layout_mode = 2
|
|
72
|
+
|
|
73
|
+
[node name="Label" type="Label" parent="VBoxContainer/ConnectionsContainer"]
|
|
74
|
+
layout_mode = 2
|
|
75
|
+
text = "Connected Clients:"
|
|
76
|
+
|
|
77
|
+
[node name="CountLabel" type="Label" parent="VBoxContainer/ConnectionsContainer"]
|
|
78
|
+
layout_mode = 2
|
|
79
|
+
text = "0"
|
|
80
|
+
|
|
81
|
+
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"]
|
|
82
|
+
layout_mode = 2
|
|
83
|
+
|
|
84
|
+
[node name="LogLabel" type="Label" parent="VBoxContainer"]
|
|
85
|
+
layout_mode = 2
|
|
86
|
+
text = "Server Log:"
|
|
87
|
+
|
|
88
|
+
[node name="LogContainer" type="VBoxContainer" parent="VBoxContainer"]
|
|
89
|
+
layout_mode = 2
|
|
90
|
+
size_flags_vertical = 3
|
|
91
|
+
|
|
92
|
+
[node name="LogText" type="TextEdit" parent="VBoxContainer/LogContainer"]
|
|
93
|
+
layout_mode = 2
|
|
94
|
+
size_flags_vertical = 3
|
|
95
|
+
editable = false
|
|
96
|
+
wrap_mode = 1
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
class_name NodeUtils
|
|
3
|
+
extends RefCounted
|
|
4
|
+
|
|
5
|
+
# Get all nodes of a certain type in the scene tree
|
|
6
|
+
static func get_nodes_by_type(root_node: Node, type_name: String) -> Array[Node]:
|
|
7
|
+
var result: Array[Node] = []
|
|
8
|
+
|
|
9
|
+
if root_node.get_class() == type_name:
|
|
10
|
+
result.push_back(root_node)
|
|
11
|
+
|
|
12
|
+
for child in root_node.get_children():
|
|
13
|
+
result.append_array(get_nodes_by_type(child, type_name))
|
|
14
|
+
|
|
15
|
+
return result
|
|
16
|
+
|
|
17
|
+
# Get a node by its path, or null if not found
|
|
18
|
+
static func find_node_by_path(root_node: Node, path: String) -> Node:
|
|
19
|
+
if path.is_empty():
|
|
20
|
+
return null
|
|
21
|
+
|
|
22
|
+
if path.begins_with("/"):
|
|
23
|
+
path = path.substr(1)
|
|
24
|
+
|
|
25
|
+
var path_parts = path.split("/")
|
|
26
|
+
var current_node = root_node
|
|
27
|
+
|
|
28
|
+
for part in path_parts:
|
|
29
|
+
var found = false
|
|
30
|
+
for child in current_node.get_children():
|
|
31
|
+
if child.name == part:
|
|
32
|
+
current_node = child
|
|
33
|
+
found = true
|
|
34
|
+
break
|
|
35
|
+
|
|
36
|
+
if not found:
|
|
37
|
+
return null
|
|
38
|
+
|
|
39
|
+
return current_node
|
|
40
|
+
|
|
41
|
+
# Convert a node to a JSON-compatible dictionary
|
|
42
|
+
static func node_to_dict(node: Node) -> Dictionary:
|
|
43
|
+
var result = {
|
|
44
|
+
"name": node.name,
|
|
45
|
+
"type": node.get_class(),
|
|
46
|
+
"path": str(node.get_path()),
|
|
47
|
+
"properties": {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Get properties
|
|
51
|
+
var properties = {}
|
|
52
|
+
var property_list = node.get_property_list()
|
|
53
|
+
|
|
54
|
+
for prop in property_list:
|
|
55
|
+
var name = prop["name"]
|
|
56
|
+
if not name.begins_with("_"): # Skip internal properties
|
|
57
|
+
result["properties"][name] = node.get(name)
|
|
58
|
+
|
|
59
|
+
# Get children
|
|
60
|
+
var children = []
|
|
61
|
+
for child in node.get_children():
|
|
62
|
+
children.append({
|
|
63
|
+
"name": child.name,
|
|
64
|
+
"type": child.get_class(),
|
|
65
|
+
"path": str(child.get_path())
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
result["children"] = children
|
|
69
|
+
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
# Create a screenshot of a node (only works for CanvasItem nodes)
|
|
73
|
+
static func take_node_screenshot(node: CanvasItem) -> Image:
|
|
74
|
+
if not node is CanvasItem:
|
|
75
|
+
push_error("Can only take screenshots of CanvasItem nodes")
|
|
76
|
+
return null
|
|
77
|
+
|
|
78
|
+
var viewport = node.get_viewport()
|
|
79
|
+
if not viewport:
|
|
80
|
+
return null
|
|
81
|
+
|
|
82
|
+
return viewport.get_texture().get_image()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://bo1e0xfs0v0d
|