@satelliteoflove/godot-mcp 2.2.0 → 2.4.0
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 +3 -4
- package/addon/command_router.gd +39 -0
- package/addon/command_router.gd.uid +1 -0
- package/addon/commands/animation_commands.gd +633 -0
- package/addon/commands/animation_commands.gd.uid +1 -0
- package/addon/commands/debug_commands.gd +109 -0
- package/addon/commands/debug_commands.gd.uid +1 -0
- package/addon/commands/file_commands.gd +95 -0
- package/addon/commands/file_commands.gd.uid +1 -0
- package/addon/commands/node_commands.gd +252 -0
- package/addon/commands/node_commands.gd.uid +1 -0
- package/addon/commands/project_commands.gd +114 -0
- package/addon/commands/project_commands.gd.uid +1 -0
- package/addon/commands/resource_commands.gd +293 -0
- package/addon/commands/resource_commands.gd.uid +1 -0
- package/addon/commands/scene3d_commands.gd +169 -0
- package/addon/commands/scene3d_commands.gd.uid +1 -0
- package/addon/commands/scene_commands.gd +131 -0
- package/addon/commands/scene_commands.gd.uid +1 -0
- package/addon/commands/screenshot_commands.gd +130 -0
- package/addon/commands/screenshot_commands.gd.uid +1 -0
- package/addon/commands/script_commands.gd +156 -0
- package/addon/commands/script_commands.gd.uid +1 -0
- package/addon/commands/selection_commands.gd +170 -0
- package/addon/commands/selection_commands.gd.uid +1 -0
- package/addon/commands/system_commands.gd +29 -0
- package/addon/commands/tilemap_commands.gd +657 -0
- package/addon/commands/tilemap_commands.gd.uid +1 -0
- package/addon/core/base_command.gd +29 -0
- package/addon/core/base_command.gd.uid +1 -0
- package/addon/core/mcp_debugger_plugin.gd +144 -0
- package/addon/core/mcp_debugger_plugin.gd.uid +1 -0
- package/addon/core/mcp_logger.gd +40 -0
- package/addon/core/mcp_logger.gd.uid +1 -0
- package/addon/core/mcp_utils.gd +125 -0
- package/addon/core/mcp_utils.gd.uid +1 -0
- package/addon/game_bridge/mcp_game_bridge.gd +195 -0
- package/addon/game_bridge/mcp_game_bridge.gd.uid +1 -0
- package/addon/plugin.cfg +8 -0
- package/addon/plugin.gd +89 -0
- package/addon/plugin.gd.uid +1 -0
- package/addon/ui/status_panel.gd +23 -0
- package/addon/ui/status_panel.gd.uid +1 -0
- package/addon/ui/status_panel.tscn +41 -0
- package/addon/websocket_server.gd +143 -0
- package/addon/websocket_server.gd.uid +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +60 -0
- package/dist/cli.js.map +1 -0
- package/dist/connection/websocket.d.ts +14 -0
- package/dist/connection/websocket.d.ts.map +1 -1
- package/dist/connection/websocket.js +69 -1
- package/dist/connection/websocket.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -6
- package/dist/index.js.map +1 -1
- package/dist/installer/install.d.ts +13 -0
- package/dist/installer/install.d.ts.map +1 -0
- package/dist/installer/install.js +77 -0
- package/dist/installer/install.js.map +1 -0
- package/dist/tools/editor.d.ts +20 -5
- package/dist/tools/editor.d.ts.map +1 -1
- package/dist/tools/editor.js +27 -4
- package/dist/tools/editor.js.map +1 -1
- package/dist/tools/project.d.ts +3 -3
- package/dist/tools/project.d.ts.map +1 -1
- package/dist/tools/project.js +28 -2
- package/dist/tools/project.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +20 -0
- package/dist/version.js.map +1 -0
- package/package.json +6 -4
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends MCPBaseCommand
|
|
3
|
+
class_name MCPDebugCommands
|
|
4
|
+
|
|
5
|
+
const DEBUG_OUTPUT_TIMEOUT := 5.0
|
|
6
|
+
const PERFORMANCE_METRICS_TIMEOUT := 5.0
|
|
7
|
+
|
|
8
|
+
var _debug_output_result: PackedStringArray = []
|
|
9
|
+
var _debug_output_pending: bool = false
|
|
10
|
+
|
|
11
|
+
var _performance_metrics_result: Dictionary = {}
|
|
12
|
+
var _performance_metrics_pending: bool = false
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
func get_commands() -> Dictionary:
|
|
16
|
+
return {
|
|
17
|
+
"run_project": run_project,
|
|
18
|
+
"stop_project": stop_project,
|
|
19
|
+
"get_debug_output": get_debug_output,
|
|
20
|
+
"get_performance_metrics": get_performance_metrics
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
func run_project(params: Dictionary) -> Dictionary:
|
|
25
|
+
var scene_path: String = params.get("scene_path", "")
|
|
26
|
+
|
|
27
|
+
MCPLogger.clear()
|
|
28
|
+
|
|
29
|
+
if scene_path.is_empty():
|
|
30
|
+
EditorInterface.play_main_scene()
|
|
31
|
+
else:
|
|
32
|
+
EditorInterface.play_custom_scene(scene_path)
|
|
33
|
+
|
|
34
|
+
return _success({})
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
func stop_project(_params: Dictionary) -> Dictionary:
|
|
38
|
+
EditorInterface.stop_playing_scene()
|
|
39
|
+
return _success({})
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
func get_debug_output(params: Dictionary) -> Dictionary:
|
|
43
|
+
var clear: bool = params.get("clear", false)
|
|
44
|
+
|
|
45
|
+
if not EditorInterface.is_playing_scene():
|
|
46
|
+
var output := "\n".join(MCPLogger.get_output())
|
|
47
|
+
if clear:
|
|
48
|
+
MCPLogger.clear()
|
|
49
|
+
return _success({"output": output})
|
|
50
|
+
|
|
51
|
+
var debugger_plugin = _plugin.get_debugger_plugin() if _plugin else null
|
|
52
|
+
if debugger_plugin == null or not debugger_plugin.has_active_session():
|
|
53
|
+
var output := "\n".join(MCPLogger.get_output())
|
|
54
|
+
if clear:
|
|
55
|
+
MCPLogger.clear()
|
|
56
|
+
return _success({"output": output})
|
|
57
|
+
|
|
58
|
+
_debug_output_pending = true
|
|
59
|
+
_debug_output_result = PackedStringArray()
|
|
60
|
+
|
|
61
|
+
debugger_plugin.debug_output_received.connect(_on_debug_output_received, CONNECT_ONE_SHOT)
|
|
62
|
+
debugger_plugin.request_debug_output(clear)
|
|
63
|
+
|
|
64
|
+
var start_time := Time.get_ticks_msec()
|
|
65
|
+
while _debug_output_pending:
|
|
66
|
+
await Engine.get_main_loop().process_frame
|
|
67
|
+
if (Time.get_ticks_msec() - start_time) / 1000.0 > DEBUG_OUTPUT_TIMEOUT:
|
|
68
|
+
_debug_output_pending = false
|
|
69
|
+
if debugger_plugin.debug_output_received.is_connected(_on_debug_output_received):
|
|
70
|
+
debugger_plugin.debug_output_received.disconnect(_on_debug_output_received)
|
|
71
|
+
return _success({"output": "\n".join(MCPLogger.get_output())})
|
|
72
|
+
|
|
73
|
+
return _success({"output": "\n".join(_debug_output_result)})
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
func _on_debug_output_received(output: PackedStringArray) -> void:
|
|
77
|
+
_debug_output_pending = false
|
|
78
|
+
_debug_output_result = output
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
func get_performance_metrics(_params: Dictionary) -> Dictionary:
|
|
82
|
+
if not EditorInterface.is_playing_scene():
|
|
83
|
+
return _error("NOT_RUNNING", "No game is currently running")
|
|
84
|
+
|
|
85
|
+
var debugger_plugin = _plugin.get_debugger_plugin() if _plugin else null
|
|
86
|
+
if debugger_plugin == null or not debugger_plugin.has_active_session():
|
|
87
|
+
return _error("NO_SESSION", "No active debug session")
|
|
88
|
+
|
|
89
|
+
_performance_metrics_pending = true
|
|
90
|
+
_performance_metrics_result = {}
|
|
91
|
+
|
|
92
|
+
debugger_plugin.performance_metrics_received.connect(_on_performance_metrics_received, CONNECT_ONE_SHOT)
|
|
93
|
+
debugger_plugin.request_performance_metrics()
|
|
94
|
+
|
|
95
|
+
var start_time := Time.get_ticks_msec()
|
|
96
|
+
while _performance_metrics_pending:
|
|
97
|
+
await Engine.get_main_loop().process_frame
|
|
98
|
+
if (Time.get_ticks_msec() - start_time) / 1000.0 > PERFORMANCE_METRICS_TIMEOUT:
|
|
99
|
+
_performance_metrics_pending = false
|
|
100
|
+
if debugger_plugin.performance_metrics_received.is_connected(_on_performance_metrics_received):
|
|
101
|
+
debugger_plugin.performance_metrics_received.disconnect(_on_performance_metrics_received)
|
|
102
|
+
return _error("TIMEOUT", "Timed out waiting for performance metrics")
|
|
103
|
+
|
|
104
|
+
return _success(_performance_metrics_result)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
func _on_performance_metrics_received(metrics: Dictionary) -> void:
|
|
108
|
+
_performance_metrics_pending = false
|
|
109
|
+
_performance_metrics_result = metrics
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://b8870g3hyn1fd
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends MCPBaseCommand
|
|
3
|
+
class_name MCPFileCommands
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
func get_commands() -> Dictionary:
|
|
7
|
+
return {
|
|
8
|
+
"list_project_files": list_project_files,
|
|
9
|
+
"search_files": search_files
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
func list_project_files(params: Dictionary) -> Dictionary:
|
|
14
|
+
var file_type: String = params.get("file_type", "all")
|
|
15
|
+
var directory: String = params.get("directory", "res://")
|
|
16
|
+
var recursive: bool = params.get("recursive", true)
|
|
17
|
+
|
|
18
|
+
var extensions: PackedStringArray
|
|
19
|
+
match file_type:
|
|
20
|
+
"scripts":
|
|
21
|
+
extensions = PackedStringArray(["gd", "cs"])
|
|
22
|
+
"scenes":
|
|
23
|
+
extensions = PackedStringArray(["tscn", "scn"])
|
|
24
|
+
"resources":
|
|
25
|
+
extensions = PackedStringArray(["tres", "res"])
|
|
26
|
+
"images":
|
|
27
|
+
extensions = PackedStringArray(["png", "jpg", "jpeg", "webp", "svg"])
|
|
28
|
+
"audio":
|
|
29
|
+
extensions = PackedStringArray(["ogg", "mp3", "wav"])
|
|
30
|
+
"all":
|
|
31
|
+
extensions = PackedStringArray()
|
|
32
|
+
_:
|
|
33
|
+
return _error("INVALID_TYPE", "Unknown file type: %s" % file_type)
|
|
34
|
+
|
|
35
|
+
var files := _scan_directory(directory, extensions, recursive)
|
|
36
|
+
return _success({"files": files})
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
func search_files(params: Dictionary) -> Dictionary:
|
|
40
|
+
var pattern: String = params.get("pattern", "")
|
|
41
|
+
var directory: String = params.get("directory", "res://")
|
|
42
|
+
|
|
43
|
+
if pattern.is_empty():
|
|
44
|
+
return _error("INVALID_PARAMS", "pattern is required")
|
|
45
|
+
|
|
46
|
+
var all_files := _scan_directory(directory, PackedStringArray(), true)
|
|
47
|
+
var matching: Array[String] = []
|
|
48
|
+
|
|
49
|
+
for file_path in all_files:
|
|
50
|
+
var file_name := file_path.get_file()
|
|
51
|
+
if _matches_pattern(file_name, pattern):
|
|
52
|
+
matching.append(file_path)
|
|
53
|
+
|
|
54
|
+
return _success({"files": matching})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
func _scan_directory(path: String, extensions: PackedStringArray, recursive: bool) -> Array[String]:
|
|
58
|
+
var files: Array[String] = []
|
|
59
|
+
var dir := DirAccess.open(path)
|
|
60
|
+
|
|
61
|
+
if not dir:
|
|
62
|
+
return files
|
|
63
|
+
|
|
64
|
+
dir.list_dir_begin()
|
|
65
|
+
var file_name := dir.get_next()
|
|
66
|
+
|
|
67
|
+
while not file_name.is_empty():
|
|
68
|
+
var full_path := path.path_join(file_name)
|
|
69
|
+
|
|
70
|
+
if dir.current_is_dir():
|
|
71
|
+
if recursive and not file_name.begins_with("."):
|
|
72
|
+
files.append_array(_scan_directory(full_path, extensions, recursive))
|
|
73
|
+
else:
|
|
74
|
+
if extensions.is_empty():
|
|
75
|
+
files.append(full_path)
|
|
76
|
+
else:
|
|
77
|
+
var ext := file_name.get_extension().to_lower()
|
|
78
|
+
if ext in extensions:
|
|
79
|
+
files.append(full_path)
|
|
80
|
+
|
|
81
|
+
file_name = dir.get_next()
|
|
82
|
+
|
|
83
|
+
dir.list_dir_end()
|
|
84
|
+
return files
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
func _matches_pattern(text: String, pattern: String) -> bool:
|
|
88
|
+
if not "*" in pattern:
|
|
89
|
+
return pattern.to_lower() in text.to_lower()
|
|
90
|
+
|
|
91
|
+
var regex_pattern := "^" + pattern.replace(".", "\\.").replace("*", ".*") + "$"
|
|
92
|
+
var regex := RegEx.new()
|
|
93
|
+
regex.compile(regex_pattern)
|
|
94
|
+
|
|
95
|
+
return regex.search(text.to_lower()) != null
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://cfxkg4lhdnxgw
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends MCPBaseCommand
|
|
3
|
+
class_name MCPNodeCommands
|
|
4
|
+
|
|
5
|
+
var _find_nodes_pending := false
|
|
6
|
+
var _find_nodes_result: Dictionary = {}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
func get_commands() -> Dictionary:
|
|
10
|
+
return {
|
|
11
|
+
"get_node_properties": get_node_properties,
|
|
12
|
+
"find_nodes": find_nodes,
|
|
13
|
+
"create_node": create_node,
|
|
14
|
+
"update_node": update_node,
|
|
15
|
+
"delete_node": delete_node,
|
|
16
|
+
"reparent_node": reparent_node
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
func _require_scene_open() -> Dictionary:
|
|
21
|
+
var root := EditorInterface.get_edited_scene_root()
|
|
22
|
+
if not root:
|
|
23
|
+
return _error("NO_SCENE", "No scene is currently open")
|
|
24
|
+
return {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
func get_node_properties(params: Dictionary) -> Dictionary:
|
|
28
|
+
var node_path: String = params.get("node_path", "")
|
|
29
|
+
if node_path.is_empty():
|
|
30
|
+
return _error("INVALID_PARAMS", "node_path is required")
|
|
31
|
+
|
|
32
|
+
var node := _get_node(node_path)
|
|
33
|
+
if not node:
|
|
34
|
+
return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
|
|
35
|
+
|
|
36
|
+
var properties := {}
|
|
37
|
+
for prop in node.get_property_list():
|
|
38
|
+
var name: String = prop["name"]
|
|
39
|
+
if name.begins_with("_") or prop["usage"] & PROPERTY_USAGE_SCRIPT_VARIABLE == 0:
|
|
40
|
+
if prop["usage"] & PROPERTY_USAGE_EDITOR == 0:
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
var value = node.get(name)
|
|
44
|
+
properties[name] = _serialize_value(value)
|
|
45
|
+
|
|
46
|
+
return _success({"properties": properties})
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
func find_nodes(params: Dictionary) -> Dictionary:
|
|
50
|
+
var name_pattern: String = params.get("name_pattern", "")
|
|
51
|
+
var type_filter: String = params.get("type", "")
|
|
52
|
+
var root_path: String = params.get("root_path", "")
|
|
53
|
+
|
|
54
|
+
if name_pattern.is_empty() and type_filter.is_empty():
|
|
55
|
+
return _error("INVALID_PARAMS", "At least one of name_pattern or type is required")
|
|
56
|
+
|
|
57
|
+
var debugger := _plugin.get_debugger_plugin() as MCPDebuggerPlugin
|
|
58
|
+
if debugger and debugger.has_active_session():
|
|
59
|
+
return await _find_nodes_via_game(debugger, name_pattern, type_filter, root_path)
|
|
60
|
+
|
|
61
|
+
var scene_check := _require_scene_open()
|
|
62
|
+
if not scene_check.is_empty():
|
|
63
|
+
return scene_check
|
|
64
|
+
|
|
65
|
+
var scene_root := EditorInterface.get_edited_scene_root()
|
|
66
|
+
var search_root: Node = scene_root
|
|
67
|
+
|
|
68
|
+
if not root_path.is_empty():
|
|
69
|
+
search_root = _get_node(root_path)
|
|
70
|
+
if not search_root:
|
|
71
|
+
return _error("NODE_NOT_FOUND", "Root node not found: %s" % root_path)
|
|
72
|
+
|
|
73
|
+
var matches: Array[Dictionary] = []
|
|
74
|
+
_find_recursive(search_root, scene_root, name_pattern, type_filter, matches)
|
|
75
|
+
|
|
76
|
+
return _success({"matches": matches, "count": matches.size()})
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
func _find_nodes_via_game(debugger: MCPDebuggerPlugin, name_pattern: String, type_filter: String, root_path: String) -> Dictionary:
|
|
80
|
+
_find_nodes_pending = true
|
|
81
|
+
_find_nodes_result = {}
|
|
82
|
+
|
|
83
|
+
debugger.find_nodes_received.connect(_on_find_nodes_received, CONNECT_ONE_SHOT)
|
|
84
|
+
debugger.request_find_nodes(name_pattern, type_filter, root_path)
|
|
85
|
+
|
|
86
|
+
while _find_nodes_pending:
|
|
87
|
+
await Engine.get_main_loop().process_frame
|
|
88
|
+
|
|
89
|
+
return _find_nodes_result
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
func _on_find_nodes_received(matches: Array, count: int, error: String) -> void:
|
|
93
|
+
_find_nodes_pending = false
|
|
94
|
+
if not error.is_empty():
|
|
95
|
+
_find_nodes_result = _error("GAME_ERROR", error)
|
|
96
|
+
else:
|
|
97
|
+
_find_nodes_result = _success({"matches": matches, "count": count})
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
func _find_recursive(node: Node, scene_root: Node, name_pattern: String, type_filter: String, results: Array[Dictionary]) -> void:
|
|
101
|
+
var name_matches := name_pattern.is_empty() or node.name.matchn(name_pattern)
|
|
102
|
+
var type_matches := type_filter.is_empty() or node.is_class(type_filter)
|
|
103
|
+
|
|
104
|
+
if name_matches and type_matches:
|
|
105
|
+
var relative_path := scene_root.get_path_to(node)
|
|
106
|
+
var usable_path := "/root/" + scene_root.name
|
|
107
|
+
if relative_path != NodePath("."):
|
|
108
|
+
usable_path += "/" + str(relative_path)
|
|
109
|
+
|
|
110
|
+
results.append({
|
|
111
|
+
"path": usable_path,
|
|
112
|
+
"type": node.get_class()
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
for child in node.get_children():
|
|
116
|
+
_find_recursive(child, scene_root, name_pattern, type_filter, results)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
func create_node(params: Dictionary) -> Dictionary:
|
|
120
|
+
var scene_check := _require_scene_open()
|
|
121
|
+
if not scene_check.is_empty():
|
|
122
|
+
return scene_check
|
|
123
|
+
|
|
124
|
+
var parent_path: String = params.get("parent_path", "")
|
|
125
|
+
var node_type: String = params.get("node_type", "")
|
|
126
|
+
var scene_path: String = params.get("scene_path", "")
|
|
127
|
+
var node_name: String = params.get("node_name", "")
|
|
128
|
+
var properties: Dictionary = params.get("properties", {})
|
|
129
|
+
|
|
130
|
+
if parent_path.is_empty():
|
|
131
|
+
return _error("INVALID_PARAMS", "parent_path is required")
|
|
132
|
+
if node_name.is_empty():
|
|
133
|
+
return _error("INVALID_PARAMS", "node_name is required")
|
|
134
|
+
if node_type.is_empty() and scene_path.is_empty():
|
|
135
|
+
return _error("INVALID_PARAMS", "Either node_type or scene_path is required")
|
|
136
|
+
if not node_type.is_empty() and not scene_path.is_empty():
|
|
137
|
+
return _error("INVALID_PARAMS", "Provide node_type OR scene_path, not both")
|
|
138
|
+
|
|
139
|
+
var parent := _get_node(parent_path)
|
|
140
|
+
if not parent:
|
|
141
|
+
return _error("NODE_NOT_FOUND", "Parent node not found: %s" % parent_path)
|
|
142
|
+
|
|
143
|
+
var node: Node
|
|
144
|
+
if not scene_path.is_empty():
|
|
145
|
+
if not ResourceLoader.exists(scene_path):
|
|
146
|
+
return _error("SCENE_NOT_FOUND", "Scene not found: %s" % scene_path)
|
|
147
|
+
var packed_scene: PackedScene = load(scene_path)
|
|
148
|
+
if not packed_scene:
|
|
149
|
+
return _error("LOAD_FAILED", "Failed to load scene: %s" % scene_path)
|
|
150
|
+
node = packed_scene.instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
|
|
151
|
+
if not node:
|
|
152
|
+
return _error("INSTANTIATE_FAILED", "Failed to instantiate: %s" % scene_path)
|
|
153
|
+
else:
|
|
154
|
+
if not ClassDB.class_exists(node_type):
|
|
155
|
+
return _error("INVALID_TYPE", "Unknown node type: %s" % node_type)
|
|
156
|
+
node = ClassDB.instantiate(node_type)
|
|
157
|
+
if not node:
|
|
158
|
+
return _error("CREATE_FAILED", "Failed to create node of type: %s" % node_type)
|
|
159
|
+
|
|
160
|
+
node.name = node_name
|
|
161
|
+
|
|
162
|
+
for key in properties:
|
|
163
|
+
if node.has_method("set") and key in node:
|
|
164
|
+
var deserialized := MCPUtils.deserialize_value(properties[key])
|
|
165
|
+
node.set(key, deserialized)
|
|
166
|
+
|
|
167
|
+
parent.add_child(node)
|
|
168
|
+
_set_owner_recursive(node, EditorInterface.get_edited_scene_root())
|
|
169
|
+
|
|
170
|
+
return _success({"node_path": str(node.get_path())})
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
func _set_owner_recursive(node: Node, owner: Node) -> void:
|
|
174
|
+
node.owner = owner
|
|
175
|
+
for child in node.get_children():
|
|
176
|
+
_set_owner_recursive(child, owner)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
func update_node(params: Dictionary) -> Dictionary:
|
|
180
|
+
var node_path: String = params.get("node_path", "")
|
|
181
|
+
var properties: Dictionary = params.get("properties", {})
|
|
182
|
+
|
|
183
|
+
if node_path.is_empty():
|
|
184
|
+
return _error("INVALID_PARAMS", "node_path is required")
|
|
185
|
+
if properties.is_empty():
|
|
186
|
+
return _error("INVALID_PARAMS", "properties is required")
|
|
187
|
+
|
|
188
|
+
var node := _get_node(node_path)
|
|
189
|
+
if not node:
|
|
190
|
+
return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
|
|
191
|
+
|
|
192
|
+
for key in properties:
|
|
193
|
+
if key in node:
|
|
194
|
+
var deserialized := MCPUtils.deserialize_value(properties[key])
|
|
195
|
+
node.set(key, deserialized)
|
|
196
|
+
|
|
197
|
+
return _success({})
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
func delete_node(params: Dictionary) -> Dictionary:
|
|
201
|
+
var scene_check := _require_scene_open()
|
|
202
|
+
if not scene_check.is_empty():
|
|
203
|
+
return scene_check
|
|
204
|
+
|
|
205
|
+
var node_path: String = params.get("node_path", "")
|
|
206
|
+
if node_path.is_empty():
|
|
207
|
+
return _error("INVALID_PARAMS", "node_path is required")
|
|
208
|
+
|
|
209
|
+
var node := _get_node(node_path)
|
|
210
|
+
if not node:
|
|
211
|
+
return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
|
|
212
|
+
|
|
213
|
+
var root := EditorInterface.get_edited_scene_root()
|
|
214
|
+
if node == root:
|
|
215
|
+
return _error("CANNOT_DELETE_ROOT", "Cannot delete the root node")
|
|
216
|
+
|
|
217
|
+
node.get_parent().remove_child(node)
|
|
218
|
+
node.queue_free()
|
|
219
|
+
|
|
220
|
+
return _success({})
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
func reparent_node(params: Dictionary) -> Dictionary:
|
|
224
|
+
var scene_check := _require_scene_open()
|
|
225
|
+
if not scene_check.is_empty():
|
|
226
|
+
return scene_check
|
|
227
|
+
|
|
228
|
+
var node_path: String = params.get("node_path", "")
|
|
229
|
+
var new_parent_path: String = params.get("new_parent_path", "")
|
|
230
|
+
|
|
231
|
+
if node_path.is_empty():
|
|
232
|
+
return _error("INVALID_PARAMS", "node_path is required")
|
|
233
|
+
if new_parent_path.is_empty():
|
|
234
|
+
return _error("INVALID_PARAMS", "new_parent_path is required")
|
|
235
|
+
|
|
236
|
+
var node := _get_node(node_path)
|
|
237
|
+
if not node:
|
|
238
|
+
return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
|
|
239
|
+
|
|
240
|
+
var new_parent := _get_node(new_parent_path)
|
|
241
|
+
if not new_parent:
|
|
242
|
+
return _error("NODE_NOT_FOUND", "New parent not found: %s" % new_parent_path)
|
|
243
|
+
|
|
244
|
+
var root := EditorInterface.get_edited_scene_root()
|
|
245
|
+
if node == root:
|
|
246
|
+
return _error("CANNOT_REPARENT_ROOT", "Cannot reparent the root node")
|
|
247
|
+
|
|
248
|
+
node.reparent(new_parent)
|
|
249
|
+
|
|
250
|
+
return _success({"new_path": str(node.get_path())})
|
|
251
|
+
|
|
252
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://68p58x7fq2ve
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends MCPBaseCommand
|
|
3
|
+
class_name MCPProjectCommands
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
func get_commands() -> Dictionary:
|
|
7
|
+
return {
|
|
8
|
+
"get_project_info": get_project_info,
|
|
9
|
+
"get_project_settings": get_project_settings
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
func get_project_info(_params: Dictionary) -> Dictionary:
|
|
14
|
+
return _success({
|
|
15
|
+
"name": ProjectSettings.get_setting("application/config/name", "Unknown"),
|
|
16
|
+
"path": ProjectSettings.globalize_path("res://"),
|
|
17
|
+
"godot_version": Engine.get_version_info()["string"],
|
|
18
|
+
"main_scene": ProjectSettings.get_setting("application/run/main_scene", null)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
func get_project_settings(params: Dictionary) -> Dictionary:
|
|
23
|
+
var category: String = params.get("category", "")
|
|
24
|
+
|
|
25
|
+
if category == "input":
|
|
26
|
+
return _get_input_mappings(params)
|
|
27
|
+
|
|
28
|
+
var settings := {}
|
|
29
|
+
var all_settings := ProjectSettings.get_property_list()
|
|
30
|
+
|
|
31
|
+
for prop in all_settings:
|
|
32
|
+
var name: String = prop["name"]
|
|
33
|
+
if not category.is_empty() and not name.begins_with(category):
|
|
34
|
+
continue
|
|
35
|
+
if prop["usage"] & PROPERTY_USAGE_EDITOR:
|
|
36
|
+
settings[name] = _serialize_value(ProjectSettings.get_setting(name))
|
|
37
|
+
|
|
38
|
+
return _success({"settings": settings})
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
func _get_input_mappings(params: Dictionary) -> Dictionary:
|
|
42
|
+
var include_builtin: bool = params.get("include_builtin", false)
|
|
43
|
+
var actions := {}
|
|
44
|
+
|
|
45
|
+
# Read from ProjectSettings instead of InputMap
|
|
46
|
+
# InputMap in editor context only has editor actions, not game inputs
|
|
47
|
+
# Game inputs are stored as "input/<action_name>" in ProjectSettings
|
|
48
|
+
var all_settings := ProjectSettings.get_property_list()
|
|
49
|
+
|
|
50
|
+
for prop in all_settings:
|
|
51
|
+
var name: String = prop["name"]
|
|
52
|
+
if not name.begins_with("input/"):
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
var action_name := name.substr(6) # Remove "input/" prefix
|
|
56
|
+
|
|
57
|
+
if not include_builtin and action_name.begins_with("ui_"):
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
var action_data = ProjectSettings.get_setting(name)
|
|
61
|
+
if action_data is Dictionary:
|
|
62
|
+
var events := []
|
|
63
|
+
var raw_events = action_data.get("events", [])
|
|
64
|
+
for event in raw_events:
|
|
65
|
+
if event is InputEvent:
|
|
66
|
+
events.append(_serialize_input_event(event))
|
|
67
|
+
|
|
68
|
+
actions[action_name] = {
|
|
69
|
+
"deadzone": action_data.get("deadzone", 0.5),
|
|
70
|
+
"events": events
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return _success({"settings": actions})
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
func _serialize_input_event(event: InputEvent) -> Dictionary:
|
|
77
|
+
if event is InputEventKey:
|
|
78
|
+
var keycode: int = event.keycode if event.keycode else event.physical_keycode
|
|
79
|
+
return {
|
|
80
|
+
"type": "key",
|
|
81
|
+
"keycode": event.keycode,
|
|
82
|
+
"physical_keycode": event.physical_keycode,
|
|
83
|
+
"key_label": OS.get_keycode_string(keycode),
|
|
84
|
+
"modifiers": _get_modifiers(event)
|
|
85
|
+
}
|
|
86
|
+
elif event is InputEventMouseButton:
|
|
87
|
+
return {
|
|
88
|
+
"type": "mouse_button",
|
|
89
|
+
"button_index": event.button_index,
|
|
90
|
+
"modifiers": _get_modifiers(event)
|
|
91
|
+
}
|
|
92
|
+
elif event is InputEventJoypadButton:
|
|
93
|
+
return {
|
|
94
|
+
"type": "joypad_button",
|
|
95
|
+
"button_index": event.button_index,
|
|
96
|
+
"device": event.device
|
|
97
|
+
}
|
|
98
|
+
elif event is InputEventJoypadMotion:
|
|
99
|
+
return {
|
|
100
|
+
"type": "joypad_motion",
|
|
101
|
+
"axis": event.axis,
|
|
102
|
+
"axis_value": event.axis_value,
|
|
103
|
+
"device": event.device
|
|
104
|
+
}
|
|
105
|
+
return {"type": "unknown", "event": str(event)}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
func _get_modifiers(event: InputEventWithModifiers) -> Dictionary:
|
|
109
|
+
return {
|
|
110
|
+
"shift": event.shift_pressed,
|
|
111
|
+
"ctrl": event.ctrl_pressed,
|
|
112
|
+
"alt": event.alt_pressed,
|
|
113
|
+
"meta": event.meta_pressed
|
|
114
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://cyfa3l85qu83y
|