@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,81 @@
1
+ @tool
2
+ class_name ResourceUtils
3
+ extends RefCounted
4
+
5
+ # Load a resource safely, returning null if it fails
6
+ static func safe_load(path: String) -> Resource:
7
+ if not ResourceLoader.exists(path):
8
+ push_error("Resource does not exist: " + path)
9
+ return null
10
+
11
+ var res = ResourceLoader.load(path)
12
+ if not res:
13
+ push_error("Failed to load resource: " + path)
14
+ return null
15
+
16
+ return res
17
+
18
+ # Save a resource safely, returning true if successful
19
+ static func safe_save(resource: Resource, path: String) -> bool:
20
+ if not resource:
21
+ push_error("Cannot save null resource")
22
+ return false
23
+
24
+ # Make sure directory exists
25
+ var dir_path = path.get_base_dir()
26
+ if not DirAccess.dir_exists_absolute(dir_path):
27
+ var err = DirAccess.make_dir_recursive_absolute(dir_path)
28
+ if err != OK:
29
+ push_error("Failed to create directory: " + dir_path)
30
+ return false
31
+
32
+ var result = ResourceSaver.save(resource, path)
33
+ if result != OK:
34
+ push_error("Failed to save resource: " + path)
35
+ return false
36
+
37
+ return true
38
+
39
+ # Create a new resource instance by type name
40
+ static func create_resource(type_name: String) -> Resource:
41
+ if not ClassDB.class_exists(type_name):
42
+ push_error("Class does not exist: " + type_name)
43
+ return null
44
+
45
+ if not ClassDB.is_parent_class(type_name, "Resource"):
46
+ push_error("Class is not a Resource: " + type_name)
47
+ return null
48
+
49
+ if not ClassDB.can_instantiate(type_name):
50
+ push_error("Cannot instantiate Resource class: " + type_name)
51
+ return null
52
+
53
+ return ClassDB.instantiate(type_name)
54
+
55
+ # Get a list of all resource types that inherit from a base class
56
+ static func get_resource_types(base_class: String = "Resource") -> Array[String]:
57
+ var result: Array[String] = []
58
+
59
+ for class_type in ClassDB.get_class_list():
60
+ if ClassDB.is_parent_class(class_type, base_class) and ClassDB.can_instantiate(class_type):
61
+ result.append(class_type)
62
+
63
+ return result
64
+
65
+ # Convert a resource to a JSON-compatible dictionary
66
+ static func resource_to_dict(resource: Resource) -> Dictionary:
67
+ var result = {
68
+ "resource_path": resource.resource_path,
69
+ "resource_name": resource.resource_name,
70
+ "type": resource.get_class(),
71
+ "properties": {}
72
+ }
73
+
74
+ # Get properties
75
+ var property_list = resource.get_property_list()
76
+ for prop in property_list:
77
+ var prop_name = prop["name"]
78
+ if not prop_name.begins_with("_") and prop_name != "resource_path" and prop_name != "resource_name":
79
+ result["properties"][prop_name] = resource.get(prop_name)
80
+
81
+ return result
@@ -0,0 +1 @@
1
+ uid://3jnu2dy6wa8a
@@ -0,0 +1,114 @@
1
+ @tool
2
+ class_name ScriptUtils
3
+ extends RefCounted
4
+
5
+ # Create a new GDScript with basic template content
6
+ static func create_new_script(class_name_str: String = "", extends_type: String = "Node") -> GDScript:
7
+ var script = GDScript.new()
8
+ var content = ""
9
+
10
+ if not class_name_str.is_empty():
11
+ content += "class_name " + class_name_str + "\n"
12
+
13
+ content += "extends " + extends_type + "\n\n"
14
+ content += "func _ready():\n"
15
+ content += "\tpass\n"
16
+
17
+ script.source_code = content
18
+ return script
19
+
20
+ # Create a new script file with basic template content
21
+ static func create_script_file(path: String, class_name_str: String = "", extends_type: String = "Node") -> bool:
22
+ # Make sure directory exists
23
+ var dir_path = path.get_base_dir()
24
+ if not DirAccess.dir_exists_absolute(dir_path):
25
+ var err = DirAccess.make_dir_recursive_absolute(dir_path)
26
+ if err != OK:
27
+ push_error("Failed to create directory: " + dir_path)
28
+ return false
29
+
30
+ var content = ""
31
+
32
+ if not class_name_str.is_empty():
33
+ content += "class_name " + class_name_str + "\n"
34
+
35
+ content += "extends " + extends_type + "\n\n"
36
+ content += "func _ready():\n"
37
+ content += "\tpass\n"
38
+
39
+ var file = FileAccess.open(path, FileAccess.WRITE)
40
+ if file == null:
41
+ push_error("Failed to open file for writing: " + path)
42
+ return false
43
+
44
+ file.store_string(content)
45
+ file = null # Close the file
46
+
47
+ return true
48
+
49
+ # Parse a script file and extract its class name and base class
50
+ static func get_script_info(path: String) -> Dictionary:
51
+ var result = {
52
+ "class_name": "",
53
+ "extends": "",
54
+ "path": path
55
+ }
56
+
57
+ var file = FileAccess.open(path, FileAccess.READ)
58
+ if file == null:
59
+ push_error("Failed to open file for reading: " + path)
60
+ return result
61
+
62
+ var content = file.get_as_text()
63
+ file = null # Close the file
64
+
65
+ # Find class_name
66
+ var class_name_regex = RegEx.new()
67
+ class_name_regex.compile("class_name\\s+([A-Za-z0-9_]+)")
68
+ var matches = class_name_regex.search(content)
69
+ if matches:
70
+ result["class_name"] = matches.get_string(1)
71
+
72
+ # Find extends
73
+ var extends_regex = RegEx.new()
74
+ extends_regex.compile("extends\\s+([A-Za-z0-9_]+)")
75
+ matches = extends_regex.search(content)
76
+ if matches:
77
+ result["extends"] = matches.get_string(1)
78
+
79
+ return result
80
+
81
+ # Extract all method names from a script
82
+ static func get_script_methods(path: String) -> Array:
83
+ var methods = []
84
+
85
+ var file = FileAccess.open(path, FileAccess.READ)
86
+ if file == null:
87
+ push_error("Failed to open file for reading: " + path)
88
+ return methods
89
+
90
+ var content = file.get_as_text()
91
+ file = null # Close the file
92
+
93
+ var method_regex = RegEx.new()
94
+ method_regex.compile("func\\s+([A-Za-z0-9_]+)\\s*\\(")
95
+
96
+ var matches = method_regex.search_all(content)
97
+ for match_idx in range(matches.size()):
98
+ methods.append(matches[match_idx].get_string(1))
99
+
100
+ return methods
101
+
102
+ # Apply a script to a node
103
+ static func apply_script_to_node(node: Node, script_path: String) -> bool:
104
+ if not node:
105
+ push_error("Node is null")
106
+ return false
107
+
108
+ var script = ResourceLoader.load(script_path)
109
+ if not script:
110
+ push_error("Failed to load script: " + script_path)
111
+ return false
112
+
113
+ node.set_script(script)
114
+ return true
@@ -0,0 +1 @@
1
+ uid://dnj1wd7yiap7t
@@ -0,0 +1,197 @@
1
+ @tool
2
+ class_name MCPWebSocketServer
3
+ extends Node
4
+
5
+ signal client_connected(id)
6
+ signal client_disconnected(id)
7
+ signal command_received(client_id, command)
8
+
9
+ var tcp_server = TCPServer.new()
10
+ var peers = {}
11
+ var _port = 9080
12
+
13
+ func _ready():
14
+ set_process(false)
15
+
16
+ func _process(_delta):
17
+ poll()
18
+
19
+ func is_server_active() -> bool:
20
+ return tcp_server.is_listening()
21
+
22
+ func start_server() -> int:
23
+ if is_server_active():
24
+ return ERR_ALREADY_IN_USE
25
+
26
+ # Configure TCP server
27
+ var err = tcp_server.listen(_port, "127.0.0.1")
28
+ if err == OK:
29
+ set_process(true)
30
+ print("MCP WebSocket server started on port %d" % _port)
31
+ else:
32
+ print("Failed to start MCP WebSocket server: %d" % err)
33
+
34
+ return err
35
+
36
+ func stop_server() -> void:
37
+ if is_server_active():
38
+ # Close all client connections properly
39
+ for client_id in peers.keys():
40
+ if peers[client_id] != null:
41
+ peers[client_id].close()
42
+ peers.clear()
43
+
44
+ # Stop TCP server
45
+ tcp_server.stop()
46
+ set_process(false)
47
+ print("MCP WebSocket server stopped")
48
+
49
+ func poll() -> void:
50
+ if not tcp_server.is_listening():
51
+ return
52
+
53
+ # Handle new connections
54
+ if tcp_server.is_connection_available():
55
+ var tcp = tcp_server.take_connection()
56
+ if tcp == null:
57
+ print("Failed to take TCP connection")
58
+ return
59
+
60
+ tcp.set_no_delay(true) # Important for WebSocket
61
+
62
+ print("New TCP connection accepted")
63
+ var ws = WebSocketPeer.new()
64
+
65
+ # Configure WebSocket peer
66
+ ws.inbound_buffer_size = 64 * 1024 * 1024 # 64MB buffer
67
+ ws.outbound_buffer_size = 64 * 1024 * 1024 # 64MB buffer
68
+ ws.max_queued_packets = 4096
69
+
70
+ # Accept the stream
71
+ var err = ws.accept_stream(tcp)
72
+ if err != OK:
73
+ print("Failed to accept WebSocket stream: ", err)
74
+ return
75
+
76
+ # Generate client ID and store peer
77
+ var client_id = randi() % (1 << 30) + 1
78
+ peers[client_id] = ws
79
+ print("WebSocket connection setup for client: ", client_id)
80
+
81
+ # Process existing connections
82
+ var to_remove = []
83
+
84
+ for client_id in peers:
85
+ var peer = peers[client_id]
86
+ if peer == null:
87
+ to_remove.append(client_id)
88
+ continue
89
+
90
+ peer.poll()
91
+ var state = peer.get_ready_state()
92
+
93
+ match state:
94
+ WebSocketPeer.STATE_OPEN:
95
+ # Process any available packets
96
+ while peer.get_available_packet_count() > 0:
97
+ var packet = peer.get_packet()
98
+ _handle_packet(client_id, packet)
99
+
100
+ WebSocketPeer.STATE_CONNECTING:
101
+ print("Client %d still connecting..." % client_id)
102
+
103
+ WebSocketPeer.STATE_CLOSING:
104
+ print("Client %d closing connection..." % client_id)
105
+
106
+ WebSocketPeer.STATE_CLOSED:
107
+ print("Client %d connection closed. Code: %d, Reason: %s" % [
108
+ client_id,
109
+ peer.get_close_code(),
110
+ peer.get_close_reason()
111
+ ])
112
+ emit_signal("client_disconnected", client_id)
113
+ to_remove.append(client_id)
114
+
115
+ # Remove disconnected clients
116
+ for client_id in to_remove:
117
+ var peer = peers[client_id]
118
+ if peer != null:
119
+ peer.close()
120
+ peers.erase(client_id)
121
+
122
+ func _handle_packet(client_id: int, packet: PackedByteArray) -> void:
123
+ var text = packet.get_string_from_utf8()
124
+ var json = JSON.new()
125
+ var parse_result = json.parse(text)
126
+
127
+ if parse_result == OK:
128
+ var data = json.get_data()
129
+
130
+ # Handle ping-pong for FastMCP
131
+ if data.has("method") and data["method"] == "ping":
132
+ var response = {
133
+ "jsonrpc": "2.0",
134
+ "id": data.get("id", 0),
135
+ "result": "pong"
136
+ }
137
+ send_response(client_id, response)
138
+ return
139
+
140
+ print("Received command from client %d: %s" % [client_id, data])
141
+ emit_signal("command_received", client_id, data)
142
+ else:
143
+ print("Error parsing JSON from client %d: %s at line %d" %
144
+ [client_id, json.get_error_message(), json.get_error_line()])
145
+
146
+ func send_response(client_id: int, response: Dictionary) -> int:
147
+ if not peers.has(client_id):
148
+ print("Error: Client %d not found" % client_id)
149
+ return ERR_DOES_NOT_EXIST
150
+
151
+ var peer = peers[client_id]
152
+ if peer == null:
153
+ print("Error: Peer is null for client %d" % client_id)
154
+ return ERR_INVALID_PARAMETER
155
+
156
+ if peer.get_ready_state() != WebSocketPeer.STATE_OPEN:
157
+ print("Error: Client %d connection not open" % client_id)
158
+ return ERR_UNAVAILABLE
159
+
160
+ var json_text = JSON.stringify(response)
161
+ var result = peer.send_text(json_text)
162
+
163
+ if result != OK:
164
+ print("Error sending response to client %d: %d" % [client_id, result])
165
+
166
+ return result
167
+
168
+ func send_event(client_id: int, event: Dictionary) -> int:
169
+ if not peers.has(client_id):
170
+ return ERR_DOES_NOT_EXIST
171
+
172
+ var peer = peers[client_id]
173
+ if peer == null or peer.get_ready_state() != WebSocketPeer.STATE_OPEN:
174
+ return ERR_UNAVAILABLE
175
+
176
+ var payload := event.duplicate(true)
177
+ if not payload.has("event"):
178
+ payload["event"] = "unknown"
179
+
180
+ var json_text = JSON.stringify(payload)
181
+ return peer.send_text(json_text)
182
+
183
+ func broadcast_event(event: Dictionary) -> void:
184
+ for client_id in peers.keys():
185
+ send_event(client_id, event)
186
+
187
+ func set_port(new_port: int) -> void:
188
+ if is_server_active():
189
+ push_error("Cannot change port while server is active")
190
+ return
191
+ _port = new_port
192
+
193
+ func get_port() -> int:
194
+ return _port
195
+
196
+ func get_client_count() -> int:
197
+ return peers.size()
@@ -0,0 +1 @@
1
+ uid://5yoxi2gkygu4
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};