@tool extends RefCounted ## Version helpers: engine-version comparison plus the addon's own plugin version. ## ## Provides is_at_least / is_at_most / is_version_in_range checks against ## the running engine version, used to gate commands by their min/max Godot ## version requirements, read_plugin_version() which reads the toolkit's ## own plugin.cfg, and version_skew() which classifies how the plugin's version ## differs from the connected server's. ## Latest version tested. Versions above this still run but log a notice. const GODOT_TESTED_MAX_VERSION := "4.7.0" ## True when running under `godot --headless` (no display server). ## Use to gate tools that require a viewport or running game. static func is_headless() -> bool: return DisplayServer.get_name() == "headless" static func get_engine_version_pair() -> String: var info := Engine.get_version_info() return "%d.%d" % [info["major"], info["minor"]] ## True when the running engine's "major.minor" pair equals [param target] ## (e.g. "4.2" — matched exactly, so every 4.2.x patch release is caught and a ## future 5.2 is not). For the exact-one-minor case only; version ranges use ## [method is_at_least] / [method is_at_most]. static func is_engine_version_pair(target: String) -> bool: return get_engine_version_pair() == target ## The running engine's major/minor as an int pair — [code]Vector2i(major, minor)[/code] ## ([code].x[/code] = major, [code].y[/code] = minor). For decision predicates that take ## the version as plain data (e.g. the stale-live-instance hint feed); the same pair as a ## string is [method get_engine_version_pair]. static func get_engine_version_ints() -> Vector2i: var info := Engine.get_version_info() return Vector2i(int(info["major"]), int(info["minor"])) ## The toolkit's own version string, read from its plugin.cfg. ## Returns "unknown" if the config can't be loaded. static func read_plugin_version() -> String: var cfg := ConfigFile.new() var err := cfg.load("res://addons/godot_mcp_toolkit/plugin.cfg") if err != OK: return "unknown" return cfg.get_value("plugin", "version", "unknown") ## Classifies how the plugin's version differs from the connected server's. ## ## Compares [param local] with [param remote]. Pure — the caller decides what, if ## anything, to print. A patch difference is compatible by construction: compatibility ## floors are declared at major.minor (ADR 0024), so the patch segment carries no ## compatibility meaning. The returned names match the MCP server's own severity ## vocabulary, so both halves of the handshake speak one language. ## [br] ## Returns "ok", "patch", "minor" or "major", or "unknown" when either side is not ## exactly three dot-separated integers. static func version_skew(local: String, remote: String) -> String: var a := _parse_strict(local) var b := _parse_strict(remote) if a.is_empty() or b.is_empty(): return "unknown" if a[0] != b[0]: return "major" if a[1] != b[1]: return "minor" if a[2] != b[2]: return "patch" return "ok" ## Returns [param v]'s segments as [major, minor, patch], or an empty array when it is ## not exactly three integers. Distinct from _parse, which pads a two-segment engine ## version and is therefore too lenient for a plugin-versus-server comparison. static func _parse_strict(v: String) -> Array[int]: var parts := v.strip_edges().split(".") if parts.size() != 3: return [] var out: Array[int] = [] for part in parts: if not part.is_valid_int(): return [] out.append(int(part)) return out static func is_at_least(engine_ver: String, min_ver: String) -> bool: return _compare(_parse(engine_ver), _parse(min_ver)) >= 0 static func is_at_most(engine_ver: String, max_ver: String) -> bool: return _compare(_parse(engine_ver), _parse(max_ver)) <= 0 static func is_version_in_range(engine_ver: String, min_ver: String, max_ver: String) -> bool: var engine := _parse(engine_ver) if min_ver != "" and _compare(engine, _parse(min_ver)) < 0: return false if max_ver != "" and _compare(engine, _parse(max_ver)) > 0: return false return true static func _parse(v: String) -> Array[int]: var parts := v.split(".") return [int(parts[0]) if parts.size() > 0 else 0, int(parts[1]) if parts.size() > 1 else 0] static func _compare(a: Array[int], b: Array[int]) -> int: if a[0] != b[0]: return a[0] - b[0] return a[1] - b[1]