66 lines
2.9 KiB
GDScript
66 lines
2.9 KiB
GDScript
@tool
|
||
extends RefCounted
|
||
## file.* command handlers — generic file deletion for any res:// path.
|
||
|
||
const Modules := preload("res://addons/godot_mcp_toolkit/core/modules.gd")
|
||
const FileGuard = Modules.FileGuard
|
||
const Helpers = Modules.CommandHelpers
|
||
|
||
const _TAB_CLOSE_NOISE_HINT := "Closing a non-active scene tab may produce a _set_main_scene_state error in the editor console. This is benign Godot engine noise — safe to ignore."
|
||
|
||
|
||
static func register(registry: MCPToolkitCommandRegistry, _server: Node) -> void:
|
||
registry.add("file.delete", func(parameters: Dictionary) -> Dictionary:
|
||
return await _cmd_file_delete(parameters)
|
||
, MCPToolkitCommandOptions.new().mark_scene_independent())
|
||
|
||
|
||
# -- Commands -----------------------------------------------------------------
|
||
|
||
|
||
static func _cmd_file_delete(parameters: Dictionary) -> Dictionary:
|
||
var file_path := str(parameters.get("file_path", ""))
|
||
if file_path.is_empty():
|
||
return MCPToolkitError.fail("INVALID_PARAMS", "missing file_path")
|
||
var guard := FileGuard.resolve_safe(file_path)
|
||
if guard["error"] != null:
|
||
return MCPToolkitError.fail("PATH_DENIED", str(guard["reason"]))
|
||
if file_path.begins_with("res://addons/godot_mcp_toolkit/"):
|
||
return MCPToolkitError.fail("PATH_DENIED",
|
||
"cannot delete files inside the MCP toolkit plugin directory")
|
||
if not FileAccess.file_exists(file_path):
|
||
return MCPToolkitError.fail("NOT_FOUND", "file not found: %s" % file_path, MCPToolkitError.HINT_FILE_PATH)
|
||
|
||
# For scene files (.tscn/.scn), attempt to close the editor tab first.
|
||
var tab_closed := false
|
||
var tab_result := {}
|
||
var warnings: Array[String] = []
|
||
var ext := file_path.get_extension().to_lower()
|
||
var is_scene := (ext == "tscn" or ext == "scn")
|
||
|
||
if is_scene:
|
||
tab_result = await Helpers.close_scene_tab_safe(file_path)
|
||
tab_closed = tab_result.get("closed", false)
|
||
if not tab_closed:
|
||
var reason := str(tab_result.get("reason", ""))
|
||
if reason == "no_api":
|
||
# 4.2–4.4: no close API. Block active-scene deletion (Ctrl+S
|
||
# would silently recreate the file). Non-active tabs proceed
|
||
# with a phantom warning.
|
||
var edited_root := Helpers.get_edited_root()
|
||
if edited_root != null and edited_root.scene_file_path == file_path:
|
||
return MCPToolkitError.fail("EDITED_SCENE",
|
||
"cannot delete the currently-edited scene %s on Godot 4.2-4.4 (no programmatic tab-close API); close the scene tab manually, then retry file_delete" % file_path)
|
||
warnings.append(
|
||
"phantom tab: scene tab for %s remains open; Godot 4.2-4.4 has no API to close tabs — it will vanish on editor restart or manual close" % file_path)
|
||
|
||
var delete_result: Dictionary = await Helpers.delete_res_file_and_deindex(
|
||
file_path, [".uid", ".import"])
|
||
if is_scene:
|
||
delete_result["tab_closed"] = tab_closed
|
||
if tab_closed and tab_result.get("switched", false):
|
||
delete_result["hint"] = _TAB_CLOSE_NOISE_HINT
|
||
if not warnings.is_empty():
|
||
delete_result["warnings"] = warnings
|
||
return delete_result
|