Stealth Cymbal #3
@@ -14,9 +14,18 @@ const BUSY_TIMEOUT: float = 30.0
|
|||||||
var _key_map: Dictionary
|
var _key_map: Dictionary
|
||||||
var _held_keys: Dictionary = {}
|
var _held_keys: Dictionary = {}
|
||||||
|
|
||||||
|
var _debugger_safe: bool = false
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
# Ensure MCP server keeps processing even when game is paused
|
|
||||||
process_mode = Node.PROCESS_MODE_ALWAYS
|
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||||
|
if EngineDebugger.is_active():
|
||||||
|
EngineDebugger.send_message("core:set_skip_breakpoints", [true])
|
||||||
|
EngineDebugger.send_message("core:set_ignore_error_breaks", [true])
|
||||||
|
_debugger_safe = EngineDebugger.is_skipping_breakpoints()
|
||||||
|
if _debugger_safe:
|
||||||
|
print("McpInteractionServer: Debugger breakpoints safely disabled")
|
||||||
|
else:
|
||||||
|
push_warning("McpInteractionServer: Could not disable debugger breakpoints (LocalDebugger?). Eval with invalid code may hang the game.")
|
||||||
_init_key_map()
|
_init_key_map()
|
||||||
_server = TCPServer.new()
|
_server = TCPServer.new()
|
||||||
var err: int = _server.listen(PORT, "127.0.0.1")
|
var err: int = _server.listen(PORT, "127.0.0.1")
|
||||||
@@ -544,17 +553,18 @@ func _cmd_eval(params: Dictionary) -> void:
|
|||||||
_send_response({"error": "No code provided"})
|
_send_response({"error": "No code provided"})
|
||||||
return
|
return
|
||||||
|
|
||||||
# Wrap user code in a function so we can capture the return value
|
var script_source: String = _build_eval_script(code)
|
||||||
var script_source: String = """extends Node
|
|
||||||
|
|
||||||
func execute():
|
if EngineDebugger.is_active() and not _debugger_safe:
|
||||||
var __result = null
|
var ext_validation: Dictionary = _validate_script_external(script_source)
|
||||||
__result = await _run()
|
if not ext_validation.get("valid", false):
|
||||||
return __result
|
_send_response({"error": "Script validation failed: %s" % ext_validation.get("error", "unknown error")})
|
||||||
|
return
|
||||||
|
|
||||||
func _run():
|
var validation: Dictionary = _validate_script_source(script_source)
|
||||||
%s
|
if not validation.get("valid", false):
|
||||||
""" % [_indent_code(code)]
|
_send_response({"error": "Script validation failed: %s" % validation.get("error", "unknown error"), "error_code": validation.get("error_code", -1)})
|
||||||
|
return
|
||||||
|
|
||||||
var script: GDScript = GDScript.new()
|
var script: GDScript = GDScript.new()
|
||||||
script.source_code = script_source
|
script.source_code = script_source
|
||||||
@@ -565,7 +575,6 @@ func _run():
|
|||||||
|
|
||||||
var temp_node: Node = Node.new()
|
var temp_node: Node = Node.new()
|
||||||
temp_node.set_script(script)
|
temp_node.set_script(script)
|
||||||
# Allow eval to work even when game is paused
|
|
||||||
temp_node.process_mode = Node.PROCESS_MODE_ALWAYS
|
temp_node.process_mode = Node.PROCESS_MODE_ALWAYS
|
||||||
add_child(temp_node)
|
add_child(temp_node)
|
||||||
|
|
||||||
@@ -577,6 +586,69 @@ func _run():
|
|||||||
_send_response({"success": true, "result": _variant_to_json(result)})
|
_send_response({"success": true, "result": _variant_to_json(result)})
|
||||||
|
|
||||||
|
|
||||||
|
func _build_eval_script(code: String) -> String:
|
||||||
|
return """extends Node
|
||||||
|
|
||||||
|
func execute():
|
||||||
|
var __result = null
|
||||||
|
__result = await _run()
|
||||||
|
return __result
|
||||||
|
|
||||||
|
func _run():
|
||||||
|
%s
|
||||||
|
""" % [_indent_code(code)]
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_script_source(source: String) -> Dictionary:
|
||||||
|
var test_script: GDScript = GDScript.new()
|
||||||
|
test_script.source_code = source
|
||||||
|
var err: int = test_script.reload()
|
||||||
|
if err != OK:
|
||||||
|
var error_name: String = ""
|
||||||
|
match err:
|
||||||
|
ERR_PARSE_ERROR:
|
||||||
|
error_name = "Parse error"
|
||||||
|
ERR_COMPILATION_FAILED:
|
||||||
|
error_name = "Compilation failed"
|
||||||
|
_:
|
||||||
|
error_name = "Error code %d" % err
|
||||||
|
return {"valid": false, "error": error_name, "error_code": err}
|
||||||
|
return {"valid": true}
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_script_external(source: String) -> Dictionary:
|
||||||
|
var temp_path: String = "user://_mcp_eval_validate_%d.gd" % Time.get_ticks_msec()
|
||||||
|
var file: FileAccess = FileAccess.open(temp_path, FileAccess.WRITE)
|
||||||
|
if file == null:
|
||||||
|
return {"valid": true}
|
||||||
|
file.store_string(source)
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
var godot_path: String = OS.get_executable_path()
|
||||||
|
var project_path: String = ProjectSettings.globalize_path("res://")
|
||||||
|
var global_temp: String = ProjectSettings.globalize_path(temp_path)
|
||||||
|
var output: Array = []
|
||||||
|
var exit_code: int = OS.execute(godot_path, [
|
||||||
|
"--headless",
|
||||||
|
"--check-only",
|
||||||
|
"--script", global_temp,
|
||||||
|
"--path", project_path
|
||||||
|
], output)
|
||||||
|
|
||||||
|
DirAccess.remove_absolute(global_temp)
|
||||||
|
|
||||||
|
if exit_code != 0:
|
||||||
|
var error_detail: String = ""
|
||||||
|
for line in output:
|
||||||
|
error_detail += line + "\n"
|
||||||
|
error_detail = error_detail.strip_edges()
|
||||||
|
var msg: String = "Syntax error in eval code (external validation)"
|
||||||
|
if not error_detail.is_empty():
|
||||||
|
msg += ": " + error_detail
|
||||||
|
return {"valid": false, "error": msg}
|
||||||
|
return {"valid": true}
|
||||||
|
|
||||||
|
|
||||||
func _indent_code(code: String) -> String:
|
func _indent_code(code: String) -> String:
|
||||||
var lines: PackedStringArray = code.split("\n")
|
var lines: PackedStringArray = code.split("\n")
|
||||||
var indented: String = ""
|
var indented: String = ""
|
||||||
|
|||||||
Reference in New Issue
Block a user