gopeak 2.3.7 → 2.3.8
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/LICENSE +21 -21
- package/README.md +279 -411
- package/build/addon/auto_reload/auto_reload.gd +126 -126
- package/build/addon/auto_reload/plugin.cfg +7 -7
- package/build/addon/godot_mcp_editor/mcp_client.gd +178 -178
- package/build/addon/godot_mcp_editor/plugin.cfg +6 -6
- package/build/addon/godot_mcp_editor/plugin.gd +84 -84
- package/build/addon/godot_mcp_editor/tool_executor.gd +114 -114
- package/build/addon/godot_mcp_editor/tools/animation_tools.gd +502 -502
- package/build/addon/godot_mcp_editor/tools/resource_tools.gd +425 -425
- package/build/addon/godot_mcp_editor/tools/scene_tools.gd +761 -761
- package/build/addon/godot_mcp_runtime/godot_mcp_runtime.gd +33 -33
- package/build/addon/godot_mcp_runtime/mcp_runtime_autoload.gd +671 -619
- package/build/addon/godot_mcp_runtime/plugin.cfg +7 -7
- package/build/cli/notify.js +4 -3
- package/build/cli.js +18 -18
- package/build/godot-bridge.js +11 -11
- package/build/index.js +238 -123
- package/build/scripts/godot_operations.gd +6823 -6823
- package/build/visualizer/events.js +19 -19
- package/build/visualizer/panel.js +34 -34
- package/build/visualizer/usages.js +14 -14
- package/build/visualizer-server.js +11 -11
- package/build/visualizer.html +2596 -2596
- package/package.json +106 -107
- package/scripts/postinstall.mjs +29 -29
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
@tool
|
|
2
|
-
extends EditorPlugin
|
|
3
|
-
|
|
4
|
-
## Auto Reload Plugin for Godot MCP
|
|
5
|
-
## Automatically reloads scenes and scripts when modified externally
|
|
6
|
-
##
|
|
7
|
-
## Features:
|
|
8
|
-
## - Auto-detects external file changes (scenes, scripts, resources)
|
|
9
|
-
## - Reloads without confirmation popup
|
|
10
|
-
## - Lightweight 1-second polling (negligible performance impact)
|
|
11
|
-
## - Perfect for MCP integration workflow
|
|
12
|
-
|
|
13
|
-
var check_interval: float = 1.0 # Check every 1 second
|
|
14
|
-
var timer: Timer
|
|
15
|
-
var watched_files: Dictionary = {} # {path: last_modified_time}
|
|
16
|
-
var editor_interface: EditorInterface
|
|
17
|
-
|
|
18
|
-
# File extensions to watch
|
|
19
|
-
var watched_extensions: Array[String] = [".tscn", ".scn", ".gd", ".tres", ".res"]
|
|
20
|
-
|
|
21
|
-
func _enter_tree() -> void:
|
|
22
|
-
editor_interface = get_editor_interface()
|
|
23
|
-
|
|
24
|
-
# Create timer for periodic file checking
|
|
25
|
-
timer = Timer.new()
|
|
26
|
-
timer.wait_time = check_interval
|
|
27
|
-
timer.timeout.connect(_check_for_changes)
|
|
28
|
-
add_child(timer)
|
|
29
|
-
timer.start()
|
|
30
|
-
|
|
31
|
-
# Initial scan of open scenes
|
|
32
|
-
_update_watched_files()
|
|
33
|
-
|
|
34
|
-
print("[Godot MCP - AutoReload] Plugin activated - watching for external changes")
|
|
35
|
-
|
|
36
|
-
func _exit_tree() -> void:
|
|
37
|
-
if timer:
|
|
38
|
-
timer.stop()
|
|
39
|
-
timer.queue_free()
|
|
40
|
-
print("[Godot MCP - AutoReload] Plugin deactivated")
|
|
41
|
-
|
|
42
|
-
func _update_watched_files() -> void:
|
|
43
|
-
# Get currently edited scene
|
|
44
|
-
var edited_scene = editor_interface.get_edited_scene_root()
|
|
45
|
-
if edited_scene and edited_scene.scene_file_path:
|
|
46
|
-
var path = edited_scene.scene_file_path
|
|
47
|
-
if not watched_files.has(path):
|
|
48
|
-
watched_files[path] = _get_modified_time(path)
|
|
49
|
-
|
|
50
|
-
# Also watch attached scripts
|
|
51
|
-
_watch_node_scripts(edited_scene)
|
|
52
|
-
|
|
53
|
-
func _watch_node_scripts(node: Node) -> void:
|
|
54
|
-
# Watch the script attached to this node
|
|
55
|
-
var script = node.get_script()
|
|
56
|
-
if script and script.resource_path:
|
|
57
|
-
var path = script.resource_path
|
|
58
|
-
if not watched_files.has(path):
|
|
59
|
-
watched_files[path] = _get_modified_time(path)
|
|
60
|
-
|
|
61
|
-
# Recursively watch children
|
|
62
|
-
for child in node.get_children():
|
|
63
|
-
_watch_node_scripts(child)
|
|
64
|
-
|
|
65
|
-
func _get_modified_time(path: String) -> int:
|
|
66
|
-
var global_path = ProjectSettings.globalize_path(path)
|
|
67
|
-
if FileAccess.file_exists(global_path):
|
|
68
|
-
return FileAccess.get_modified_time(global_path)
|
|
69
|
-
return 0
|
|
70
|
-
|
|
71
|
-
func _check_for_changes() -> void:
|
|
72
|
-
_update_watched_files()
|
|
73
|
-
|
|
74
|
-
var files_to_reload: Array = []
|
|
75
|
-
var scripts_to_reload: Array = []
|
|
76
|
-
|
|
77
|
-
for path in watched_files.keys():
|
|
78
|
-
var current_time = _get_modified_time(path)
|
|
79
|
-
var last_time = watched_files[path]
|
|
80
|
-
|
|
81
|
-
if current_time > last_time:
|
|
82
|
-
if path.ends_with(".gd"):
|
|
83
|
-
scripts_to_reload.append(path)
|
|
84
|
-
else:
|
|
85
|
-
files_to_reload.append(path)
|
|
86
|
-
watched_files[path] = current_time
|
|
87
|
-
|
|
88
|
-
# Reload changed scripts first
|
|
89
|
-
for path in scripts_to_reload:
|
|
90
|
-
_reload_script(path)
|
|
91
|
-
|
|
92
|
-
# Then reload changed scenes
|
|
93
|
-
for path in files_to_reload:
|
|
94
|
-
_reload_scene(path)
|
|
95
|
-
|
|
96
|
-
func _reload_script(path: String) -> void:
|
|
97
|
-
print("[Godot MCP - AutoReload] Script changed: ", path)
|
|
98
|
-
|
|
99
|
-
# Reload the script resource
|
|
100
|
-
var script = load(path)
|
|
101
|
-
if script:
|
|
102
|
-
ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_REPLACE)
|
|
103
|
-
print("[Godot MCP - AutoReload] Script reloaded: ", path)
|
|
104
|
-
|
|
105
|
-
func _reload_scene(path: String) -> void:
|
|
106
|
-
print("[Godot MCP - AutoReload] Scene changed: ", path)
|
|
107
|
-
|
|
108
|
-
# Check if this is the currently edited scene
|
|
109
|
-
var edited_scene = editor_interface.get_edited_scene_root()
|
|
110
|
-
if edited_scene and edited_scene.scene_file_path == path:
|
|
111
|
-
# Reload the current scene
|
|
112
|
-
editor_interface.reload_scene_from_path(path)
|
|
113
|
-
print("[Godot MCP - AutoReload] Scene reloaded: ", path)
|
|
114
|
-
|
|
115
|
-
# Public API for configuration
|
|
116
|
-
func set_check_interval(interval: float) -> void:
|
|
117
|
-
check_interval = interval
|
|
118
|
-
if timer:
|
|
119
|
-
timer.wait_time = interval
|
|
120
|
-
|
|
121
|
-
func add_watched_extension(ext: String) -> void:
|
|
122
|
-
if not watched_extensions.has(ext):
|
|
123
|
-
watched_extensions.append(ext)
|
|
124
|
-
|
|
125
|
-
func clear_watched_files() -> void:
|
|
126
|
-
watched_files.clear()
|
|
1
|
+
@tool
|
|
2
|
+
extends EditorPlugin
|
|
3
|
+
|
|
4
|
+
## Auto Reload Plugin for Godot MCP
|
|
5
|
+
## Automatically reloads scenes and scripts when modified externally
|
|
6
|
+
##
|
|
7
|
+
## Features:
|
|
8
|
+
## - Auto-detects external file changes (scenes, scripts, resources)
|
|
9
|
+
## - Reloads without confirmation popup
|
|
10
|
+
## - Lightweight 1-second polling (negligible performance impact)
|
|
11
|
+
## - Perfect for MCP integration workflow
|
|
12
|
+
|
|
13
|
+
var check_interval: float = 1.0 # Check every 1 second
|
|
14
|
+
var timer: Timer
|
|
15
|
+
var watched_files: Dictionary = {} # {path: last_modified_time}
|
|
16
|
+
var editor_interface: EditorInterface
|
|
17
|
+
|
|
18
|
+
# File extensions to watch
|
|
19
|
+
var watched_extensions: Array[String] = [".tscn", ".scn", ".gd", ".tres", ".res"]
|
|
20
|
+
|
|
21
|
+
func _enter_tree() -> void:
|
|
22
|
+
editor_interface = get_editor_interface()
|
|
23
|
+
|
|
24
|
+
# Create timer for periodic file checking
|
|
25
|
+
timer = Timer.new()
|
|
26
|
+
timer.wait_time = check_interval
|
|
27
|
+
timer.timeout.connect(_check_for_changes)
|
|
28
|
+
add_child(timer)
|
|
29
|
+
timer.start()
|
|
30
|
+
|
|
31
|
+
# Initial scan of open scenes
|
|
32
|
+
_update_watched_files()
|
|
33
|
+
|
|
34
|
+
print("[Godot MCP - AutoReload] Plugin activated - watching for external changes")
|
|
35
|
+
|
|
36
|
+
func _exit_tree() -> void:
|
|
37
|
+
if timer:
|
|
38
|
+
timer.stop()
|
|
39
|
+
timer.queue_free()
|
|
40
|
+
print("[Godot MCP - AutoReload] Plugin deactivated")
|
|
41
|
+
|
|
42
|
+
func _update_watched_files() -> void:
|
|
43
|
+
# Get currently edited scene
|
|
44
|
+
var edited_scene = editor_interface.get_edited_scene_root()
|
|
45
|
+
if edited_scene and edited_scene.scene_file_path:
|
|
46
|
+
var path = edited_scene.scene_file_path
|
|
47
|
+
if not watched_files.has(path):
|
|
48
|
+
watched_files[path] = _get_modified_time(path)
|
|
49
|
+
|
|
50
|
+
# Also watch attached scripts
|
|
51
|
+
_watch_node_scripts(edited_scene)
|
|
52
|
+
|
|
53
|
+
func _watch_node_scripts(node: Node) -> void:
|
|
54
|
+
# Watch the script attached to this node
|
|
55
|
+
var script = node.get_script()
|
|
56
|
+
if script and script.resource_path:
|
|
57
|
+
var path = script.resource_path
|
|
58
|
+
if not watched_files.has(path):
|
|
59
|
+
watched_files[path] = _get_modified_time(path)
|
|
60
|
+
|
|
61
|
+
# Recursively watch children
|
|
62
|
+
for child in node.get_children():
|
|
63
|
+
_watch_node_scripts(child)
|
|
64
|
+
|
|
65
|
+
func _get_modified_time(path: String) -> int:
|
|
66
|
+
var global_path = ProjectSettings.globalize_path(path)
|
|
67
|
+
if FileAccess.file_exists(global_path):
|
|
68
|
+
return FileAccess.get_modified_time(global_path)
|
|
69
|
+
return 0
|
|
70
|
+
|
|
71
|
+
func _check_for_changes() -> void:
|
|
72
|
+
_update_watched_files()
|
|
73
|
+
|
|
74
|
+
var files_to_reload: Array = []
|
|
75
|
+
var scripts_to_reload: Array = []
|
|
76
|
+
|
|
77
|
+
for path in watched_files.keys():
|
|
78
|
+
var current_time = _get_modified_time(path)
|
|
79
|
+
var last_time = watched_files[path]
|
|
80
|
+
|
|
81
|
+
if current_time > last_time:
|
|
82
|
+
if path.ends_with(".gd"):
|
|
83
|
+
scripts_to_reload.append(path)
|
|
84
|
+
else:
|
|
85
|
+
files_to_reload.append(path)
|
|
86
|
+
watched_files[path] = current_time
|
|
87
|
+
|
|
88
|
+
# Reload changed scripts first
|
|
89
|
+
for path in scripts_to_reload:
|
|
90
|
+
_reload_script(path)
|
|
91
|
+
|
|
92
|
+
# Then reload changed scenes
|
|
93
|
+
for path in files_to_reload:
|
|
94
|
+
_reload_scene(path)
|
|
95
|
+
|
|
96
|
+
func _reload_script(path: String) -> void:
|
|
97
|
+
print("[Godot MCP - AutoReload] Script changed: ", path)
|
|
98
|
+
|
|
99
|
+
# Reload the script resource
|
|
100
|
+
var script = load(path)
|
|
101
|
+
if script:
|
|
102
|
+
ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_REPLACE)
|
|
103
|
+
print("[Godot MCP - AutoReload] Script reloaded: ", path)
|
|
104
|
+
|
|
105
|
+
func _reload_scene(path: String) -> void:
|
|
106
|
+
print("[Godot MCP - AutoReload] Scene changed: ", path)
|
|
107
|
+
|
|
108
|
+
# Check if this is the currently edited scene
|
|
109
|
+
var edited_scene = editor_interface.get_edited_scene_root()
|
|
110
|
+
if edited_scene and edited_scene.scene_file_path == path:
|
|
111
|
+
# Reload the current scene
|
|
112
|
+
editor_interface.reload_scene_from_path(path)
|
|
113
|
+
print("[Godot MCP - AutoReload] Scene reloaded: ", path)
|
|
114
|
+
|
|
115
|
+
# Public API for configuration
|
|
116
|
+
func set_check_interval(interval: float) -> void:
|
|
117
|
+
check_interval = interval
|
|
118
|
+
if timer:
|
|
119
|
+
timer.wait_time = interval
|
|
120
|
+
|
|
121
|
+
func add_watched_extension(ext: String) -> void:
|
|
122
|
+
if not watched_extensions.has(ext):
|
|
123
|
+
watched_extensions.append(ext)
|
|
124
|
+
|
|
125
|
+
func clear_watched_files() -> void:
|
|
126
|
+
watched_files.clear()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
[plugin]
|
|
2
|
-
|
|
3
|
-
name="Godot MCP Auto Reload"
|
|
4
|
-
description="Automatically reload scenes and scripts when modified externally. Essential for MCP (Model Context Protocol) integration workflow."
|
|
5
|
-
author="HaD0Yun"
|
|
6
|
-
version="1.0.0"
|
|
7
|
-
script="auto_reload.gd"
|
|
1
|
+
[plugin]
|
|
2
|
+
|
|
3
|
+
name="Godot MCP Auto Reload"
|
|
4
|
+
description="Automatically reload scenes and scripts when modified externally. Essential for MCP (Model Context Protocol) integration workflow."
|
|
5
|
+
author="HaD0Yun"
|
|
6
|
+
version="1.0.0"
|
|
7
|
+
script="auto_reload.gd"
|
|
@@ -1,178 +1,178 @@
|
|
|
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 = "") -> void:
|
|
64
|
-
server_url = _resolve_server_url(url)
|
|
65
|
-
_should_reconnect = true
|
|
66
|
-
_current_reconnect_delay = RECONNECT_DELAY
|
|
67
|
-
_attempt_connection()
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
func _resolve_server_url(explicit_url: String) -> String:
|
|
71
|
-
if explicit_url != "":
|
|
72
|
-
return explicit_url
|
|
73
|
-
|
|
74
|
-
var env_keys := ["GODOT_BRIDGE_PORT", "MCP_BRIDGE_PORT", "GOPEAK_BRIDGE_PORT"]
|
|
75
|
-
for key in env_keys:
|
|
76
|
-
var raw := OS.get_environment(key)
|
|
77
|
-
if raw == "":
|
|
78
|
-
continue
|
|
79
|
-
if raw.is_valid_int():
|
|
80
|
-
var port := int(raw)
|
|
81
|
-
if port >= 1 and port <= 65535:
|
|
82
|
-
return "ws://127.0.0.1:%d/godot" % port
|
|
83
|
-
|
|
84
|
-
return DEFAULT_URL
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
func disconnect_from_server() -> void:
|
|
88
|
-
_should_reconnect = false
|
|
89
|
-
if _reconnect_timer:
|
|
90
|
-
_reconnect_timer.stop()
|
|
91
|
-
if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
|
92
|
-
socket.close()
|
|
93
|
-
_is_connected = false
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
func _attempt_connection() -> void:
|
|
97
|
-
if socket.get_ready_state() != WebSocketPeer.STATE_CLOSED:
|
|
98
|
-
socket.close()
|
|
99
|
-
|
|
100
|
-
var err := socket.connect_to_url(server_url)
|
|
101
|
-
if err != OK:
|
|
102
|
-
push_error("[MCP Editor] Failed to connect: %s" % err)
|
|
103
|
-
_schedule_reconnect()
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
func _handle_connect() -> void:
|
|
107
|
-
_is_connected = true
|
|
108
|
-
_current_reconnect_delay = RECONNECT_DELAY
|
|
109
|
-
|
|
110
|
-
_send_message({
|
|
111
|
-
"type": "godot_ready",
|
|
112
|
-
"project_path": _project_path
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
connected.emit()
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
func _handle_disconnect() -> void:
|
|
119
|
-
_is_connected = false
|
|
120
|
-
disconnected.emit()
|
|
121
|
-
|
|
122
|
-
if _should_reconnect:
|
|
123
|
-
_schedule_reconnect()
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
func _schedule_reconnect() -> void:
|
|
127
|
-
if _reconnect_timer == null:
|
|
128
|
-
return
|
|
129
|
-
_reconnect_timer.start(_current_reconnect_delay)
|
|
130
|
-
_current_reconnect_delay = min(_current_reconnect_delay * 2.0, MAX_RECONNECT_DELAY)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
func _on_reconnect_timer() -> void:
|
|
134
|
-
_attempt_connection()
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
func _handle_message(json_string: String) -> void:
|
|
138
|
-
var message = JSON.parse_string(json_string)
|
|
139
|
-
if message == null:
|
|
140
|
-
push_error("[MCP Editor] Failed to parse message: %s" % json_string)
|
|
141
|
-
return
|
|
142
|
-
|
|
143
|
-
match message.get("type", ""):
|
|
144
|
-
"ping":
|
|
145
|
-
_send_message({"type": "pong"})
|
|
146
|
-
|
|
147
|
-
"tool_invoke":
|
|
148
|
-
var request_id: String = message.get("id", "")
|
|
149
|
-
var tool_name: String = message.get("tool", "")
|
|
150
|
-
var args: Dictionary = message.get("args", {})
|
|
151
|
-
tool_requested.emit(request_id, tool_name, args)
|
|
152
|
-
|
|
153
|
-
_:
|
|
154
|
-
pass
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
func send_tool_result(request_id: String, success: bool, result = null, error: String = "") -> void:
|
|
158
|
-
var response := {
|
|
159
|
-
"type": "tool_result",
|
|
160
|
-
"id": request_id,
|
|
161
|
-
"success": success
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if success:
|
|
165
|
-
response["result"] = result
|
|
166
|
-
else:
|
|
167
|
-
response["error"] = error
|
|
168
|
-
|
|
169
|
-
_send_message(response)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
func _send_message(message: Dictionary) -> void:
|
|
173
|
-
if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
|
174
|
-
socket.send_text(JSON.stringify(message))
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
func is_connected_to_server() -> bool:
|
|
178
|
-
return _is_connected
|
|
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 = "") -> void:
|
|
64
|
+
server_url = _resolve_server_url(url)
|
|
65
|
+
_should_reconnect = true
|
|
66
|
+
_current_reconnect_delay = RECONNECT_DELAY
|
|
67
|
+
_attempt_connection()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
func _resolve_server_url(explicit_url: String) -> String:
|
|
71
|
+
if explicit_url != "":
|
|
72
|
+
return explicit_url
|
|
73
|
+
|
|
74
|
+
var env_keys := ["GODOT_BRIDGE_PORT", "MCP_BRIDGE_PORT", "GOPEAK_BRIDGE_PORT"]
|
|
75
|
+
for key in env_keys:
|
|
76
|
+
var raw := OS.get_environment(key)
|
|
77
|
+
if raw == "":
|
|
78
|
+
continue
|
|
79
|
+
if raw.is_valid_int():
|
|
80
|
+
var port := int(raw)
|
|
81
|
+
if port >= 1 and port <= 65535:
|
|
82
|
+
return "ws://127.0.0.1:%d/godot" % port
|
|
83
|
+
|
|
84
|
+
return DEFAULT_URL
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
func disconnect_from_server() -> void:
|
|
88
|
+
_should_reconnect = false
|
|
89
|
+
if _reconnect_timer:
|
|
90
|
+
_reconnect_timer.stop()
|
|
91
|
+
if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
|
92
|
+
socket.close()
|
|
93
|
+
_is_connected = false
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
func _attempt_connection() -> void:
|
|
97
|
+
if socket.get_ready_state() != WebSocketPeer.STATE_CLOSED:
|
|
98
|
+
socket.close()
|
|
99
|
+
|
|
100
|
+
var err := socket.connect_to_url(server_url)
|
|
101
|
+
if err != OK:
|
|
102
|
+
push_error("[MCP Editor] Failed to connect: %s" % err)
|
|
103
|
+
_schedule_reconnect()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
func _handle_connect() -> void:
|
|
107
|
+
_is_connected = true
|
|
108
|
+
_current_reconnect_delay = RECONNECT_DELAY
|
|
109
|
+
|
|
110
|
+
_send_message({
|
|
111
|
+
"type": "godot_ready",
|
|
112
|
+
"project_path": _project_path
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
connected.emit()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
func _handle_disconnect() -> void:
|
|
119
|
+
_is_connected = false
|
|
120
|
+
disconnected.emit()
|
|
121
|
+
|
|
122
|
+
if _should_reconnect:
|
|
123
|
+
_schedule_reconnect()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
func _schedule_reconnect() -> void:
|
|
127
|
+
if _reconnect_timer == null:
|
|
128
|
+
return
|
|
129
|
+
_reconnect_timer.start(_current_reconnect_delay)
|
|
130
|
+
_current_reconnect_delay = min(_current_reconnect_delay * 2.0, MAX_RECONNECT_DELAY)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
func _on_reconnect_timer() -> void:
|
|
134
|
+
_attempt_connection()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
func _handle_message(json_string: String) -> void:
|
|
138
|
+
var message = JSON.parse_string(json_string)
|
|
139
|
+
if message == null:
|
|
140
|
+
push_error("[MCP Editor] Failed to parse message: %s" % json_string)
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
match message.get("type", ""):
|
|
144
|
+
"ping":
|
|
145
|
+
_send_message({"type": "pong"})
|
|
146
|
+
|
|
147
|
+
"tool_invoke":
|
|
148
|
+
var request_id: String = message.get("id", "")
|
|
149
|
+
var tool_name: String = message.get("tool", "")
|
|
150
|
+
var args: Dictionary = message.get("args", {})
|
|
151
|
+
tool_requested.emit(request_id, tool_name, args)
|
|
152
|
+
|
|
153
|
+
_:
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
func send_tool_result(request_id: String, success: bool, result = null, error: String = "") -> void:
|
|
158
|
+
var response := {
|
|
159
|
+
"type": "tool_result",
|
|
160
|
+
"id": request_id,
|
|
161
|
+
"success": success
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if success:
|
|
165
|
+
response["result"] = result
|
|
166
|
+
else:
|
|
167
|
+
response["error"] = error
|
|
168
|
+
|
|
169
|
+
_send_message(response)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
func _send_message(message: Dictionary) -> void:
|
|
173
|
+
if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
|
174
|
+
socket.send_text(JSON.stringify(message))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
func is_connected_to_server() -> bool:
|
|
178
|
+
return _is_connected
|
|
@@ -1,6 +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"
|
|
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"
|