godot-mcp-tool 0.1.0 → 0.1.1
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/addon/godot_mcp_tool/addon_info.gd +1 -1
- package/addon/godot_mcp_tool/animation/animation_reference.gd +12 -12
- package/addon/godot_mcp_tool/animation/animation_report.gd +8 -8
- package/addon/godot_mcp_tool/animation/animation_track_planner.gd +14 -14
- package/addon/godot_mcp_tool/animation/state_machine_plan.gd +3 -3
- package/addon/godot_mcp_tool/animation/state_machine_report.gd +7 -7
- package/addon/godot_mcp_tool/assets/import_options.gd +6 -6
- package/addon/godot_mcp_tool/assets/import_settings.gd +7 -7
- package/addon/godot_mcp_tool/assets/imported_scene_report.gd +11 -11
- package/addon/godot_mcp_tool/bridge/authenticator.gd +7 -7
- package/addon/godot_mcp_tool/bridge/bridge_connection.gd +7 -7
- package/addon/godot_mcp_tool/bridge/bridge_probe.gd +2 -2
- package/addon/godot_mcp_tool/bridge/bridge_server.gd +5 -5
- package/addon/godot_mcp_tool/bridge/bridge_session.gd +22 -22
- package/addon/godot_mcp_tool/bridge/discovery_file.gd +2 -2
- package/addon/godot_mcp_tool/capture/bounds.gd +4 -4
- package/addon/godot_mcp_tool/capture/bounds_report.gd +12 -12
- package/addon/godot_mcp_tool/capture/capture_params.gd +9 -9
- package/addon/godot_mcp_tool/capture/screenshot_file.gd +8 -8
- package/addon/godot_mcp_tool/capture/view_renderer.gd +4 -4
- package/addon/godot_mcp_tool/capture/view_requests.gd +14 -14
- package/addon/godot_mcp_tool/codec/converters/array_converter.gd +3 -3
- package/addon/godot_mcp_tool/codec/converters/color_converter.gd +2 -2
- package/addon/godot_mcp_tool/codec/converters/converter.gd +10 -10
- package/addon/godot_mcp_tool/codec/converters/dictionary_converter.gd +1 -1
- package/addon/godot_mcp_tool/codec/converters/enum_converter.gd +3 -3
- package/addon/godot_mcp_tool/codec/converters/flags_converter.gd +3 -3
- package/addon/godot_mcp_tool/codec/converters/godot_syntax_converter.gd +1 -1
- package/addon/godot_mcp_tool/codec/converters/node_reference_converter.gd +1 -1
- package/addon/godot_mcp_tool/codec/converters/primitive_converter.gd +1 -1
- package/addon/godot_mcp_tool/codec/converters/resource_converter.gd +5 -5
- package/addon/godot_mcp_tool/codec/converters/untyped_converter.gd +1 -1
- package/addon/godot_mcp_tool/codec/converters/vector_converter.gd +1 -1
- package/addon/godot_mcp_tool/codec/value_decoder.gd +3 -3
- package/addon/godot_mcp_tool/codec/variant_codec.gd +2 -2
- package/addon/godot_mcp_tool/commands/animation/animation_delete_command.gd +20 -20
- package/addon/godot_mcp_tool/commands/animation/animation_get_command.gd +21 -21
- package/addon/godot_mcp_tool/commands/animation/animation_set_command.gd +31 -31
- package/addon/godot_mcp_tool/commands/animation/animation_tree_get_command.gd +19 -19
- package/addon/godot_mcp_tool/commands/animation/animation_tree_set_command.gd +31 -31
- package/addon/godot_mcp_tool/commands/asset/asset_import_settings_get_command.gd +17 -17
- package/addon/godot_mcp_tool/commands/asset/asset_import_settings_set_command.gd +28 -28
- package/addon/godot_mcp_tool/commands/command.gd +3 -3
- package/addon/godot_mcp_tool/commands/command_catalog.gd +125 -125
- package/addon/godot_mcp_tool/commands/command_router.gd +4 -4
- package/addon/godot_mcp_tool/commands/editor/editor_breakpoints_command.gd +3 -3
- package/addon/godot_mcp_tool/commands/editor/editor_rescan_command.gd +11 -11
- package/addon/godot_mcp_tool/commands/editor/editor_run_script_command.gd +7 -7
- package/addon/godot_mcp_tool/commands/editor/editor_screenshot_command.gd +34 -34
- package/addon/godot_mcp_tool/commands/editor/editor_select_command.gd +20 -20
- package/addon/godot_mcp_tool/commands/editor/editor_unsaved_files_command.gd +5 -5
- package/addon/godot_mcp_tool/commands/file/filesystem_delete_command.gd +27 -27
- package/addon/godot_mcp_tool/commands/file/filesystem_move_command.gd +31 -31
- package/addon/godot_mcp_tool/commands/file/resource_find_usages_command.gd +8 -8
- package/addon/godot_mcp_tool/commands/game/game_get_output_command.gd +9 -9
- package/addon/godot_mcp_tool/commands/game/game_request_command.gd +11 -11
- package/addon/godot_mcp_tool/commands/game/game_run_command.gd +30 -30
- package/addon/godot_mcp_tool/commands/game/game_stop_command.gd +6 -6
- package/addon/godot_mcp_tool/commands/input/input_map_get_command.gd +10 -10
- package/addon/godot_mcp_tool/commands/input/input_map_set_command.gd +16 -16
- package/addon/godot_mcp_tool/commands/navigation/navigation_bake_command.gd +25 -25
- package/addon/godot_mcp_tool/commands/node/node_add_command.gd +3 -3
- package/addon/godot_mcp_tool/commands/node/node_call_method_command.gd +23 -23
- package/addon/godot_mcp_tool/commands/node/node_delete_command.gd +3 -3
- package/addon/godot_mcp_tool/commands/node/node_duplicate_command.gd +3 -3
- package/addon/godot_mcp_tool/commands/node/node_find_command.gd +26 -26
- package/addon/godot_mcp_tool/commands/node/node_get_bounds_command.gd +23 -23
- package/addon/godot_mcp_tool/commands/node/node_get_properties_command.gd +18 -18
- package/addon/godot_mcp_tool/commands/node/node_reparent_command.gd +3 -3
- package/addon/godot_mcp_tool/commands/node/node_set_groups_command.gd +3 -3
- package/addon/godot_mcp_tool/commands/node/node_set_properties_command.gd +3 -3
- package/addon/godot_mcp_tool/commands/params.gd +5 -5
- package/addon/godot_mcp_tool/commands/project/autoload_add_command.gd +20 -20
- package/addon/godot_mcp_tool/commands/project/autoload_remove_command.gd +16 -16
- package/addon/godot_mcp_tool/commands/project/project_info_command.gd +6 -6
- package/addon/godot_mcp_tool/commands/project/project_settings_get_command.gd +10 -10
- package/addon/godot_mcp_tool/commands/project/project_settings_set_command.gd +13 -13
- package/addon/godot_mcp_tool/commands/resource/resource_create_command.gd +16 -16
- package/addon/godot_mcp_tool/commands/resource/resource_get_command.gd +14 -14
- package/addon/godot_mcp_tool/commands/resource/resource_set_properties_command.gd +16 -16
- package/addon/godot_mcp_tool/commands/scene/scene_batch_edit_command.gd +20 -20
- package/addon/godot_mcp_tool/commands/scene/scene_close_command.gd +18 -18
- package/addon/godot_mcp_tool/commands/scene/scene_create_command.gd +23 -23
- package/addon/godot_mcp_tool/commands/scene/scene_get_tree_command.gd +13 -13
- package/addon/godot_mcp_tool/commands/scene/scene_open_command.gd +14 -14
- package/addon/godot_mcp_tool/commands/scene/scene_save_command.gd +12 -12
- package/addon/godot_mcp_tool/commands/scene_edit_command.gd +9 -9
- package/addon/godot_mcp_tool/commands/signal/signal_connect_command.gd +3 -3
- package/addon/godot_mcp_tool/commands/signal/signal_disconnect_command.gd +3 -3
- package/addon/godot_mcp_tool/commands/signal/signal_list_command.gd +25 -25
- package/addon/godot_mcp_tool/commands/unsafe_command.gd +5 -5
- package/addon/godot_mcp_tool/editor/editor_services.gd +72 -72
- package/addon/godot_mcp_tool/editor/editor_viewport_access.gd +3 -3
- package/addon/godot_mcp_tool/editor/first_scan_gate.gd +5 -5
- package/addon/godot_mcp_tool/editor/mcp_dock.gd +3 -3
- package/addon/godot_mcp_tool/editor/responsiveness_controller.gd +2 -2
- package/addon/godot_mcp_tool/editor/script_sync.gd +7 -7
- package/addon/godot_mcp_tool/editor/supported_engine.gd +2 -2
- package/addon/godot_mcp_tool/editor/unsafe_tools_switch.gd +3 -3
- package/addon/godot_mcp_tool/files/file_usages.gd +10 -10
- package/addon/godot_mcp_tool/files/project_paths.gd +8 -8
- package/addon/godot_mcp_tool/input/input_actions.gd +2 -2
- package/addon/godot_mcp_tool/input/input_event_codec.gd +2 -2
- package/addon/godot_mcp_tool/navigation/navigation_bake_report.gd +6 -6
- package/addon/godot_mcp_tool/plugin.cfg +1 -1
- package/addon/godot_mcp_tool/plugin.gd +6 -6
- package/addon/godot_mcp_tool/project/autoload_list.gd +3 -3
- package/addon/godot_mcp_tool/project/autoload_names.gd +2 -2
- package/addon/godot_mcp_tool/project/project_query.gd +35 -35
- package/addon/godot_mcp_tool/resources/resource_factory.gd +17 -17
- package/addon/godot_mcp_tool/resources/resource_files.gd +12 -12
- package/addon/godot_mcp_tool/runtime/debugger_sink.gd +3 -3
- package/addon/godot_mcp_tool/runtime/game_channel.gd +9 -9
- package/addon/godot_mcp_tool/runtime/game_commands/game_bounds_command.gd +14 -14
- package/addon/godot_mcp_tool/runtime/game_commands/game_command_catalog.gd +33 -33
- package/addon/godot_mcp_tool/runtime/game_commands/game_eval_command.gd +14 -14
- package/addon/godot_mcp_tool/runtime/game_commands/game_input_command.gd +24 -24
- package/addon/godot_mcp_tool/runtime/game_commands/game_pause_command.gd +6 -6
- package/addon/godot_mcp_tool/runtime/game_commands/game_performance_command.gd +7 -7
- package/addon/godot_mcp_tool/runtime/game_commands/game_properties_get_command.gd +17 -17
- package/addon/godot_mcp_tool/runtime/game_commands/game_properties_set_command.gd +11 -11
- package/addon/godot_mcp_tool/runtime/game_commands/game_scene.gd +8 -8
- package/addon/godot_mcp_tool/runtime/game_commands/game_screenshot_command.gd +25 -25
- package/addon/godot_mcp_tool/runtime/game_commands/game_step_command.gd +7 -7
- package/addon/godot_mcp_tool/runtime/game_commands/game_tree_command.gd +11 -11
- package/addon/godot_mcp_tool/runtime/game_commands/game_wait_command.gd +23 -23
- package/addon/godot_mcp_tool/runtime/game_request_responder.gd +7 -7
- package/addon/godot_mcp_tool/runtime/game_state.gd +4 -4
- package/addon/godot_mcp_tool/runtime/run_arguments.gd +2 -2
- package/addon/godot_mcp_tool/runtime/runtime_activation.gd +7 -7
- package/addon/godot_mcp_tool/runtime/runtime_agent.gd +30 -30
- package/addon/godot_mcp_tool/runtime/runtime_context.gd +9 -9
- package/addon/godot_mcp_tool/runtime/runtime_debugger.gd +14 -14
- package/addon/godot_mcp_tool/runtime/runtime_logger.gd +4 -4
- package/addon/godot_mcp_tool/runtime/server_connection.gd +7 -7
- package/addon/godot_mcp_tool/scene/change_set.gd +3 -3
- package/addon/godot_mcp_tool/scene/editing_context.gd +19 -19
- package/addon/godot_mcp_tool/scene/edits/group_change.gd +14 -14
- package/addon/godot_mcp_tool/scene/edits/node_addition.gd +12 -12
- package/addon/godot_mcp_tool/scene/edits/node_deletion.gd +9 -9
- package/addon/godot_mcp_tool/scene/edits/node_duplication.gd +16 -16
- package/addon/godot_mcp_tool/scene/edits/node_move.gd +24 -24
- package/addon/godot_mcp_tool/scene/edits/property_change.gd +8 -8
- package/addon/godot_mcp_tool/scene/edits/scene_edit.gd +16 -16
- package/addon/godot_mcp_tool/scene/edits/signal_connection.gd +24 -24
- package/addon/godot_mcp_tool/scene/edits/signal_disconnection.gd +18 -18
- package/addon/godot_mcp_tool/scene/node_factory.gd +16 -16
- package/addon/godot_mcp_tool/scene/node_locator.gd +2 -2
- package/addon/godot_mcp_tool/scene/property_editor.gd +15 -15
- package/addon/godot_mcp_tool/scene/scene_resolver.gd +21 -21
- package/addon/godot_mcp_tool/scene/scene_tree_describer.gd +2 -2
- package/addon/godot_mcp_tool/scene/signal_catalog.gd +9 -9
- package/addon/godot_mcp_tool/scene/undo_history.gd +3 -3
- package/dist/serverInfo.js +1 -1
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
3
|
+
const GodotMcpCommandError := preload("../commands/command_error.gd")
|
|
4
|
+
const GodotMcpIndefiniteArticle := preload("../util/indefinite_article.gd")
|
|
5
|
+
const GodotMcpJsonRpc := preload("../bridge/json_rpc.gd")
|
|
6
|
+
const GodotMcpNodeFactory := preload("../scene/node_factory.gd")
|
|
7
|
+
const GodotMcpNodeLocator := preload("../scene/node_locator.gd")
|
|
8
8
|
|
|
9
9
|
const FORBIDDEN_NAME_CHARACTERS := ["/", ":", ",", "["]
|
|
10
10
|
const LIBRARY_SEPARATOR := "/"
|
|
@@ -14,13 +14,13 @@ var animation_name := ""
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
static func find_player(scene_root: Node, player_path: String) -> Variant:
|
|
17
|
-
var node :=
|
|
17
|
+
var node := GodotMcpNodeLocator.find(scene_root, player_path)
|
|
18
18
|
if node == null:
|
|
19
|
-
return
|
|
19
|
+
return GodotMcpCommandError.new(GodotMcpJsonRpc.NOT_FOUND, "Node \"%s\" not found" % player_path, GodotMcpNodeLocator.not_found_hint(scene_root, player_path))
|
|
20
20
|
if not node is AnimationPlayer:
|
|
21
|
-
return
|
|
22
|
-
|
|
23
|
-
"%s is %s, not an AnimationPlayer" % [player_path,
|
|
21
|
+
return GodotMcpCommandError.new(
|
|
22
|
+
GodotMcpJsonRpc.INVALID_PARAMS,
|
|
23
|
+
"%s is %s, not an AnimationPlayer" % [player_path, GodotMcpIndefiniteArticle.before(GodotMcpNodeFactory.display_class(node))],
|
|
24
24
|
"Add an AnimationPlayer with node_add; its tracks animate the nodes under its parent.",
|
|
25
25
|
)
|
|
26
26
|
return node
|
|
@@ -34,8 +34,8 @@ static func parse(full_name: String) -> Variant:
|
|
|
34
34
|
name = name.substr(reference.library_name.length() + 1)
|
|
35
35
|
reference.animation_name = name
|
|
36
36
|
if name.is_empty() or FORBIDDEN_NAME_CHARACTERS.any(func(character: String) -> bool: return name.contains(character)) or _has_forbidden_character(reference.library_name):
|
|
37
|
-
return
|
|
38
|
-
|
|
37
|
+
return GodotMcpCommandError.new(
|
|
38
|
+
GodotMcpJsonRpc.INVALID_PARAMS,
|
|
39
39
|
"\"%s\" is not a valid animation name" % full_name,
|
|
40
40
|
"Animation names cannot be empty or contain / : , [; write \"library/name\" for an animation of another library than the default one.",
|
|
41
41
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const GodotMcpAnimationVocabulary := preload("animation_vocabulary.gd")
|
|
4
|
+
const GodotMcpVariantCodec := preload("../codec/variant_codec.gd")
|
|
5
5
|
|
|
6
6
|
const MAXIMUM_KEYS_SHOWN := 20
|
|
7
7
|
const DECIMAL_SCALE := 1000.0
|
|
@@ -15,7 +15,7 @@ static func list(player: AnimationPlayer) -> Array:
|
|
|
15
15
|
return animations
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
static func detail(animation: Animation, name: String, codec:
|
|
18
|
+
static func detail(animation: Animation, name: String, codec: GodotMcpVariantCodec) -> Dictionary:
|
|
19
19
|
var tracks: Array = []
|
|
20
20
|
for index in animation.get_track_count():
|
|
21
21
|
tracks.append(_track(animation, index, codec))
|
|
@@ -23,18 +23,18 @@ static func detail(animation: Animation, name: String, codec: VariantCodec) -> D
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
static func loop_name(animation: Animation) -> String:
|
|
26
|
-
return
|
|
26
|
+
return GodotMcpAnimationVocabulary.name_of(GodotMcpAnimationVocabulary.LOOP_MODES, animation.loop_mode)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
static func _track(animation: Animation, index: int, codec:
|
|
29
|
+
static func _track(animation: Animation, index: int, codec: GodotMcpVariantCodec) -> Dictionary:
|
|
30
30
|
var type := animation.track_get_type(index)
|
|
31
31
|
var track := {
|
|
32
32
|
"path": str(animation.track_get_path(index)),
|
|
33
|
-
"type":
|
|
34
|
-
"interpolation":
|
|
33
|
+
"type": GodotMcpAnimationVocabulary.name_of(GodotMcpAnimationVocabulary.TRACK_TYPES, type),
|
|
34
|
+
"interpolation": GodotMcpAnimationVocabulary.name_of(GodotMcpAnimationVocabulary.INTERPOLATIONS, animation.track_get_interpolation_type(index)),
|
|
35
35
|
}
|
|
36
36
|
if type == Animation.TYPE_VALUE:
|
|
37
|
-
track["update"] =
|
|
37
|
+
track["update"] = GodotMcpAnimationVocabulary.name_of(GodotMcpAnimationVocabulary.UPDATE_MODES, animation.value_track_get_update_mode(index))
|
|
38
38
|
var key_count := animation.track_get_key_count(index)
|
|
39
39
|
var keys: Array = []
|
|
40
40
|
for key_index in mini(key_count, MAXIMUM_KEYS_SHOWN):
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
3
|
+
const GodotMcpAnimationVocabulary := preload("animation_vocabulary.gd")
|
|
4
|
+
const GodotMcpNodeLocator := preload("../scene/node_locator.gd")
|
|
5
|
+
const GodotMcpSimilarNames := preload("../util/similar_names.gd")
|
|
6
|
+
const GodotMcpVariantCodec := preload("../codec/variant_codec.gd")
|
|
7
7
|
|
|
8
8
|
const STEPPED_TYPES := [TYPE_BOOL, TYPE_STRING, TYPE_STRING_NAME, TYPE_NODE_PATH, TYPE_OBJECT]
|
|
9
9
|
const DEFAULT_LENGTH := 1.0
|
|
10
10
|
const DECIMAL_SCALE := 1000.0
|
|
11
11
|
const RESET_LENGTH := 0.001
|
|
12
12
|
|
|
13
|
-
var _codec:
|
|
13
|
+
var _codec: GodotMcpVariantCodec
|
|
14
14
|
var _root: Node
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
func _init(codec:
|
|
17
|
+
func _init(codec: GodotMcpVariantCodec, root: Node) -> void:
|
|
18
18
|
_codec = codec
|
|
19
19
|
_root = root
|
|
20
20
|
|
|
@@ -88,8 +88,8 @@ func describe(track: Dictionary) -> Dictionary:
|
|
|
88
88
|
return {
|
|
89
89
|
"path": track["path"],
|
|
90
90
|
"keys": keys.size(),
|
|
91
|
-
"interpolation":
|
|
92
|
-
"update":
|
|
91
|
+
"interpolation": GodotMcpAnimationVocabulary.name_of(GodotMcpAnimationVocabulary.INTERPOLATIONS, track["interpolation"]),
|
|
92
|
+
"update": GodotMcpAnimationVocabulary.name_of(GodotMcpAnimationVocabulary.UPDATE_MODES, track["update"]),
|
|
93
93
|
"from": _codec.encode(keys.front()["value"]),
|
|
94
94
|
"to": _codec.encode(keys.back()["value"]),
|
|
95
95
|
}
|
|
@@ -105,15 +105,15 @@ func _plan_track(description: Variant) -> Variant:
|
|
|
105
105
|
return "track paths are Node:property relative to the parent of the AnimationPlayer, such as \"Door:position\" or \".:visible\""
|
|
106
106
|
var target := _root.get_node_or_null(NodePath(node_part))
|
|
107
107
|
if target == null:
|
|
108
|
-
return "there is no node %s under %s. %s" % [node_part, _root.name,
|
|
108
|
+
return "there is no node %s under %s. %s" % [node_part, _root.name, GodotMcpNodeLocator.not_found_hint(_root, node_part)]
|
|
109
109
|
var property: Variant = _property_of(target, property_path)
|
|
110
110
|
if property is String:
|
|
111
111
|
return property
|
|
112
|
-
var interpolation: Variant = _named_value(description, "interpolation",
|
|
112
|
+
var interpolation: Variant = _named_value(description, "interpolation", GodotMcpAnimationVocabulary.INTERPOLATIONS, "linear")
|
|
113
113
|
if interpolation is String:
|
|
114
114
|
return interpolation
|
|
115
115
|
var default_update := "discrete" if _is_stepped(property) else "continuous"
|
|
116
|
-
var update: Variant = _named_value(description, "update",
|
|
116
|
+
var update: Variant = _named_value(description, "update", GodotMcpAnimationVocabulary.UPDATE_MODES, default_update)
|
|
117
117
|
if update is String:
|
|
118
118
|
return update
|
|
119
119
|
var keys: Variant = _decode_keys(description.get("keys", []), property, target, property_path)
|
|
@@ -128,7 +128,7 @@ func _property_of(target: Node, property_path: String) -> Variant:
|
|
|
128
128
|
for property in target.get_property_list():
|
|
129
129
|
properties[str(property["name"])] = property
|
|
130
130
|
if not properties.has(property_name):
|
|
131
|
-
var similar :=
|
|
131
|
+
var similar := GodotMcpSimilarNames.hint(property_name, properties.keys())
|
|
132
132
|
return "%s has no property \"%s\"%s" % [target.name, property_name, "" if similar.is_empty() else ". " + similar]
|
|
133
133
|
if property_path == property_name:
|
|
134
134
|
return properties[property_name]
|
|
@@ -157,9 +157,9 @@ func _decode_keys(key_descriptions: Variant, property: Dictionary, target: Node,
|
|
|
157
157
|
|
|
158
158
|
static func _named_value(description: Dictionary, key: String, names: Dictionary, default_name: String) -> Variant:
|
|
159
159
|
var name: Variant = description.get(key, default_name)
|
|
160
|
-
var value: Variant =
|
|
160
|
+
var value: Variant = GodotMcpAnimationVocabulary.value_of(names, name)
|
|
161
161
|
if value == null:
|
|
162
|
-
return "unknown %s \"%s\"; use one of: %s" % [key, name,
|
|
162
|
+
return "unknown %s \"%s\"; use one of: %s" % [key, name, GodotMcpAnimationVocabulary.listed(names)]
|
|
163
163
|
return value
|
|
164
164
|
|
|
165
165
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const GodotMcpSimilarNames := preload("../util/similar_names.gd")
|
|
4
4
|
|
|
5
5
|
const START := "Start"
|
|
6
6
|
const END := "End"
|
|
@@ -74,7 +74,7 @@ func _plan_state(description: Variant) -> void:
|
|
|
74
74
|
reason = "this state is given twice"
|
|
75
75
|
elif not _player.has_animation(animation):
|
|
76
76
|
var known := Array(_player.get_animation_list()).map(func(animation_name: StringName) -> String: return str(animation_name))
|
|
77
|
-
var similar :=
|
|
77
|
+
var similar := GodotMcpSimilarNames.hint(animation, known)
|
|
78
78
|
reason = "%s has no animation \"%s\"%s" % [_player.name, animation, "" if similar.is_empty() else ". " + similar]
|
|
79
79
|
if not reason.is_empty():
|
|
80
80
|
rejected.append({"item": name, "reason": reason})
|
|
@@ -105,7 +105,7 @@ func _transition(description: Dictionary, from: String, to: String) -> Variant:
|
|
|
105
105
|
return "a state cannot have a transition to itself"
|
|
106
106
|
for state_name in [from, to]:
|
|
107
107
|
if not _is_known_state(state_name):
|
|
108
|
-
return "there is no state %s. %s" % [state_name,
|
|
108
|
+
return "there is no state %s. %s" % [state_name, GodotMcpSimilarNames.hint(state_name, _state_names())]
|
|
109
109
|
if transitions.any(func(transition: Dictionary) -> bool: return transition["from"] == from and transition["to"] == to):
|
|
110
110
|
return "this transition is given twice"
|
|
111
111
|
var planned := {"from": from, "to": to}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const GodotMcpNodeLocator := preload("../scene/node_locator.gd")
|
|
4
|
+
const GodotMcpStateMachinePlan := preload("state_machine_plan.gd")
|
|
5
5
|
|
|
6
6
|
const DECIMAL_SCALE := 1000.0
|
|
7
7
|
const DEFAULT_PRIORITY := 1
|
|
@@ -11,8 +11,8 @@ static func describe(tree: AnimationTree, scene_root: Node, scene_path: String)
|
|
|
11
11
|
var player := tree.get_node_or_null(tree.anim_player) if not tree.anim_player.is_empty() else null
|
|
12
12
|
var report := {
|
|
13
13
|
"scene": scene_path,
|
|
14
|
-
"tree":
|
|
15
|
-
"player":
|
|
14
|
+
"tree": GodotMcpNodeLocator.path_of(scene_root, tree),
|
|
15
|
+
"player": GodotMcpNodeLocator.path_of(scene_root, player) if player != null else "",
|
|
16
16
|
"active": tree.active,
|
|
17
17
|
"expression_base": str(tree.advance_expression_base_node),
|
|
18
18
|
"root": tree.tree_root.get_class() if tree.tree_root != null else "",
|
|
@@ -26,7 +26,7 @@ static func describe(tree: AnimationTree, scene_root: Node, scene_path: String)
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
static func _states(machine: AnimationNodeStateMachine) -> Array:
|
|
29
|
-
var names: Array = Array(machine.get_node_list()).map(func(name: StringName) -> String: return str(name)).filter(func(name: String) -> bool: return name !=
|
|
29
|
+
var names: Array = Array(machine.get_node_list()).map(func(name: StringName) -> String: return str(name)).filter(func(name: String) -> bool: return name != GodotMcpStateMachinePlan.START and name != GodotMcpStateMachinePlan.END)
|
|
30
30
|
names.sort()
|
|
31
31
|
return names.map(func(name: String) -> Dictionary: return _state(name, machine.get_node(name)))
|
|
32
32
|
|
|
@@ -44,8 +44,8 @@ static func _transitions(machine: AnimationNodeStateMachine) -> Array:
|
|
|
44
44
|
var entry := {
|
|
45
45
|
"from": str(machine.get_transition_from(index)),
|
|
46
46
|
"to": str(machine.get_transition_to(index)),
|
|
47
|
-
"advance": _name_of(
|
|
48
|
-
"switch": _name_of(
|
|
47
|
+
"advance": _name_of(GodotMcpStateMachinePlan.ADVANCE_MODES, transition.advance_mode),
|
|
48
|
+
"switch": _name_of(GodotMcpStateMachinePlan.SWITCH_MODES, transition.switch_mode),
|
|
49
49
|
"xfade": roundf(transition.xfade_time * DECIMAL_SCALE) / DECIMAL_SCALE,
|
|
50
50
|
}
|
|
51
51
|
if not str(transition.advance_condition).is_empty():
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const GodotMcpEnumOptions := preload("../codec/enum_options.gd")
|
|
4
|
+
const GodotMcpVariantCodec := preload("../codec/variant_codec.gd")
|
|
5
5
|
|
|
6
6
|
const OPTION_ENUMS := {
|
|
7
7
|
"meshes/light_baking": "Disabled,Static,Static Lightmaps,Dynamic",
|
|
@@ -53,13 +53,13 @@ static func node_option_names() -> Array:
|
|
|
53
53
|
return NODE_OPTIONS.keys()
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
static func describe_option(name: String, value: Variant, codec:
|
|
56
|
+
static func describe_option(name: String, value: Variant, codec: GodotMcpVariantCodec) -> Variant:
|
|
57
57
|
if OPTION_ENUMS.has(name) and value is int:
|
|
58
58
|
return _enum_name(OPTION_ENUMS[name], value, codec)
|
|
59
59
|
return codec.encode(value)
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
static func describe_node_option(name: String, value: Variant, codec:
|
|
62
|
+
static func describe_node_option(name: String, value: Variant, codec: GodotMcpVariantCodec) -> Variant:
|
|
63
63
|
var option: Dictionary = NODE_OPTIONS.get(name, {})
|
|
64
64
|
if option.get("hint") == PROPERTY_HINT_ENUM and value is int:
|
|
65
65
|
return _enum_name(option["hint_string"], value, codec)
|
|
@@ -70,6 +70,6 @@ static func _property(name: String, option: Dictionary) -> Dictionary:
|
|
|
70
70
|
return {"name": name, "type": option["type"], "hint": option.get("hint", PROPERTY_HINT_NONE), "hint_string": option.get("hint_string", ""), "usage": PROPERTY_USAGE_DEFAULT}
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
static func _enum_name(hint_string: String, value: int, codec:
|
|
74
|
-
var option_name :=
|
|
73
|
+
static func _enum_name(hint_string: String, value: int, codec: GodotMcpVariantCodec) -> Variant:
|
|
74
|
+
var option_name := GodotMcpEnumOptions.name_of(GodotMcpEnumOptions.parse(hint_string), value)
|
|
75
75
|
return option_name if not option_name.is_empty() else codec.encode(value)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const GodotMcpCommandError := preload("../commands/command_error.gd")
|
|
4
|
+
const GodotMcpJsonRpc := preload("../bridge/json_rpc.gd")
|
|
5
5
|
|
|
6
6
|
const IMPORT_SUFFIX := ".import"
|
|
7
7
|
const REMAP_SECTION := "remap"
|
|
@@ -17,12 +17,12 @@ var _file := ConfigFile.new()
|
|
|
17
17
|
static func open(path: String) -> Variant:
|
|
18
18
|
var inside_project := path.begins_with("res://") and not ".." in path.trim_prefix("res://").split("/")
|
|
19
19
|
if not inside_project or path.contains("::"):
|
|
20
|
-
return
|
|
20
|
+
return GodotMcpCommandError.new(GodotMcpJsonRpc.INVALID_PARAMS, "\"%s\" is not a file in the project" % path, "Asset paths look like res://models/character.glb.")
|
|
21
21
|
if not FileAccess.file_exists(path):
|
|
22
|
-
return
|
|
22
|
+
return GodotMcpCommandError.new(GodotMcpJsonRpc.NOT_FOUND, "%s not found" % path, "Check the path, or let the editor import new files with editor_rescan.")
|
|
23
23
|
if not FileAccess.file_exists(path + IMPORT_SUFFIX):
|
|
24
|
-
return
|
|
25
|
-
|
|
24
|
+
return GodotMcpCommandError.new(
|
|
25
|
+
GodotMcpJsonRpc.INVALID_PARAMS,
|
|
26
26
|
"%s is not imported, so it has no import settings" % path,
|
|
27
27
|
"Godot imports files such as .glb, .gltf, .fbx, .blend, .png and .wav; scenes and .tres resources are changed with the scene_, node_ and resource_ tools.",
|
|
28
28
|
)
|
|
@@ -30,7 +30,7 @@ static func open(path: String) -> Variant:
|
|
|
30
30
|
settings.asset_path = path
|
|
31
31
|
var load_error := settings._file.load(path + IMPORT_SUFFIX)
|
|
32
32
|
if load_error != OK:
|
|
33
|
-
return
|
|
33
|
+
return GodotMcpCommandError.new(GodotMcpJsonRpc.OPERATION_FAILED, "%s%s could not be read: %s" % [path, IMPORT_SUFFIX, error_string(load_error)])
|
|
34
34
|
return settings
|
|
35
35
|
|
|
36
36
|
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
3
|
+
const GodotMcpBounds := preload("../capture/bounds.gd")
|
|
4
|
+
const GodotMcpNodeFactory := preload("../scene/node_factory.gd")
|
|
5
|
+
const GodotMcpSpatialTransforms := preload("../scene/spatial_transforms.gd")
|
|
6
|
+
const GodotMcpVariantCodec := preload("../codec/variant_codec.gd")
|
|
7
7
|
|
|
8
8
|
const DECIMAL_SCALE := 1000.0
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
static func describe(scene_path: String, codec:
|
|
11
|
+
static func describe(scene_path: String, codec: GodotMcpVariantCodec) -> Variant:
|
|
12
12
|
var scene := ResourceLoader.load(scene_path, "", ResourceLoader.CACHE_MODE_IGNORE) as PackedScene
|
|
13
13
|
if scene == null:
|
|
14
14
|
return null
|
|
15
15
|
var root := scene.instantiate()
|
|
16
16
|
if root == null:
|
|
17
17
|
return null
|
|
18
|
-
var bounds :=
|
|
18
|
+
var bounds := GodotMcpBounds.measure(root)["aabb"] as AABB
|
|
19
19
|
var report := {
|
|
20
20
|
"size": codec.encode(_rounded_vector(bounds.size)),
|
|
21
21
|
"position": codec.encode(_rounded_vector(bounds.position)),
|
|
@@ -27,11 +27,11 @@ static func describe(scene_path: String, codec: VariantCodec) -> Variant:
|
|
|
27
27
|
return report
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
static func _describe_subtree(node: Node, root: Node, report: Dictionary, codec:
|
|
30
|
+
static func _describe_subtree(node: Node, root: Node, report: Dictionary, codec: GodotMcpVariantCodec) -> void:
|
|
31
31
|
var path := str(root.get_path_to(node))
|
|
32
|
-
var described := {"path": path, "type":
|
|
32
|
+
var described := {"path": path, "type": GodotMcpNodeFactory.display_class(node)}
|
|
33
33
|
if node is MeshInstance3D:
|
|
34
|
-
described["size"] = codec.encode(_rounded_vector((
|
|
34
|
+
described["size"] = codec.encode(_rounded_vector((GodotMcpBounds.measure(node)["aabb"] as AABB).size))
|
|
35
35
|
(report["nodes"] as Array).append(described)
|
|
36
36
|
if node is CollisionShape3D and (node as CollisionShape3D).shape != null:
|
|
37
37
|
var shape := (node as CollisionShape3D).shape
|
|
@@ -39,13 +39,13 @@ static func _describe_subtree(node: Node, root: Node, report: Dictionary, codec:
|
|
|
39
39
|
"path": path,
|
|
40
40
|
"shape": shape.get_class(),
|
|
41
41
|
"dimensions": _dimensions_of(shape, codec),
|
|
42
|
-
"position": codec.encode(_rounded_vector(
|
|
42
|
+
"position": codec.encode(_rounded_vector(GodotMcpSpatialTransforms.global_of(node).origin)),
|
|
43
43
|
})
|
|
44
44
|
for child in node.get_children():
|
|
45
45
|
_describe_subtree(child, root, report, codec)
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
static func _dimensions_of(shape: Shape3D, codec:
|
|
48
|
+
static func _dimensions_of(shape: Shape3D, codec: GodotMcpVariantCodec) -> Array:
|
|
49
49
|
if shape is BoxShape3D:
|
|
50
50
|
return [_dimension("size", codec.encode(_rounded_vector((shape as BoxShape3D).size)))]
|
|
51
51
|
if shape is SphereShape3D:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const GodotMcpCommandError := preload("../commands/command_error.gd")
|
|
4
|
+
const GodotMcpJsonRpc := preload("json_rpc.gd")
|
|
5
5
|
|
|
6
6
|
var _expected_token: String
|
|
7
7
|
var _protocol: int
|
|
@@ -12,17 +12,17 @@ func _init(expected_token: String, protocol: int) -> void:
|
|
|
12
12
|
_protocol = protocol
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
func verify(params: Dictionary) ->
|
|
15
|
+
func verify(params: Dictionary) -> GodotMcpCommandError:
|
|
16
16
|
var token: Variant = params.get("token")
|
|
17
17
|
if not token is String or not _tokens_match(token, _expected_token):
|
|
18
|
-
return
|
|
19
|
-
|
|
18
|
+
return GodotMcpCommandError.new(
|
|
19
|
+
GodotMcpJsonRpc.UNAUTHORIZED,
|
|
20
20
|
"Invalid token",
|
|
21
21
|
"Restart the MCP server so it reads the current discovery file of this editor.")
|
|
22
22
|
var protocol: Variant = params.get("protocol")
|
|
23
23
|
if not (protocol is int or protocol is float) or int(protocol) != _protocol:
|
|
24
|
-
return
|
|
25
|
-
|
|
24
|
+
return GodotMcpCommandError.new(
|
|
25
|
+
GodotMcpJsonRpc.PROTOCOL_MISMATCH,
|
|
26
26
|
"Unsupported bridge protocol %s; this addon speaks protocol %d" % [str(protocol), _protocol],
|
|
27
27
|
"Install matching versions of the MCP server and the Godot addon.")
|
|
28
28
|
return null
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
3
|
+
const GodotMcpBridgeSession := preload("bridge_session.gd")
|
|
4
|
+
const GodotMcpJsonRpc := preload("json_rpc.gd")
|
|
5
|
+
const GodotMcpNdjsonBuffer := preload("ndjson_buffer.gd")
|
|
6
6
|
|
|
7
7
|
var is_closed := false
|
|
8
|
-
var session:
|
|
8
|
+
var session: GodotMcpBridgeSession
|
|
9
9
|
|
|
10
10
|
var _peer: StreamPeerTCP
|
|
11
|
-
var _incoming :=
|
|
11
|
+
var _incoming := GodotMcpNdjsonBuffer.new()
|
|
12
12
|
var _outgoing := PackedByteArray()
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
func _init(peer: StreamPeerTCP, bridge_session:
|
|
15
|
+
func _init(peer: StreamPeerTCP, bridge_session: GodotMcpBridgeSession) -> void:
|
|
16
16
|
_peer = peer
|
|
17
17
|
session = bridge_session
|
|
18
18
|
_peer.set_no_delay(true)
|
|
@@ -37,7 +37,7 @@ func poll() -> int:
|
|
|
37
37
|
|
|
38
38
|
func send(message: Dictionary) -> void:
|
|
39
39
|
if not is_closed:
|
|
40
|
-
_outgoing.append_array(
|
|
40
|
+
_outgoing.append_array(GodotMcpJsonRpc.encode(message))
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
func close() -> void:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const GodotMcpAddonInfo := preload("../addon_info.gd")
|
|
4
4
|
|
|
5
5
|
const TIMEOUT_MSEC := 1000
|
|
6
6
|
const POLL_INTERVAL_MSEC := 5
|
|
@@ -8,7 +8,7 @@ const POLL_INTERVAL_MSEC := 5
|
|
|
8
8
|
|
|
9
9
|
static func is_listening(port: int) -> bool:
|
|
10
10
|
var connection := StreamPeerTCP.new()
|
|
11
|
-
if connection.connect_to_host(
|
|
11
|
+
if connection.connect_to_host(GodotMcpAddonInfo.LOOPBACK_ADDRESS, port) != OK:
|
|
12
12
|
return false
|
|
13
13
|
_poll_until_connected_or_refused(connection)
|
|
14
14
|
var listening := _stays_connected(connection)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const GodotMcpBridgeConnection := preload("bridge_connection.gd")
|
|
4
4
|
|
|
5
5
|
var last_activity_msec := -1
|
|
6
6
|
|
|
7
7
|
var _tcp_server := TCPServer.new()
|
|
8
|
-
var _connections: Array[
|
|
8
|
+
var _connections: Array[GodotMcpBridgeConnection] = []
|
|
9
9
|
var _create_session: Callable
|
|
10
10
|
|
|
11
11
|
|
|
@@ -26,18 +26,18 @@ func connection_count() -> int:
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
func authenticated_count() -> int:
|
|
29
|
-
return _connections.filter(func(connection:
|
|
29
|
+
return _connections.filter(func(connection: GodotMcpBridgeConnection) -> bool: return connection.session.is_authenticated).size()
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
func poll() -> void:
|
|
33
33
|
while _tcp_server.is_connection_available():
|
|
34
34
|
var peer := _tcp_server.take_connection()
|
|
35
35
|
if peer != null:
|
|
36
|
-
_connections.append(
|
|
36
|
+
_connections.append(GodotMcpBridgeConnection.new(peer, _create_session.call()))
|
|
37
37
|
for connection in _connections:
|
|
38
38
|
if connection.poll() > 0:
|
|
39
39
|
last_activity_msec = Time.get_ticks_msec()
|
|
40
|
-
_connections = _connections.filter(func(connection:
|
|
40
|
+
_connections = _connections.filter(func(connection: GodotMcpBridgeConnection) -> bool: return not connection.is_closed)
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
func broadcast(message: Dictionary) -> void:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
3
|
+
const GodotMcpAuthenticator := preload("authenticator.gd")
|
|
4
|
+
const GodotMcpCommandError := preload("../commands/command_error.gd")
|
|
5
|
+
const GodotMcpCommandRouter := preload("../commands/command_router.gd")
|
|
6
|
+
const GodotMcpJsonRpc := preload("json_rpc.gd")
|
|
7
7
|
|
|
8
8
|
var is_authenticated := false
|
|
9
9
|
var should_close := false
|
|
@@ -11,8 +11,8 @@ var should_close := false
|
|
|
11
11
|
const STATUS_NOTIFICATION := "log.status"
|
|
12
12
|
const KEEPALIVE_METHODS := ["ping"]
|
|
13
13
|
|
|
14
|
-
var _authenticator:
|
|
15
|
-
var _router:
|
|
14
|
+
var _authenticator: GodotMcpAuthenticator
|
|
15
|
+
var _router: GodotMcpCommandRouter
|
|
16
16
|
var _build_hello_payload: Callable
|
|
17
17
|
var _read_status: Callable
|
|
18
18
|
var _wait_for_first_scan: Callable
|
|
@@ -22,8 +22,8 @@ var _notifications: Array = []
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
func _init(
|
|
25
|
-
authenticator:
|
|
26
|
-
router:
|
|
25
|
+
authenticator: GodotMcpAuthenticator,
|
|
26
|
+
router: GodotMcpCommandRouter,
|
|
27
27
|
build_hello_payload: Callable,
|
|
28
28
|
read_status: Callable = Callable(),
|
|
29
29
|
wait_for_first_scan: Callable = Callable(),
|
|
@@ -47,20 +47,20 @@ func handle_line(line: String) -> Dictionary:
|
|
|
47
47
|
var json := JSON.new()
|
|
48
48
|
if json.parse(line) != OK or not json.data is Dictionary:
|
|
49
49
|
should_close = true
|
|
50
|
-
return
|
|
50
|
+
return GodotMcpJsonRpc.failure(null, GodotMcpJsonRpc.PARSE_ERROR, "Expected one JSON-RPC request object per line")
|
|
51
51
|
var request: Dictionary = json.data
|
|
52
52
|
var request_id: Variant = _normalize_request_id(request.get("id"))
|
|
53
53
|
var method: Variant = request.get("method")
|
|
54
54
|
if not method is String:
|
|
55
|
-
return
|
|
55
|
+
return GodotMcpJsonRpc.failure(request_id, GodotMcpJsonRpc.INVALID_REQUEST, "The request has no method")
|
|
56
56
|
var params: Variant = request.get("params", {})
|
|
57
57
|
if not params is Dictionary:
|
|
58
|
-
return
|
|
58
|
+
return GodotMcpJsonRpc.failure(request_id, GodotMcpJsonRpc.INVALID_PARAMS, "The params of %s must be an object" % method)
|
|
59
59
|
if method == "hello":
|
|
60
60
|
return _answer_hello(request_id, params)
|
|
61
61
|
if not is_authenticated:
|
|
62
62
|
should_close = true
|
|
63
|
-
return
|
|
63
|
+
return GodotMcpJsonRpc.failure(request_id, GodotMcpJsonRpc.UNAUTHORIZED, "Send hello with the token before %s" % method)
|
|
64
64
|
return await _run_command(request_id, method, params)
|
|
65
65
|
|
|
66
66
|
|
|
@@ -68,10 +68,10 @@ func _answer_hello(request_id: Variant, params: Dictionary) -> Dictionary:
|
|
|
68
68
|
var rejection := _authenticator.verify(params)
|
|
69
69
|
if rejection != null:
|
|
70
70
|
should_close = true
|
|
71
|
-
return
|
|
71
|
+
return GodotMcpJsonRpc.failure(request_id, rejection.code, rejection.message, rejection.hint)
|
|
72
72
|
is_authenticated = true
|
|
73
73
|
_queue_status_if_changed()
|
|
74
|
-
return
|
|
74
|
+
return GodotMcpJsonRpc.success(request_id, _build_hello_payload.call())
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
func _run_command(request_id: Variant, method: String, params: Dictionary) -> Dictionary:
|
|
@@ -86,22 +86,22 @@ func _run_command(request_id: Variant, method: String, params: Dictionary) -> Di
|
|
|
86
86
|
func _answer_command(request_id: Variant, method: String, params: Dictionary) -> Dictionary:
|
|
87
87
|
var command := _router.find(method)
|
|
88
88
|
if command == null:
|
|
89
|
-
return
|
|
89
|
+
return GodotMcpJsonRpc.failure(
|
|
90
90
|
request_id,
|
|
91
|
-
|
|
91
|
+
GodotMcpJsonRpc.METHOD_NOT_FOUND,
|
|
92
92
|
"Unknown method %s" % method,
|
|
93
93
|
"Available methods: %s" % ", ".join(_router.methods()))
|
|
94
94
|
if not await _is_editor_ready_for(method):
|
|
95
|
-
return
|
|
95
|
+
return GodotMcpJsonRpc.failure(
|
|
96
96
|
request_id,
|
|
97
|
-
|
|
97
|
+
GodotMcpJsonRpc.EDITOR_BUSY,
|
|
98
98
|
"The editor is still scanning and importing the project, as it does after it starts",
|
|
99
99
|
"Call again in a few seconds: the first import of a large project takes a while.")
|
|
100
100
|
var outcome: Variant = await command.execute(params)
|
|
101
101
|
_queue_status_if_changed()
|
|
102
|
-
if outcome is
|
|
103
|
-
return
|
|
104
|
-
return
|
|
102
|
+
if outcome is GodotMcpCommandError:
|
|
103
|
+
return GodotMcpJsonRpc.failure(request_id, outcome.code, outcome.message, outcome.hint)
|
|
104
|
+
return GodotMcpJsonRpc.success(request_id, outcome)
|
|
105
105
|
|
|
106
106
|
|
|
107
107
|
func _is_editor_ready_for(method: String) -> bool:
|
|
@@ -117,7 +117,7 @@ func _queue_status_if_changed() -> void:
|
|
|
117
117
|
if status == _last_sent_status:
|
|
118
118
|
return
|
|
119
119
|
_last_sent_status = status
|
|
120
|
-
_notifications.append(
|
|
120
|
+
_notifications.append(GodotMcpJsonRpc.make_notification(STATUS_NOTIFICATION, status))
|
|
121
121
|
|
|
122
122
|
|
|
123
123
|
static func _normalize_request_id(request_id: Variant) -> Variant:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const GodotMcpBridgeProbe := preload("bridge_probe.gd")
|
|
4
4
|
|
|
5
5
|
const OWNER_ONLY_PERMISSIONS := FileAccess.UNIX_READ_OWNER | FileAccess.UNIX_WRITE_OWNER
|
|
6
6
|
const NO_OWNER := -1
|
|
@@ -64,4 +64,4 @@ func _is_served_by_another_editor(current: Dictionary, record: Dictionary) -> bo
|
|
|
64
64
|
var current_port := int(current["port"])
|
|
65
65
|
if int(current.get("pid", NO_OWNER)) == int(record.get("pid", NO_OWNER)) or current_port == int(record.get("port", 0)):
|
|
66
66
|
return false
|
|
67
|
-
return
|
|
67
|
+
return GodotMcpBridgeProbe.is_listening(current_port)
|