@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,337 @@
1
+ @tool
2
+ class_name MCPSceneCommands
3
+ extends MCPBaseCommandProcessor
4
+
5
+ func process_command(client_id: int, command_type: String, params: Dictionary, command_id: String) -> bool:
6
+ match command_type:
7
+ "save_scene":
8
+ _save_scene(client_id, params, command_id)
9
+ return true
10
+ "open_scene":
11
+ _open_scene(client_id, params, command_id)
12
+ return true
13
+ "get_current_scene":
14
+ _get_current_scene(client_id, params, command_id)
15
+ return true
16
+ "get_scene_structure":
17
+ _get_scene_structure(client_id, params, command_id)
18
+ return true
19
+ "create_scene":
20
+ _create_scene(client_id, params, command_id)
21
+ return true
22
+ "delete_scene":
23
+ _delete_scene(client_id, params, command_id)
24
+ return true
25
+ return false # Command not handled
26
+
27
+ func _save_scene(client_id: int, params: Dictionary, command_id: String) -> void:
28
+ var path = params.get("path", "")
29
+
30
+ # Get editor plugin and interfaces
31
+ var plugin = Engine.get_meta("GodotMCPPlugin")
32
+ if not plugin:
33
+ return _send_error(client_id, "GodotMCPPlugin not found in Engine metadata", command_id)
34
+
35
+ var editor_interface = plugin.get_editor_interface()
36
+ var edited_scene_root = editor_interface.get_edited_scene_root()
37
+
38
+ # If no path provided, use the current scene path
39
+ if path.is_empty() and edited_scene_root:
40
+ path = edited_scene_root.scene_file_path
41
+
42
+ # Validation
43
+ if path.is_empty():
44
+ return _send_error(client_id, "Scene path cannot be empty", command_id)
45
+
46
+ # Make sure we have an absolute path
47
+ if not path.begins_with("res://"):
48
+ path = "res://" + path
49
+
50
+ if not path.ends_with(".tscn"):
51
+ path += ".tscn"
52
+
53
+ # Check if we have an edited scene
54
+ if not edited_scene_root:
55
+ return _send_error(client_id, "No scene is currently being edited", command_id)
56
+
57
+ # Save the scene
58
+ var packed_scene = PackedScene.new()
59
+ var result = packed_scene.pack(edited_scene_root)
60
+ if result != OK:
61
+ return _send_error(client_id, "Failed to pack scene: %d" % result, command_id)
62
+
63
+ result = ResourceSaver.save(packed_scene, path)
64
+ if result != OK:
65
+ return _send_error(client_id, "Failed to save scene: %d" % result, command_id)
66
+
67
+ _send_success(client_id, {
68
+ "scene_path": path
69
+ }, command_id)
70
+
71
+ func _open_scene(client_id: int, params: Dictionary, command_id: String) -> void:
72
+ var path = params.get("path", "")
73
+
74
+ # Validation
75
+ if path.is_empty():
76
+ return _send_error(client_id, "Scene path cannot be empty", command_id)
77
+
78
+ # Make sure we have an absolute path
79
+ if not path.begins_with("res://"):
80
+ path = "res://" + path
81
+
82
+ # Check if the file exists
83
+ if not FileAccess.file_exists(path):
84
+ return _send_error(client_id, "Scene file not found: %s" % path, command_id)
85
+
86
+ # Since we can't directly open scenes in tool scripts,
87
+ # we need to defer to the plugin which has access to EditorInterface
88
+ var plugin = Engine.get_meta("GodotMCPPlugin") if Engine.has_meta("GodotMCPPlugin") else null
89
+
90
+ if plugin and plugin.has_method("get_editor_interface"):
91
+ var editor_interface = plugin.get_editor_interface()
92
+ editor_interface.open_scene_from_path(path)
93
+ _send_success(client_id, {
94
+ "scene_path": path
95
+ }, command_id)
96
+ else:
97
+ _send_error(client_id, "Cannot access EditorInterface. Please open the scene manually: %s" % path, command_id)
98
+
99
+ func _get_current_scene(client_id: int, _params: Dictionary, command_id: String) -> void:
100
+ # Get editor plugin and interfaces
101
+ var plugin = Engine.get_meta("GodotMCPPlugin")
102
+ if not plugin:
103
+ return _send_error(client_id, "GodotMCPPlugin not found in Engine metadata", command_id)
104
+
105
+ var editor_interface = plugin.get_editor_interface()
106
+ var edited_scene_root = editor_interface.get_edited_scene_root()
107
+
108
+ if not edited_scene_root:
109
+ print("No scene is currently being edited")
110
+ # Instead of returning an error, return a valid response with empty/default values
111
+ _send_success(client_id, {
112
+ "scene_path": "None",
113
+ "root_node_type": "None",
114
+ "root_node_name": "None"
115
+ }, command_id)
116
+ return
117
+
118
+ var scene_path = edited_scene_root.scene_file_path
119
+ if scene_path.is_empty():
120
+ scene_path = "Untitled"
121
+
122
+ print("Current scene path: ", scene_path)
123
+ print("Root node type: ", edited_scene_root.get_class())
124
+ print("Root node name: ", edited_scene_root.name)
125
+
126
+ _send_success(client_id, {
127
+ "scene_path": scene_path,
128
+ "root_node_type": edited_scene_root.get_class(),
129
+ "root_node_name": edited_scene_root.name
130
+ }, command_id)
131
+
132
+ func _get_scene_structure(client_id: int, params: Dictionary, command_id: String) -> void:
133
+ var path = params.get("path", "")
134
+
135
+ # Validation
136
+ if path.is_empty():
137
+ return _send_error(client_id, "Scene path cannot be empty", command_id)
138
+
139
+ if not path.begins_with("res://"):
140
+ path = "res://" + path
141
+
142
+ if not FileAccess.file_exists(path):
143
+ return _send_error(client_id, "Scene file not found: " + path, command_id)
144
+
145
+ # Load the scene to analyze its structure
146
+ var packed_scene = load(path)
147
+ if not packed_scene:
148
+ return _send_error(client_id, "Failed to load scene: " + path, command_id)
149
+
150
+ # Create a temporary instance to analyze
151
+ var scene_instance = packed_scene.instantiate()
152
+ if not scene_instance:
153
+ return _send_error(client_id, "Failed to instantiate scene: " + path, command_id)
154
+
155
+ # Get the scene structure
156
+ var structure = _get_node_structure(scene_instance)
157
+
158
+ # Clean up the temporary instance
159
+ scene_instance.queue_free()
160
+
161
+ # Return the structure
162
+ _send_success(client_id, {
163
+ "path": path,
164
+ "structure": structure
165
+ }, command_id)
166
+
167
+ func _get_node_structure(node: Node) -> Dictionary:
168
+ var structure = {
169
+ "name": node.name,
170
+ "type": node.get_class(),
171
+ "path": node.get_path()
172
+ }
173
+
174
+ # Get script information
175
+ var script = node.get_script()
176
+ if script:
177
+ structure["script"] = script.resource_path
178
+
179
+ # Get important properties
180
+ var properties = {}
181
+ var property_list = node.get_property_list()
182
+
183
+ for prop in property_list:
184
+ var name = prop["name"]
185
+ # Filter to include only the most useful properties
186
+ if not name.begins_with("_") and name not in ["script", "children", "position", "rotation", "scale"]:
187
+ continue
188
+
189
+ # Skip properties that are default values
190
+ if name == "position" and node.position == Vector2():
191
+ continue
192
+ if name == "rotation" and node.rotation == 0:
193
+ continue
194
+ if name == "scale" and node.scale == Vector2(1, 1):
195
+ continue
196
+
197
+ properties[name] = node.get(name)
198
+
199
+ structure["properties"] = properties
200
+
201
+ # Get children
202
+ var children = []
203
+ for child in node.get_children():
204
+ children.append(_get_node_structure(child))
205
+
206
+ structure["children"] = children
207
+
208
+ return structure
209
+
210
+ func _create_scene(client_id: int, params: Dictionary, command_id: String) -> void:
211
+ var path = params.get("path", "")
212
+ var root_node_type = params.get("root_node_type", "Node")
213
+
214
+ # Validation
215
+ if path.is_empty():
216
+ return _send_error(client_id, "Scene path cannot be empty", command_id)
217
+
218
+ # Make sure we have an absolute path
219
+ if not path.begins_with("res://"):
220
+ path = "res://" + path
221
+
222
+ # Ensure path ends with .tscn
223
+ if not path.ends_with(".tscn"):
224
+ path += ".tscn"
225
+
226
+ # Create directory structure if it doesn't exist
227
+ var dir_path = path.get_base_dir()
228
+ if not DirAccess.dir_exists_absolute(dir_path):
229
+ var dir = DirAccess.open("res://")
230
+ if dir:
231
+ dir.make_dir_recursive(dir_path.trim_prefix("res://"))
232
+
233
+ # Check if file already exists
234
+ if FileAccess.file_exists(path):
235
+ return _send_error(client_id, "Scene file already exists: %s" % path, command_id)
236
+
237
+ # Create the root node of the specified type
238
+ var root_node = null
239
+
240
+ match root_node_type:
241
+ "Node":
242
+ root_node = Node.new()
243
+ "Node2D":
244
+ root_node = Node2D.new()
245
+ "Node3D", "Spatial":
246
+ root_node = Node3D.new()
247
+ "Control":
248
+ root_node = Control.new()
249
+ "CanvasLayer":
250
+ root_node = CanvasLayer.new()
251
+ "Panel":
252
+ root_node = Panel.new()
253
+ _:
254
+ # Attempt to create a custom class if built-in type not recognized
255
+ if ClassDB.class_exists(root_node_type):
256
+ root_node = ClassDB.instantiate(root_node_type)
257
+ else:
258
+ return _send_error(client_id, "Invalid root node type: %s" % root_node_type, command_id)
259
+
260
+ # Give the root node a name based on the file name
261
+ var file_name = path.get_file().get_basename()
262
+ root_node.name = file_name
263
+
264
+ # Create a packed scene
265
+ var packed_scene = PackedScene.new()
266
+ var result = packed_scene.pack(root_node)
267
+ if result != OK:
268
+ root_node.free()
269
+ return _send_error(client_id, "Failed to pack scene: %d" % result, command_id)
270
+
271
+ # Save the packed scene to disk
272
+ result = ResourceSaver.save(packed_scene, path)
273
+ if result != OK:
274
+ root_node.free()
275
+ return _send_error(client_id, "Failed to save scene: %d" % result, command_id)
276
+
277
+ # Clean up
278
+ root_node.free()
279
+
280
+ # Try to open the scene in the editor
281
+ var plugin = Engine.get_meta("GodotMCPPlugin") if Engine.has_meta("GodotMCPPlugin") else null
282
+ if plugin and plugin.has_method("get_editor_interface"):
283
+ var editor_interface = plugin.get_editor_interface()
284
+ editor_interface.open_scene_from_path(path)
285
+
286
+ _send_success(client_id, {
287
+ "scene_path": path,
288
+ "root_node_type": root_node_type
289
+ }, command_id)
290
+
291
+ func _delete_scene(client_id: int, params: Dictionary, command_id: String) -> void:
292
+ var path = params.get("path", "")
293
+
294
+ # Validation
295
+ if path.is_empty():
296
+ return _send_error(client_id, "Scene path cannot be empty", command_id)
297
+
298
+ # Make sure we have an absolute path
299
+ if not path.begins_with("res://"):
300
+ path = "res://" + path
301
+
302
+ # Ensure path ends with .tscn
303
+ if not path.ends_with(".tscn"):
304
+ path += ".tscn"
305
+
306
+ # Check if file exists
307
+ if not FileAccess.file_exists(path):
308
+ return _send_error(client_id, "Scene file not found: %s" % path, command_id)
309
+
310
+ # Check if this is the currently open scene
311
+ var plugin = Engine.get_meta("GodotMCPPlugin") if Engine.has_meta("GodotMCPPlugin") else null
312
+ if plugin and plugin.has_method("get_editor_interface"):
313
+ var editor_interface = plugin.get_editor_interface()
314
+ var edited_scene_root = editor_interface.get_edited_scene_root()
315
+
316
+ if edited_scene_root and edited_scene_root.scene_file_path == path:
317
+ return _send_error(client_id, "Cannot delete currently open scene: %s. Close it first." % path, command_id)
318
+
319
+ # Delete the file
320
+ var dir = DirAccess.open("res://")
321
+ if not dir:
322
+ return _send_error(client_id, "Failed to access project directory", command_id)
323
+
324
+ var result = dir.remove(path)
325
+ if result != OK:
326
+ return _send_error(client_id, "Failed to delete scene file: %d" % result, command_id)
327
+
328
+ # Rescan the filesystem to update the editor
329
+ if plugin and plugin.has_method("get_editor_interface"):
330
+ var editor_interface = plugin.get_editor_interface()
331
+ var filesystem = editor_interface.get_resource_filesystem()
332
+ if filesystem:
333
+ filesystem.scan()
334
+
335
+ _send_success(client_id, {
336
+ "scene_path": path
337
+ }, command_id)
@@ -0,0 +1 @@
1
+ uid://n3ktd1vtbv1p
@@ -0,0 +1,349 @@
1
+ @tool
2
+ class_name MCPScriptCommands
3
+ extends MCPBaseCommandProcessor
4
+
5
+ func process_command(client_id: int, command_type: String, params: Dictionary, command_id: String) -> bool:
6
+ match command_type:
7
+ "create_script":
8
+ _create_script(client_id, params, command_id)
9
+ return true
10
+ "edit_script":
11
+ _edit_script(client_id, params, command_id)
12
+ return true
13
+ "get_script":
14
+ _get_script(client_id, params, command_id)
15
+ return true
16
+ "get_script_metadata":
17
+ _get_script_metadata(client_id, params, command_id)
18
+ return true
19
+ "get_current_script":
20
+ _get_current_script(client_id, params, command_id)
21
+ return true
22
+ return false # Command not handled
23
+
24
+ # Add this function to help find script files
25
+ func _find_script_file(script_name: String) -> String:
26
+ # First, normalize the path
27
+ var script_path = script_name
28
+
29
+ # Add extension if missing
30
+ if not script_path.ends_with(".gd") and not script_path.ends_with(".cs"):
31
+ script_path += ".gd" # Default to GDScript
32
+
33
+ # If path already contains res://, use it directly
34
+ if script_path.begins_with("res://"):
35
+ if FileAccess.file_exists(script_path):
36
+ return script_path
37
+ else:
38
+ # Add res:// prefix if missing
39
+ script_path = "res://" + script_path
40
+ if FileAccess.file_exists(script_path):
41
+ return script_path
42
+
43
+ # If not found directly, try common script locations
44
+ var file_name = script_path.get_file()
45
+ var common_dirs = [
46
+ "res://scripts/",
47
+ "res://",
48
+ "res://scenes/",
49
+ "res://addons/"
50
+ ]
51
+
52
+ for dir in common_dirs:
53
+ var test_path = dir + file_name
54
+ if FileAccess.file_exists(test_path):
55
+ return test_path
56
+
57
+ return "" # Not found
58
+
59
+ func _create_script(client_id: int, params: Dictionary, command_id: String) -> void:
60
+ var script_path = params.get("script_path", "")
61
+ var content = params.get("content", "")
62
+ var node_path = params.get("node_path", "")
63
+
64
+ # Validation
65
+ if script_path.is_empty():
66
+ return _send_error(client_id, "Script path cannot be empty", command_id)
67
+
68
+ # Make sure we have an absolute path
69
+ if not script_path.begins_with("res://"):
70
+ script_path = "res://" + script_path
71
+
72
+ if not script_path.ends_with(".gd"):
73
+ script_path += ".gd"
74
+
75
+ # Get editor plugin and interfaces
76
+ var plugin = Engine.get_meta("GodotMCPPlugin")
77
+ if not plugin:
78
+ return _send_error(client_id, "GodotMCPPlugin not found in Engine metadata", command_id)
79
+
80
+ var editor_interface = plugin.get_editor_interface()
81
+ var script_editor = editor_interface.get_script_editor()
82
+
83
+ # Create the directory if it doesn't exist
84
+ var dir = script_path.get_base_dir()
85
+ if not DirAccess.dir_exists_absolute(dir):
86
+ var err = DirAccess.make_dir_recursive_absolute(dir)
87
+ if err != OK:
88
+ return _send_error(client_id, "Failed to create directory: %s (Error code: %d)" % [dir, err], command_id)
89
+
90
+ # Create the script file
91
+ var file = FileAccess.open(script_path, FileAccess.WRITE)
92
+ if file == null:
93
+ return _send_error(client_id, "Failed to create script file: %s" % script_path, command_id)
94
+
95
+ file.store_string(content)
96
+ file = null # Close the file
97
+
98
+ # Refresh the filesystem
99
+ editor_interface.get_resource_filesystem().scan()
100
+
101
+ # Attach the script to a node if specified
102
+ if not node_path.is_empty():
103
+ var node = _get_editor_node(node_path)
104
+ if not node:
105
+ # Try enhanced node resolution
106
+ node = _get_editor_node_enhanced(node_path)
107
+ if not node:
108
+ return _send_error(client_id, "Node not found: %s" % node_path, command_id)
109
+
110
+ # Wait for script to be recognized in the filesystem
111
+ await get_tree().create_timer(0.5).timeout
112
+
113
+ var script = load(script_path)
114
+ if not script:
115
+ return _send_error(client_id, "Failed to load script: %s" % script_path, command_id)
116
+
117
+ # Use undo/redo for script assignment
118
+ var undo_redo = _get_undo_redo()
119
+ if not undo_redo:
120
+ # Fallback method if we can't get undo/redo
121
+ node.set_script(script)
122
+ _mark_scene_modified()
123
+ else:
124
+ # Use undo/redo for proper editor integration
125
+ undo_redo.create_action("Assign Script")
126
+ undo_redo.add_do_method(node, "set_script", script)
127
+ undo_redo.add_undo_method(node, "set_script", node.get_script())
128
+ undo_redo.commit_action()
129
+
130
+ # Mark the scene as modified
131
+ _mark_scene_modified()
132
+
133
+ # Open the script in the editor
134
+ var script_resource = load(script_path)
135
+ if script_resource:
136
+ editor_interface.edit_script(script_resource)
137
+
138
+ _send_success(client_id, {
139
+ "script_path": script_path,
140
+ "node_path": node_path
141
+ }, command_id)
142
+
143
+ func _edit_script(client_id: int, params: Dictionary, command_id: String) -> void:
144
+ var script_path = params.get("script_path", "")
145
+ var content = params.get("content", "")
146
+
147
+ # Validation
148
+ if script_path.is_empty():
149
+ return _send_error(client_id, "Script path cannot be empty", command_id)
150
+
151
+ if content.is_empty():
152
+ return _send_error(client_id, "Content cannot be empty", command_id)
153
+
154
+ # Make sure we have an absolute path
155
+ if not script_path.begins_with("res://"):
156
+ script_path = "res://" + script_path
157
+
158
+ # Try to find the script if not found directly
159
+ if not FileAccess.file_exists(script_path):
160
+ var found_path = _find_script_file(script_path)
161
+ if not found_path.is_empty():
162
+ script_path = found_path
163
+ else:
164
+ return _send_error(client_id, "Script file not found: %s" % script_path, command_id)
165
+
166
+ # Edit the script file
167
+ var file = FileAccess.open(script_path, FileAccess.WRITE)
168
+ if file == null:
169
+ return _send_error(client_id, "Failed to open script file: %s" % script_path, command_id)
170
+
171
+ file.store_string(content)
172
+ file = null # Close the file
173
+
174
+ _send_success(client_id, {
175
+ "script_path": script_path
176
+ }, command_id)
177
+
178
+ func _get_script(client_id: int, params: Dictionary, command_id: String) -> void:
179
+ var script_path = params.get("script_path", "")
180
+ var node_path = params.get("node_path", "")
181
+
182
+ # Validation - either script_path or node_path must be provided
183
+ if script_path.is_empty() and node_path.is_empty():
184
+ return _send_error(client_id, "Either script_path or node_path must be provided", command_id)
185
+
186
+ # If node_path is provided, get the script from the node
187
+ if not node_path.is_empty():
188
+ var node = _get_editor_node(node_path)
189
+ if not node:
190
+ # Try enhanced node resolution
191
+ node = _get_editor_node_enhanced(node_path)
192
+ if not node:
193
+ return _send_error(client_id, "Node not found: %s" % node_path, command_id)
194
+
195
+ var script = node.get_script()
196
+ if not script:
197
+ return _send_error(client_id, "Node does not have a script: %s" % node_path, command_id)
198
+
199
+ # Handle various script types safely
200
+ if typeof(script) == TYPE_OBJECT and "resource_path" in script:
201
+ script_path = script.resource_path
202
+ elif typeof(script) == TYPE_STRING:
203
+ script_path = script
204
+ else:
205
+ # Try to handle other script types gracefully
206
+ print("Script type is not directly supported: ", typeof(script))
207
+ if script.has_method("get_path"):
208
+ script_path = script.get_path()
209
+ elif script.has_method("get_source_code"):
210
+ # Return the script content directly
211
+ _send_success(client_id, {
212
+ "script_path": node_path + " (embedded script)",
213
+ "content": script.get_source_code()
214
+ }, command_id)
215
+ return
216
+ else:
217
+ return _send_error(client_id, "Cannot extract script path from node: %s" % node_path, command_id)
218
+
219
+ # Try to find the script if it's not found directly
220
+ if not FileAccess.file_exists(script_path):
221
+ var found_path = _find_script_file(script_path)
222
+ if not found_path.is_empty():
223
+ script_path = found_path
224
+ else:
225
+ return _send_error(client_id, "Script file not found: %s" % script_path, command_id)
226
+
227
+ # Read the script file
228
+ var file = FileAccess.open(script_path, FileAccess.READ)
229
+ if file == null:
230
+ return _send_error(client_id, "Failed to open script file: %s" % script_path, command_id)
231
+
232
+ var content = file.get_as_text()
233
+ file = null # Close the file
234
+
235
+ _send_success(client_id, {
236
+ "script_path": script_path,
237
+ "content": content
238
+ }, command_id)
239
+
240
+ func _get_script_metadata(client_id: int, params: Dictionary, command_id: String) -> void:
241
+ var path = params.get("path", "")
242
+
243
+ # Validation
244
+ if path.is_empty():
245
+ return _send_error(client_id, "Script path cannot be empty", command_id)
246
+
247
+ if not path.begins_with("res://"):
248
+ path = "res://" + path
249
+
250
+ # Try to find the script if it's not found directly
251
+ if not FileAccess.file_exists(path):
252
+ var found_path = _find_script_file(path)
253
+ if not found_path.is_empty():
254
+ path = found_path
255
+ else:
256
+ return _send_error(client_id, "Script file not found: " + path, command_id)
257
+
258
+ # Load the script
259
+ var script = load(path)
260
+ if not script:
261
+ return _send_error(client_id, "Failed to load script: " + path, command_id)
262
+
263
+ # Extract script metadata
264
+ var metadata = {
265
+ "path": path,
266
+ "language": "gdscript" if path.ends_with(".gd") else "csharp" if path.ends_with(".cs") else "unknown"
267
+ }
268
+
269
+ # Attempt to get script class info
270
+ var class_name_str = ""
271
+ var extends_class = ""
272
+
273
+ # Read the file to extract class_name and extends info
274
+ var file = FileAccess.open(path, FileAccess.READ)
275
+ if file:
276
+ var content = file.get_as_text()
277
+
278
+ # Extract class_name
279
+ var class_regex = RegEx.new()
280
+ class_regex.compile("class_name\\s+([a-zA-Z_][a-zA-Z0-9_]*)")
281
+ var result = class_regex.search(content)
282
+ if result:
283
+ class_name_str = result.get_string(1)
284
+
285
+ # Extract extends
286
+ var extends_regex = RegEx.new()
287
+ extends_regex.compile("extends\\s+([a-zA-Z_][a-zA-Z0-9_]*)")
288
+ result = extends_regex.search(content)
289
+ if result:
290
+ extends_class = result.get_string(1)
291
+
292
+ # Add to metadata
293
+ metadata["class_name"] = class_name_str
294
+ metadata["extends"] = extends_class
295
+
296
+ # Try to extract methods and signals
297
+ var methods = []
298
+ var signals = []
299
+
300
+ var method_regex = RegEx.new()
301
+ method_regex.compile("func\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(")
302
+ var method_matches = method_regex.search_all(content)
303
+
304
+ for match_result in method_matches:
305
+ methods.append(match_result.get_string(1))
306
+
307
+ var signal_regex = RegEx.new()
308
+ signal_regex.compile("signal\\s+([a-zA-Z_][a-zA-Z0-9_]*)")
309
+ var signal_matches = signal_regex.search_all(content)
310
+
311
+ for match_result in signal_matches:
312
+ signals.append(match_result.get_string(1))
313
+
314
+ metadata["methods"] = methods
315
+ metadata["signals"] = signals
316
+
317
+ _send_success(client_id, metadata, command_id)
318
+
319
+ func _get_current_script(client_id: int, params: Dictionary, command_id: String) -> void:
320
+ # Get editor plugin and interfaces
321
+ var plugin = Engine.get_meta("GodotMCPPlugin")
322
+ if not plugin:
323
+ return _send_error(client_id, "GodotMCPPlugin not found in Engine metadata", command_id)
324
+
325
+ var editor_interface = plugin.get_editor_interface()
326
+ var script_editor = editor_interface.get_script_editor()
327
+ var current_script = script_editor.get_current_script()
328
+
329
+ if not current_script:
330
+ return _send_success(client_id, {
331
+ "script_found": false,
332
+ "message": "No script is currently being edited"
333
+ }, command_id)
334
+
335
+ var script_path = current_script.resource_path
336
+
337
+ # Read the script content
338
+ var file = FileAccess.open(script_path, FileAccess.READ)
339
+ if not file:
340
+ return _send_error(client_id, "Failed to open script file: %s" % script_path, command_id)
341
+
342
+ var content = file.get_as_text()
343
+ file = null # Close the file
344
+
345
+ _send_success(client_id, {
346
+ "script_found": true,
347
+ "script_path": script_path,
348
+ "content": content
349
+ }, command_id)
@@ -0,0 +1 @@
1
+ uid://bbfou75clfpg5