@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.
Files changed (115) hide show
  1. package/README.md +187 -0
  2. package/addons/godot_mcp/command_handler.gd +161 -0
  3. package/addons/godot_mcp/command_handler.gd.uid +1 -0
  4. package/addons/godot_mcp/commands/base_command_processor.gd +221 -0
  5. package/addons/godot_mcp/commands/base_command_processor.gd.uid +1 -0
  6. package/addons/godot_mcp/commands/debugger_commands.gd +221 -0
  7. package/addons/godot_mcp/commands/debugger_commands.gd.uid +1 -0
  8. package/addons/godot_mcp/commands/editor_commands.gd +237 -0
  9. package/addons/godot_mcp/commands/editor_commands.gd.uid +1 -0
  10. package/addons/godot_mcp/commands/editor_script_commands.gd +365 -0
  11. package/addons/godot_mcp/commands/editor_script_commands.gd.uid +1 -0
  12. package/addons/godot_mcp/commands/input_commands.gd +337 -0
  13. package/addons/godot_mcp/commands/input_commands.gd.uid +1 -0
  14. package/addons/godot_mcp/commands/node_commands.gd +222 -0
  15. package/addons/godot_mcp/commands/node_commands.gd.uid +1 -0
  16. package/addons/godot_mcp/commands/project_commands.gd +298 -0
  17. package/addons/godot_mcp/commands/project_commands.gd.uid +1 -0
  18. package/addons/godot_mcp/commands/scene_commands.gd +337 -0
  19. package/addons/godot_mcp/commands/scene_commands.gd.uid +1 -0
  20. package/addons/godot_mcp/commands/script_commands.gd +349 -0
  21. package/addons/godot_mcp/commands/script_commands.gd.uid +1 -0
  22. package/addons/godot_mcp/mcp_asset_commands.gd +153 -0
  23. package/addons/godot_mcp/mcp_asset_commands.gd.uid +1 -0
  24. package/addons/godot_mcp/mcp_debug_output_publisher.gd +1669 -0
  25. package/addons/godot_mcp/mcp_debug_output_publisher.gd.uid +1 -0
  26. package/addons/godot_mcp/mcp_debugger_bridge.gd +1455 -0
  27. package/addons/godot_mcp/mcp_debugger_bridge.gd.uid +1 -0
  28. package/addons/godot_mcp/mcp_enhanced_commands.gd +1083 -0
  29. package/addons/godot_mcp/mcp_enhanced_commands.gd.uid +1 -0
  30. package/addons/godot_mcp/mcp_input_handler.gd +545 -0
  31. package/addons/godot_mcp/mcp_input_handler.gd.uid +1 -0
  32. package/addons/godot_mcp/mcp_runtime_debugger_bridge.gd +464 -0
  33. package/addons/godot_mcp/mcp_runtime_debugger_bridge.gd.uid +1 -0
  34. package/addons/godot_mcp/mcp_script_resource_commands.gd +165 -0
  35. package/addons/godot_mcp/mcp_script_resource_commands.gd.uid +1 -0
  36. package/addons/godot_mcp/mcp_server.gd +260 -0
  37. package/addons/godot_mcp/mcp_server.gd.uid +1 -0
  38. package/addons/godot_mcp/plugin.cfg +7 -0
  39. package/addons/godot_mcp/runtime_debugger.gd +81 -0
  40. package/addons/godot_mcp/runtime_debugger.gd.uid +1 -0
  41. package/addons/godot_mcp/ui/mcp_panel.gd +94 -0
  42. package/addons/godot_mcp/ui/mcp_panel.gd.uid +1 -0
  43. package/addons/godot_mcp/ui/mcp_panel.tscn +96 -0
  44. package/addons/godot_mcp/utils/node_utils.gd +82 -0
  45. package/addons/godot_mcp/utils/node_utils.gd.uid +1 -0
  46. package/addons/godot_mcp/utils/resource_utils.gd +81 -0
  47. package/addons/godot_mcp/utils/resource_utils.gd.uid +1 -0
  48. package/addons/godot_mcp/utils/script_utils.gd +114 -0
  49. package/addons/godot_mcp/utils/script_utils.gd.uid +1 -0
  50. package/addons/godot_mcp/websocket_server.gd +197 -0
  51. package/addons/godot_mcp/websocket_server.gd.uid +1 -0
  52. package/dist/cli.d.ts +2 -0
  53. package/dist/cli.js +561 -0
  54. package/dist/cli.js.map +1 -0
  55. package/dist/index.d.ts +1 -0
  56. package/dist/index.js +156 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/resources/asset_resources.d.ts +29 -0
  59. package/dist/resources/asset_resources.js +145 -0
  60. package/dist/resources/asset_resources.js.map +1 -0
  61. package/dist/resources/debug_resources.d.ts +11 -0
  62. package/dist/resources/debug_resources.js +106 -0
  63. package/dist/resources/debug_resources.js.map +1 -0
  64. package/dist/resources/debugger_resources.d.ts +62 -0
  65. package/dist/resources/debugger_resources.js +201 -0
  66. package/dist/resources/debugger_resources.js.map +1 -0
  67. package/dist/resources/editor_resources.d.ts +47 -0
  68. package/dist/resources/editor_resources.js +155 -0
  69. package/dist/resources/editor_resources.js.map +1 -0
  70. package/dist/resources/project_resources.d.ts +33 -0
  71. package/dist/resources/project_resources.js +137 -0
  72. package/dist/resources/project_resources.js.map +1 -0
  73. package/dist/resources/scene_resources.d.ts +33 -0
  74. package/dist/resources/scene_resources.js +160 -0
  75. package/dist/resources/scene_resources.js.map +1 -0
  76. package/dist/resources/script_resources.d.ts +51 -0
  77. package/dist/resources/script_resources.js +203 -0
  78. package/dist/resources/script_resources.js.map +1 -0
  79. package/dist/tools/asset_tools.d.ts +5 -0
  80. package/dist/tools/asset_tools.js +125 -0
  81. package/dist/tools/asset_tools.js.map +1 -0
  82. package/dist/tools/debugger_tools.d.ts +2 -0
  83. package/dist/tools/debugger_tools.js +342 -0
  84. package/dist/tools/debugger_tools.js.map +1 -0
  85. package/dist/tools/editor_tools.d.ts +2 -0
  86. package/dist/tools/editor_tools.js +165 -0
  87. package/dist/tools/editor_tools.js.map +1 -0
  88. package/dist/tools/enhanced_tools.d.ts +5 -0
  89. package/dist/tools/enhanced_tools.js +706 -0
  90. package/dist/tools/enhanced_tools.js.map +1 -0
  91. package/dist/tools/input_tools.d.ts +2 -0
  92. package/dist/tools/input_tools.js +408 -0
  93. package/dist/tools/input_tools.js.map +1 -0
  94. package/dist/tools/node_tools.d.ts +5 -0
  95. package/dist/tools/node_tools.js +217 -0
  96. package/dist/tools/node_tools.js.map +1 -0
  97. package/dist/tools/project_tools.d.ts +5 -0
  98. package/dist/tools/project_tools.js +162 -0
  99. package/dist/tools/project_tools.js.map +1 -0
  100. package/dist/tools/scene_tools.d.ts +5 -0
  101. package/dist/tools/scene_tools.js +260 -0
  102. package/dist/tools/scene_tools.js.map +1 -0
  103. package/dist/tools/script_resource_tools.d.ts +5 -0
  104. package/dist/tools/script_resource_tools.js +5 -0
  105. package/dist/tools/script_resource_tools.js.map +1 -0
  106. package/dist/tools/script_tools.d.ts +5 -0
  107. package/dist/tools/script_tools.js +154 -0
  108. package/dist/tools/script_tools.js.map +1 -0
  109. package/dist/utils/godot_connection.d.ts +30 -0
  110. package/dist/utils/godot_connection.js +285 -0
  111. package/dist/utils/godot_connection.js.map +1 -0
  112. package/dist/utils/types.d.ts +16 -0
  113. package/dist/utils/types.js +2 -0
  114. package/dist/utils/types.js.map +1 -0
  115. 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,7 @@
1
+ [plugin]
2
+
3
+ name="Godot MCP"
4
+ description="Godot integration with Claude AI via the Model Context Protocol"
5
+ author="Your Name"
6
+ version="1.0.0"
7
+ script="mcp_server.gd"
@@ -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