@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,130 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends MCPBaseCommand
|
|
3
|
+
class_name MCPScreenshotCommands
|
|
4
|
+
|
|
5
|
+
const DEFAULT_MAX_WIDTH := 1920
|
|
6
|
+
const SCREENSHOT_TIMEOUT := 5.0
|
|
7
|
+
|
|
8
|
+
var _screenshot_result: Dictionary = {}
|
|
9
|
+
var _screenshot_pending: bool = false
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
func get_commands() -> Dictionary:
|
|
13
|
+
return {
|
|
14
|
+
"capture_game_screenshot": capture_game_screenshot,
|
|
15
|
+
"capture_editor_screenshot": capture_editor_screenshot
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
func capture_game_screenshot(params: Dictionary) -> Dictionary:
|
|
20
|
+
if not EditorInterface.is_playing_scene():
|
|
21
|
+
return _error("NOT_RUNNING", "No game is currently running. Use run_project first.")
|
|
22
|
+
|
|
23
|
+
var max_width: int = params.get("max_width", DEFAULT_MAX_WIDTH)
|
|
24
|
+
|
|
25
|
+
var debugger_plugin = _plugin.get_debugger_plugin() if _plugin else null
|
|
26
|
+
if debugger_plugin == null:
|
|
27
|
+
return _error("NO_DEBUGGER", "Debugger plugin not available")
|
|
28
|
+
|
|
29
|
+
if not debugger_plugin.has_active_session():
|
|
30
|
+
return _error("NO_SESSION", "No active debug session. Game may not have MCPGameBridge autoload.")
|
|
31
|
+
|
|
32
|
+
_screenshot_pending = true
|
|
33
|
+
_screenshot_result = {}
|
|
34
|
+
|
|
35
|
+
debugger_plugin.screenshot_received.connect(_on_screenshot_received, CONNECT_ONE_SHOT)
|
|
36
|
+
debugger_plugin.request_screenshot(max_width)
|
|
37
|
+
|
|
38
|
+
var start_time := Time.get_ticks_msec()
|
|
39
|
+
while _screenshot_pending:
|
|
40
|
+
await Engine.get_main_loop().process_frame
|
|
41
|
+
if (Time.get_ticks_msec() - start_time) / 1000.0 > SCREENSHOT_TIMEOUT:
|
|
42
|
+
_screenshot_pending = false
|
|
43
|
+
if debugger_plugin.screenshot_received.is_connected(_on_screenshot_received):
|
|
44
|
+
debugger_plugin.screenshot_received.disconnect(_on_screenshot_received)
|
|
45
|
+
return _error("TIMEOUT", "Screenshot request timed out")
|
|
46
|
+
|
|
47
|
+
return _screenshot_result
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
func _on_screenshot_received(success: bool, image_base64: String, width: int, height: int, error: String) -> void:
|
|
51
|
+
_screenshot_pending = false
|
|
52
|
+
if success:
|
|
53
|
+
_screenshot_result = _success({
|
|
54
|
+
"image_base64": image_base64,
|
|
55
|
+
"width": width,
|
|
56
|
+
"height": height
|
|
57
|
+
})
|
|
58
|
+
else:
|
|
59
|
+
_screenshot_result = _error("CAPTURE_FAILED", error)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
func capture_editor_screenshot(params: Dictionary) -> Dictionary:
|
|
63
|
+
var viewport_type: String = params.get("viewport", "")
|
|
64
|
+
var max_width: int = params.get("max_width", DEFAULT_MAX_WIDTH)
|
|
65
|
+
|
|
66
|
+
var viewport: SubViewport = null
|
|
67
|
+
|
|
68
|
+
if viewport_type == "2d":
|
|
69
|
+
viewport = _find_2d_viewport()
|
|
70
|
+
elif viewport_type == "3d":
|
|
71
|
+
viewport = _find_3d_viewport()
|
|
72
|
+
else:
|
|
73
|
+
viewport = _find_active_viewport()
|
|
74
|
+
|
|
75
|
+
if viewport == null:
|
|
76
|
+
return _error("NO_VIEWPORT", "Could not find editor viewport")
|
|
77
|
+
|
|
78
|
+
var image := viewport.get_texture().get_image()
|
|
79
|
+
return _process_and_encode_image(image, max_width)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
func _process_and_encode_image(image: Image, max_width: int) -> Dictionary:
|
|
83
|
+
if image == null:
|
|
84
|
+
return _error("CAPTURE_FAILED", "Failed to capture image from viewport")
|
|
85
|
+
|
|
86
|
+
if max_width > 0 and image.get_width() > max_width:
|
|
87
|
+
var scale_factor := float(max_width) / float(image.get_width())
|
|
88
|
+
var new_height := int(image.get_height() * scale_factor)
|
|
89
|
+
image.resize(max_width, new_height, Image.INTERPOLATE_LANCZOS)
|
|
90
|
+
|
|
91
|
+
var png_buffer := image.save_png_to_buffer()
|
|
92
|
+
var base64 := Marshalls.raw_to_base64(png_buffer)
|
|
93
|
+
|
|
94
|
+
return _success({
|
|
95
|
+
"image_base64": base64,
|
|
96
|
+
"width": image.get_width(),
|
|
97
|
+
"height": image.get_height()
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
func _find_active_viewport() -> SubViewport:
|
|
102
|
+
var viewport := _find_3d_viewport()
|
|
103
|
+
if viewport:
|
|
104
|
+
return viewport
|
|
105
|
+
return _find_2d_viewport()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
func _find_2d_viewport() -> SubViewport:
|
|
109
|
+
var editor_main := EditorInterface.get_editor_main_screen()
|
|
110
|
+
return _find_viewport_in_tree(editor_main, "2D")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
func _find_3d_viewport() -> SubViewport:
|
|
114
|
+
var editor_main := EditorInterface.get_editor_main_screen()
|
|
115
|
+
return _find_viewport_in_tree(editor_main, "3D")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
func _find_viewport_in_tree(node: Node, hint: String) -> SubViewport:
|
|
119
|
+
if node is SubViewportContainer:
|
|
120
|
+
var container := node as SubViewportContainer
|
|
121
|
+
for child in container.get_children():
|
|
122
|
+
if child is SubViewport:
|
|
123
|
+
return child as SubViewport
|
|
124
|
+
|
|
125
|
+
for child in node.get_children():
|
|
126
|
+
var result := _find_viewport_in_tree(child, hint)
|
|
127
|
+
if result:
|
|
128
|
+
return result
|
|
129
|
+
|
|
130
|
+
return null
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://cv5f2i08buyb1
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends MCPBaseCommand
|
|
3
|
+
class_name MCPScriptCommands
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
func get_commands() -> Dictionary:
|
|
7
|
+
return {
|
|
8
|
+
"read_script": read_script,
|
|
9
|
+
"get_current_script": get_current_script,
|
|
10
|
+
"create_script": create_script,
|
|
11
|
+
"edit_script": edit_script,
|
|
12
|
+
"attach_script": attach_script,
|
|
13
|
+
"detach_script": detach_script
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
func read_script(params: Dictionary) -> Dictionary:
|
|
18
|
+
var script_path: String = params.get("script_path", "")
|
|
19
|
+
if script_path.is_empty():
|
|
20
|
+
return _error("INVALID_PARAMS", "script_path is required")
|
|
21
|
+
|
|
22
|
+
if not FileAccess.file_exists(script_path):
|
|
23
|
+
return _error("FILE_NOT_FOUND", "Script file not found: %s" % script_path)
|
|
24
|
+
|
|
25
|
+
var file := FileAccess.open(script_path, FileAccess.READ)
|
|
26
|
+
if not file:
|
|
27
|
+
return _error("READ_FAILED", "Failed to read script: %s" % script_path)
|
|
28
|
+
|
|
29
|
+
var content := file.get_as_text()
|
|
30
|
+
file.close()
|
|
31
|
+
|
|
32
|
+
return _success({"content": content})
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
func get_current_script(_params: Dictionary) -> Dictionary:
|
|
36
|
+
var script_editor := EditorInterface.get_script_editor()
|
|
37
|
+
if not script_editor:
|
|
38
|
+
return _success({"path": null, "content": null})
|
|
39
|
+
|
|
40
|
+
var current_script := script_editor.get_current_script()
|
|
41
|
+
if not current_script:
|
|
42
|
+
return _success({"path": null, "content": null})
|
|
43
|
+
|
|
44
|
+
return _success({
|
|
45
|
+
"path": current_script.resource_path,
|
|
46
|
+
"content": current_script.source_code
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
func create_script(params: Dictionary) -> Dictionary:
|
|
51
|
+
var script_path: String = params.get("script_path", "")
|
|
52
|
+
var content: String = params.get("content", "")
|
|
53
|
+
var attach_to: String = params.get("attach_to", "")
|
|
54
|
+
|
|
55
|
+
if script_path.is_empty():
|
|
56
|
+
return _error("INVALID_PARAMS", "script_path is required")
|
|
57
|
+
if content.is_empty():
|
|
58
|
+
return _error("INVALID_PARAMS", "content is required")
|
|
59
|
+
|
|
60
|
+
var dir_path := script_path.get_base_dir()
|
|
61
|
+
if not MCPUtils.dir_exists(dir_path):
|
|
62
|
+
var err := MCPUtils.ensure_dir_exists(dir_path)
|
|
63
|
+
if err != OK:
|
|
64
|
+
return _error("DIR_CREATE_FAILED", "Failed to create directory: %s" % dir_path)
|
|
65
|
+
|
|
66
|
+
var file := FileAccess.open(script_path, FileAccess.WRITE)
|
|
67
|
+
if not file:
|
|
68
|
+
return _error("WRITE_FAILED", "Failed to create script: %s" % script_path)
|
|
69
|
+
|
|
70
|
+
file.store_string(content)
|
|
71
|
+
file.close()
|
|
72
|
+
|
|
73
|
+
EditorInterface.get_resource_filesystem().scan()
|
|
74
|
+
|
|
75
|
+
if not attach_to.is_empty():
|
|
76
|
+
var attach_result := attach_script({"node_path": attach_to, "script_path": script_path})
|
|
77
|
+
if attach_result["status"] == "error":
|
|
78
|
+
return attach_result
|
|
79
|
+
|
|
80
|
+
return _success({"path": script_path})
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
func edit_script(params: Dictionary) -> Dictionary:
|
|
84
|
+
var script_path: String = params.get("script_path", "")
|
|
85
|
+
var content: String = params.get("content", "")
|
|
86
|
+
|
|
87
|
+
if script_path.is_empty():
|
|
88
|
+
return _error("INVALID_PARAMS", "script_path is required")
|
|
89
|
+
if content.is_empty():
|
|
90
|
+
return _error("INVALID_PARAMS", "content is required")
|
|
91
|
+
|
|
92
|
+
if not FileAccess.file_exists(script_path):
|
|
93
|
+
return _error("FILE_NOT_FOUND", "Script file not found: %s" % script_path)
|
|
94
|
+
|
|
95
|
+
var file := FileAccess.open(script_path, FileAccess.WRITE)
|
|
96
|
+
if not file:
|
|
97
|
+
return _error("WRITE_FAILED", "Failed to write script: %s" % script_path)
|
|
98
|
+
|
|
99
|
+
file.store_string(content)
|
|
100
|
+
file.close()
|
|
101
|
+
|
|
102
|
+
EditorInterface.get_resource_filesystem().scan()
|
|
103
|
+
|
|
104
|
+
var script := load(script_path) as Script
|
|
105
|
+
if script:
|
|
106
|
+
script.reload()
|
|
107
|
+
|
|
108
|
+
return _success({"path": script_path})
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
func attach_script(params: Dictionary) -> Dictionary:
|
|
112
|
+
var node_path: String = params.get("node_path", "")
|
|
113
|
+
var script_path: String = params.get("script_path", "")
|
|
114
|
+
|
|
115
|
+
if node_path.is_empty():
|
|
116
|
+
return _error("INVALID_PARAMS", "node_path is required")
|
|
117
|
+
if script_path.is_empty():
|
|
118
|
+
return _error("INVALID_PARAMS", "script_path is required")
|
|
119
|
+
|
|
120
|
+
var node := _get_node(node_path)
|
|
121
|
+
if not node:
|
|
122
|
+
return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
|
|
123
|
+
|
|
124
|
+
if not FileAccess.file_exists(script_path):
|
|
125
|
+
return _error("FILE_NOT_FOUND", "Script file not found: %s" % script_path)
|
|
126
|
+
|
|
127
|
+
var script := load(script_path) as Script
|
|
128
|
+
if not script:
|
|
129
|
+
return _error("LOAD_FAILED", "Failed to load script: %s" % script_path)
|
|
130
|
+
|
|
131
|
+
node.set_script(script)
|
|
132
|
+
|
|
133
|
+
EditorInterface.get_resource_filesystem().scan()
|
|
134
|
+
script.reload()
|
|
135
|
+
|
|
136
|
+
if node.get_script() != script:
|
|
137
|
+
return _error("ATTACH_FAILED", "Script attachment did not persist")
|
|
138
|
+
|
|
139
|
+
return _success({"node_path": str(node.get_path()), "script_path": script_path})
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
func detach_script(params: Dictionary) -> Dictionary:
|
|
143
|
+
var node_path: String = params.get("node_path", "")
|
|
144
|
+
|
|
145
|
+
if node_path.is_empty():
|
|
146
|
+
return _error("INVALID_PARAMS", "node_path is required")
|
|
147
|
+
|
|
148
|
+
var node := _get_node(node_path)
|
|
149
|
+
if not node:
|
|
150
|
+
return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
|
|
151
|
+
|
|
152
|
+
node.set_script(null)
|
|
153
|
+
|
|
154
|
+
return _success({})
|
|
155
|
+
|
|
156
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://6t4dbbx8421b
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends MCPBaseCommand
|
|
3
|
+
class_name MCPSelectionCommands
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
func get_commands() -> Dictionary:
|
|
7
|
+
return {
|
|
8
|
+
"get_editor_state": get_editor_state,
|
|
9
|
+
"get_selected_nodes": get_selected_nodes,
|
|
10
|
+
"select_node": select_node,
|
|
11
|
+
"set_2d_viewport": set_2d_viewport
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
func get_editor_state(_params: Dictionary) -> Dictionary:
|
|
16
|
+
var root := EditorInterface.get_edited_scene_root()
|
|
17
|
+
var open_scenes := EditorInterface.get_open_scenes()
|
|
18
|
+
|
|
19
|
+
var main_screen := _get_current_main_screen()
|
|
20
|
+
|
|
21
|
+
var result := {
|
|
22
|
+
"current_scene": root.scene_file_path if root else null,
|
|
23
|
+
"is_playing": EditorInterface.is_playing_scene(),
|
|
24
|
+
"godot_version": Engine.get_version_info()["string"],
|
|
25
|
+
"open_scenes": Array(open_scenes),
|
|
26
|
+
"main_screen": main_screen
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if main_screen == "3D":
|
|
30
|
+
var camera_info := _get_editor_camera_info()
|
|
31
|
+
if not camera_info.is_empty():
|
|
32
|
+
result["camera"] = camera_info
|
|
33
|
+
elif main_screen == "2D":
|
|
34
|
+
var viewport_2d_info := _get_editor_2d_viewport_info()
|
|
35
|
+
if not viewport_2d_info.is_empty():
|
|
36
|
+
result["viewport_2d"] = viewport_2d_info
|
|
37
|
+
|
|
38
|
+
return _success(result)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
func _get_editor_camera_info() -> Dictionary:
|
|
42
|
+
var viewport := EditorInterface.get_editor_viewport_3d(0)
|
|
43
|
+
if not viewport:
|
|
44
|
+
return {}
|
|
45
|
+
|
|
46
|
+
var camera := viewport.get_camera_3d()
|
|
47
|
+
if not camera:
|
|
48
|
+
return {}
|
|
49
|
+
|
|
50
|
+
var pos: Vector3 = camera.global_position
|
|
51
|
+
var rot: Vector3 = camera.global_rotation
|
|
52
|
+
var forward: Vector3 = -camera.global_transform.basis.z
|
|
53
|
+
|
|
54
|
+
var info := {
|
|
55
|
+
"position": {"x": pos.x, "y": pos.y, "z": pos.z},
|
|
56
|
+
"rotation": {"x": rot.x, "y": rot.y, "z": rot.z},
|
|
57
|
+
"forward": {"x": forward.x, "y": forward.y, "z": forward.z},
|
|
58
|
+
"fov": camera.fov,
|
|
59
|
+
"near": camera.near,
|
|
60
|
+
"far": camera.far,
|
|
61
|
+
"projection": "orthogonal" if camera.projection == Camera3D.PROJECTION_ORTHOGONAL else "perspective",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if camera.projection == Camera3D.PROJECTION_ORTHOGONAL:
|
|
65
|
+
info["size"] = camera.size
|
|
66
|
+
|
|
67
|
+
return info
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
func _get_editor_2d_viewport_info() -> Dictionary:
|
|
71
|
+
var viewport := EditorInterface.get_editor_viewport_2d()
|
|
72
|
+
if not viewport:
|
|
73
|
+
return {}
|
|
74
|
+
|
|
75
|
+
var transform := viewport.global_canvas_transform
|
|
76
|
+
var zoom: float = transform.x.x
|
|
77
|
+
var offset: Vector2 = -transform.origin / zoom
|
|
78
|
+
|
|
79
|
+
var size := viewport.size
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
"center": {"x": offset.x + size.x / zoom / 2, "y": offset.y + size.y / zoom / 2},
|
|
83
|
+
"zoom": zoom,
|
|
84
|
+
"size": {"width": int(size.x), "height": int(size.y)}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
func set_2d_viewport(params: Dictionary) -> Dictionary:
|
|
89
|
+
var viewport := EditorInterface.get_editor_viewport_2d()
|
|
90
|
+
if not viewport:
|
|
91
|
+
return _error("NO_VIEWPORT", "Could not access 2D editor viewport")
|
|
92
|
+
|
|
93
|
+
var center_x: float = params.get("center_x", 0.0)
|
|
94
|
+
var center_y: float = params.get("center_y", 0.0)
|
|
95
|
+
var zoom: float = params.get("zoom", 1.0)
|
|
96
|
+
|
|
97
|
+
if zoom <= 0:
|
|
98
|
+
return _error("INVALID_PARAMS", "zoom must be positive")
|
|
99
|
+
|
|
100
|
+
var size := viewport.size
|
|
101
|
+
var offset := Vector2(center_x - size.x / zoom / 2, center_y - size.y / zoom / 2)
|
|
102
|
+
var origin := -offset * zoom
|
|
103
|
+
|
|
104
|
+
var transform := Transform2D(Vector2(zoom, 0), Vector2(0, zoom), origin)
|
|
105
|
+
viewport.global_canvas_transform = transform
|
|
106
|
+
|
|
107
|
+
return _success({
|
|
108
|
+
"center": {"x": center_x, "y": center_y},
|
|
109
|
+
"zoom": zoom
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
const MAIN_SCREEN_PATTERNS := {
|
|
114
|
+
"2D": ["CanvasItemEditor", "2D"],
|
|
115
|
+
"3D": ["Node3DEditor", "3D"],
|
|
116
|
+
"Script": ["ScriptEditor", "Script"],
|
|
117
|
+
"AssetLib": ["AssetLib", "Asset"],
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
func _get_current_main_screen() -> String:
|
|
122
|
+
var main_screen := EditorInterface.get_editor_main_screen()
|
|
123
|
+
if not main_screen:
|
|
124
|
+
return "unknown"
|
|
125
|
+
|
|
126
|
+
for child in main_screen.get_children():
|
|
127
|
+
if child.visible and child is Control:
|
|
128
|
+
var cls := child.get_class()
|
|
129
|
+
var node_name := child.name
|
|
130
|
+
|
|
131
|
+
for screen_name in MAIN_SCREEN_PATTERNS:
|
|
132
|
+
var patterns: Array = MAIN_SCREEN_PATTERNS[screen_name]
|
|
133
|
+
if patterns[0] in cls or patterns[1] in node_name:
|
|
134
|
+
return screen_name
|
|
135
|
+
|
|
136
|
+
return "unknown"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
func get_selected_nodes(_params: Dictionary) -> Dictionary:
|
|
140
|
+
var selection := EditorInterface.get_selection()
|
|
141
|
+
var root := EditorInterface.get_edited_scene_root()
|
|
142
|
+
var selected: Array[String] = []
|
|
143
|
+
|
|
144
|
+
for node in selection.get_selected_nodes():
|
|
145
|
+
if root and root.is_ancestor_of(node):
|
|
146
|
+
# Build clean path relative to scene root
|
|
147
|
+
var relative_path := root.get_path_to(node)
|
|
148
|
+
var usable_path := "/root/" + root.name
|
|
149
|
+
if relative_path != NodePath("."):
|
|
150
|
+
usable_path += "/" + str(relative_path)
|
|
151
|
+
selected.append(usable_path)
|
|
152
|
+
elif node == root:
|
|
153
|
+
selected.append("/root/" + root.name)
|
|
154
|
+
|
|
155
|
+
return _success({"selected": selected})
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
func select_node(params: Dictionary) -> Dictionary:
|
|
159
|
+
var node_path: String = params.get("node_path", "")
|
|
160
|
+
if node_path.is_empty():
|
|
161
|
+
return _error("INVALID_PARAMS", "node_path is required")
|
|
162
|
+
|
|
163
|
+
var node := _get_node(node_path)
|
|
164
|
+
if not node:
|
|
165
|
+
return _error("NODE_NOT_FOUND", "Node not found: %s" % node_path)
|
|
166
|
+
|
|
167
|
+
var selection := EditorInterface.get_selection()
|
|
168
|
+
selection.clear()
|
|
169
|
+
selection.add_node(node)
|
|
170
|
+
return _success({})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://20vnciu1es3c
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends MCPBaseCommand
|
|
3
|
+
class_name MCPSystemCommands
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
func get_commands() -> Dictionary:
|
|
7
|
+
return {
|
|
8
|
+
"mcp_handshake": mcp_handshake,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
func mcp_handshake(params: Dictionary) -> Dictionary:
|
|
13
|
+
var server_version: String = params.get("server_version", "unknown")
|
|
14
|
+
|
|
15
|
+
return _success({
|
|
16
|
+
"addon_version": _get_addon_version(),
|
|
17
|
+
"godot_version": Engine.get_version_info()["string"],
|
|
18
|
+
"project_path": ProjectSettings.globalize_path("res://"),
|
|
19
|
+
"project_name": ProjectSettings.get_setting("application/config/name", ""),
|
|
20
|
+
"server_version_received": server_version
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
func _get_addon_version() -> String:
|
|
25
|
+
var config := ConfigFile.new()
|
|
26
|
+
var err := config.load("res://addons/godot_mcp/plugin.cfg")
|
|
27
|
+
if err == OK:
|
|
28
|
+
return config.get_value("plugin", "version", "unknown")
|
|
29
|
+
return "unknown"
|