gopeak 2.0.1 → 2.2.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.
@@ -0,0 +1,161 @@
1
+ @tool
2
+ extends Node
3
+ class_name MCPEditorClient
4
+
5
+ signal connected
6
+ signal disconnected
7
+ signal tool_requested(request_id: String, tool_name: String, args: Dictionary)
8
+
9
+ const DEFAULT_URL := "ws://127.0.0.1:6505/godot"
10
+ const RECONNECT_DELAY := 3.0
11
+ const MAX_RECONNECT_DELAY := 30.0
12
+
13
+ var socket: WebSocketPeer = WebSocketPeer.new()
14
+ var server_url: String = DEFAULT_URL
15
+ var _is_connected := false
16
+ var _reconnect_timer: Timer
17
+ var _current_reconnect_delay := RECONNECT_DELAY
18
+ var _should_reconnect := true
19
+ var _project_path: String
20
+ var _initialized := false
21
+
22
+
23
+ func _ready() -> void:
24
+ _project_path = ProjectSettings.globalize_path("res://")
25
+
26
+ _reconnect_timer = Timer.new()
27
+ _reconnect_timer.one_shot = true
28
+ _reconnect_timer.timeout.connect(_on_reconnect_timer)
29
+ add_child(_reconnect_timer)
30
+
31
+ set_process(true)
32
+ _initialized = true
33
+
34
+
35
+ func _process(_delta: float) -> void:
36
+ if not _initialized:
37
+ return
38
+
39
+ if socket.get_ready_state() == WebSocketPeer.STATE_CLOSED:
40
+ if _is_connected:
41
+ _handle_disconnect()
42
+ return
43
+
44
+ socket.poll()
45
+
46
+ match socket.get_ready_state():
47
+ WebSocketPeer.STATE_OPEN:
48
+ if not _is_connected:
49
+ _handle_connect()
50
+
51
+ while socket.get_available_packet_count() > 0:
52
+ var packet := socket.get_packet()
53
+ _handle_message(packet.get_string_from_utf8())
54
+
55
+ WebSocketPeer.STATE_CLOSING:
56
+ pass
57
+
58
+ WebSocketPeer.STATE_CLOSED:
59
+ if _is_connected:
60
+ _handle_disconnect()
61
+
62
+
63
+ func connect_to_server(url: String = DEFAULT_URL) -> void:
64
+ server_url = url
65
+ _should_reconnect = true
66
+ _current_reconnect_delay = RECONNECT_DELAY
67
+ _attempt_connection()
68
+
69
+
70
+ func disconnect_from_server() -> void:
71
+ _should_reconnect = false
72
+ if _reconnect_timer:
73
+ _reconnect_timer.stop()
74
+ if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
75
+ socket.close()
76
+ _is_connected = false
77
+
78
+
79
+ func _attempt_connection() -> void:
80
+ if socket.get_ready_state() != WebSocketPeer.STATE_CLOSED:
81
+ socket.close()
82
+
83
+ var err := socket.connect_to_url(server_url)
84
+ if err != OK:
85
+ push_error("[MCP Editor] Failed to connect: %s" % err)
86
+ _schedule_reconnect()
87
+
88
+
89
+ func _handle_connect() -> void:
90
+ _is_connected = true
91
+ _current_reconnect_delay = RECONNECT_DELAY
92
+
93
+ _send_message({
94
+ "type": "godot_ready",
95
+ "project_path": _project_path
96
+ })
97
+
98
+ connected.emit()
99
+
100
+
101
+ func _handle_disconnect() -> void:
102
+ _is_connected = false
103
+ disconnected.emit()
104
+
105
+ if _should_reconnect:
106
+ _schedule_reconnect()
107
+
108
+
109
+ func _schedule_reconnect() -> void:
110
+ if _reconnect_timer == null:
111
+ return
112
+ _reconnect_timer.start(_current_reconnect_delay)
113
+ _current_reconnect_delay = min(_current_reconnect_delay * 2.0, MAX_RECONNECT_DELAY)
114
+
115
+
116
+ func _on_reconnect_timer() -> void:
117
+ _attempt_connection()
118
+
119
+
120
+ func _handle_message(json_string: String) -> void:
121
+ var message = JSON.parse_string(json_string)
122
+ if message == null:
123
+ push_error("[MCP Editor] Failed to parse message: %s" % json_string)
124
+ return
125
+
126
+ match message.get("type", ""):
127
+ "ping":
128
+ _send_message({"type": "pong"})
129
+
130
+ "tool_invoke":
131
+ var request_id: String = message.get("id", "")
132
+ var tool_name: String = message.get("tool", "")
133
+ var args: Dictionary = message.get("args", {})
134
+ tool_requested.emit(request_id, tool_name, args)
135
+
136
+ _:
137
+ pass
138
+
139
+
140
+ func send_tool_result(request_id: String, success: bool, result = null, error: String = "") -> void:
141
+ var response := {
142
+ "type": "tool_result",
143
+ "id": request_id,
144
+ "success": success
145
+ }
146
+
147
+ if success:
148
+ response["result"] = result
149
+ else:
150
+ response["error"] = error
151
+
152
+ _send_message(response)
153
+
154
+
155
+ func _send_message(message: Dictionary) -> void:
156
+ if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
157
+ socket.send_text(JSON.stringify(message))
158
+
159
+
160
+ func is_connected_to_server() -> bool:
161
+ return _is_connected
@@ -0,0 +1,6 @@
1
+ [plugin]
2
+ name="Godot MCP Editor"
3
+ description="MCP Editor Plugin - Scene manipulation via Godot API"
4
+ author="HaD0Yun"
5
+ version="1.0.0"
6
+ script="plugin.gd"
@@ -0,0 +1,84 @@
1
+ @tool
2
+ extends EditorPlugin
3
+
4
+ const MCPEditorClientScript = preload("mcp_client.gd")
5
+ const MCPToolExecutorScript = preload("tool_executor.gd")
6
+
7
+ var _mcp_client: Node
8
+ var _tool_executor: Node
9
+ var _status_label: Label
10
+
11
+
12
+ func _enter_tree() -> void:
13
+ _mcp_client = MCPEditorClientScript.new()
14
+ _mcp_client.name = "MCPEditorClient"
15
+ add_child(_mcp_client)
16
+
17
+ _tool_executor = MCPToolExecutorScript.new()
18
+ _tool_executor.name = "MCPToolExecutor"
19
+ add_child(_tool_executor)
20
+ _tool_executor.set_editor_plugin(self)
21
+
22
+ _mcp_client.connected.connect(_on_connected)
23
+ _mcp_client.disconnected.connect(_on_disconnected)
24
+ _mcp_client.tool_requested.connect(_on_tool_requested)
25
+
26
+ _setup_status_indicator()
27
+ _mcp_client.connect_to_server()
28
+
29
+
30
+ func _exit_tree() -> void:
31
+ if _mcp_client:
32
+ if _mcp_client.connected.is_connected(_on_connected):
33
+ _mcp_client.connected.disconnect(_on_connected)
34
+ if _mcp_client.disconnected.is_connected(_on_disconnected):
35
+ _mcp_client.disconnected.disconnect(_on_disconnected)
36
+ if _mcp_client.tool_requested.is_connected(_on_tool_requested):
37
+ _mcp_client.tool_requested.disconnect(_on_tool_requested)
38
+ _mcp_client.disconnect_from_server()
39
+ _mcp_client.queue_free()
40
+ _mcp_client = null
41
+
42
+ if _tool_executor:
43
+ _tool_executor.queue_free()
44
+ _tool_executor = null
45
+
46
+ if _status_label:
47
+ remove_control_from_container(CONTAINER_TOOLBAR, _status_label)
48
+ _status_label.queue_free()
49
+ _status_label = null
50
+
51
+
52
+ func _setup_status_indicator() -> void:
53
+ _status_label = Label.new()
54
+ _status_label.text = "MCP: Connecting..."
55
+ _status_label.add_theme_color_override("font_color", Color.YELLOW)
56
+ _status_label.add_theme_font_size_override("font_size", 12)
57
+ add_control_to_container(CONTAINER_TOOLBAR, _status_label)
58
+
59
+
60
+ func _on_connected() -> void:
61
+ if _status_label:
62
+ _status_label.text = "MCP: Connected"
63
+ _status_label.add_theme_color_override("font_color", Color.GREEN)
64
+
65
+
66
+ func _on_disconnected() -> void:
67
+ if _status_label:
68
+ _status_label.text = "MCP: Disconnected"
69
+ _status_label.add_theme_color_override("font_color", Color.RED)
70
+
71
+
72
+ func _on_tool_requested(request_id: String, tool_name: String, args: Dictionary) -> void:
73
+ if _tool_executor == null or _mcp_client == null:
74
+ return
75
+
76
+ var result: Dictionary = _tool_executor.execute_tool(tool_name, args)
77
+ var success: bool = result.get("ok", false)
78
+
79
+ if success:
80
+ var payload := result.duplicate(true)
81
+ payload.erase("ok")
82
+ _mcp_client.send_tool_result(request_id, true, payload, "")
83
+ else:
84
+ _mcp_client.send_tool_result(request_id, false, null, str(result.get("error", "Unknown error")))
@@ -0,0 +1,114 @@
1
+ @tool
2
+ extends Node
3
+ class_name MCPToolExecutor
4
+
5
+ var _editor_plugin: EditorPlugin = null
6
+
7
+ var _scene_tools: Node = null
8
+ var _resource_tools: Node = null
9
+ var _animation_tools: Node = null
10
+
11
+ var _tool_map: Dictionary = {}
12
+ var _initialized := false
13
+
14
+
15
+ func set_editor_plugin(plugin: EditorPlugin) -> void:
16
+ _editor_plugin = plugin
17
+ _init_tools()
18
+
19
+ if _scene_tools and _scene_tools.has_method("set_editor_plugin"):
20
+ _scene_tools.set_editor_plugin(plugin)
21
+ if _resource_tools and _resource_tools.has_method("set_editor_plugin"):
22
+ _resource_tools.set_editor_plugin(plugin)
23
+ if _animation_tools and _animation_tools.has_method("set_editor_plugin"):
24
+ _animation_tools.set_editor_plugin(plugin)
25
+
26
+
27
+ func _init_tools() -> void:
28
+ if _initialized:
29
+ return
30
+ _initialized = true
31
+
32
+ var base_path: String = get_script().resource_path.get_base_dir()
33
+ var scene_tools_path := "%s/tools/scene_tools.gd" % base_path
34
+ var resource_tools_path := "%s/tools/resource_tools.gd" % base_path
35
+ var animation_tools_path := "%s/tools/animation_tools.gd" % base_path
36
+
37
+ if ResourceLoader.exists(scene_tools_path):
38
+ var scene_script: Script = load(scene_tools_path)
39
+ if scene_script:
40
+ _scene_tools = scene_script.new()
41
+ _scene_tools.name = "SceneTools"
42
+ add_child(_scene_tools)
43
+
44
+ if ResourceLoader.exists(resource_tools_path):
45
+ var resource_script: Script = load(resource_tools_path)
46
+ if resource_script:
47
+ _resource_tools = resource_script.new()
48
+ _resource_tools.name = "ResourceTools"
49
+ add_child(_resource_tools)
50
+
51
+ if ResourceLoader.exists(animation_tools_path):
52
+ var animation_script: Script = load(animation_tools_path)
53
+ if animation_script:
54
+ _animation_tools = animation_script.new()
55
+ _animation_tools.name = "AnimationTools"
56
+ add_child(_animation_tools)
57
+
58
+ _tool_map = {
59
+ # Scene tools
60
+ "create_scene": [_scene_tools, "create_scene"],
61
+ "list_scene_nodes": [_scene_tools, "list_scene_nodes"],
62
+ "add_node": [_scene_tools, "add_node"],
63
+ "delete_node": [_scene_tools, "delete_node"],
64
+ "duplicate_node": [_scene_tools, "duplicate_node"],
65
+ "reparent_node": [_scene_tools, "reparent_node"],
66
+ "set_node_properties": [_scene_tools, "set_node_properties"],
67
+ "get_node_properties": [_scene_tools, "get_node_properties"],
68
+ "load_sprite": [_scene_tools, "load_sprite"],
69
+ "save_scene": [_scene_tools, "save_scene"],
70
+ "connect_signal": [_scene_tools, "connect_signal"],
71
+ "disconnect_signal": [_scene_tools, "disconnect_signal"],
72
+ "list_connections": [_scene_tools, "list_connections"],
73
+
74
+ # Resource tools
75
+ "create_resource": [_resource_tools, "create_resource"],
76
+ "modify_resource": [_resource_tools, "modify_resource"],
77
+ "create_material": [_resource_tools, "create_material"],
78
+ "create_shader": [_resource_tools, "create_shader"],
79
+ "create_tileset": [_resource_tools, "create_tileset"],
80
+ "set_tilemap_cells": [_resource_tools, "set_tilemap_cells"],
81
+ "set_theme_color": [_resource_tools, "set_theme_color"],
82
+ "set_theme_font_size": [_resource_tools, "set_theme_font_size"],
83
+ "apply_theme_shader": [_resource_tools, "apply_theme_shader"],
84
+
85
+ # Animation tools
86
+ "create_animation": [_animation_tools, "create_animation"],
87
+ "add_animation_track": [_animation_tools, "add_animation_track"],
88
+ "create_animation_tree": [_animation_tools, "create_animation_tree"],
89
+ "add_animation_state": [_animation_tools, "add_animation_state"],
90
+ "connect_animation_states": [_animation_tools, "connect_animation_states"],
91
+ "create_navigation_region": [_animation_tools, "create_navigation_region"],
92
+ "create_navigation_agent": [_animation_tools, "create_navigation_agent"]
93
+ }
94
+
95
+
96
+ func execute_tool(tool_name: String, args: Dictionary) -> Dictionary:
97
+ if not _tool_map.has(tool_name):
98
+ return {"ok": false, "error": "Unknown tool: " + tool_name}
99
+
100
+ var handler: Array = _tool_map[tool_name]
101
+ var node: Node = handler[0]
102
+ var method: String = handler[1]
103
+
104
+ if node == null:
105
+ return {"ok": false, "error": "Tool handler unavailable: " + tool_name}
106
+
107
+ if not node.has_method(method):
108
+ return {"ok": false, "error": "Tool method not found: %s.%s" % [node.name, method]}
109
+
110
+ var result = node.call(method, args)
111
+ if result is Dictionary:
112
+ return result
113
+
114
+ return {"ok": false, "error": "Invalid tool result from: " + tool_name}