Merge of the two project copies into one self-contained plugin (the superset: variable_op + variables.py, full pcg_op runtime/declarative/preset ops, the CreateWidgetFloatAnimation widget tool, and full Voxel graph authoring). Made fully project- and machine-agnostic — no hardcoded paths: - New src/projectPaths.js auto-detects the host .uproject (walk-up), project name, Editor build target, log file, and engine install (EngineAssociation via launcher manifest/registry, else installed-engine scan). All overridable via UE_* env vars. - Rewired buildOrchestrator/insights/launcher/insightsExporter/server.js and the Python workers (cpp_scaffold, live_coding, apply_graph, console) off the old C:/Github/ihy, IHY*, E:/UE_Versions and UE_5.7 literals onto the resolver. - Voxel made optional: Build.cs auto-detects the Voxel plugin (env UE_MCP_WITH_VOXEL override) and the C++ compiles to stubs under WITH_MCP_VOXEL, so the module builds in projects without Voxel; .uplugin marks Voxel optional. - De-branded the agent-gateway and docs; scrubbed a leaked API key; excluded node_modules/Binaries/Intermediate/__pycache__/secrets from the repo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
154 lines
5.0 KiB
Python
154 lines
5.0 KiB
Python
"""Read-only discovery ops via MCPGraphLibrary v0.3.
|
|
|
|
Ops:
|
|
list_open_bps {} — open BP editors
|
|
list_graphs {bp_path} — all graphs of a BP
|
|
describe_bp {bp_path} — parent/interfaces/vars/components/funcs/dispatchers
|
|
get_selection {bp_path} — selected nodes in BP editor
|
|
resolve_call {bp_path, function, guid} — CallFunction node → source bp/class
|
|
find_var_refs {bp_path, name} — variable usage sites
|
|
find_fn_refs {bp_path, name} — function call sites in this BP
|
|
find_bp_by_parent {parent_class_path} — AssetRegistry search
|
|
get_refs_of {asset_path} — assets referenced by this asset
|
|
get_referencers {asset_path} — assets that reference this asset
|
|
read_function {bp_path, function} — wrapper over apply_graph.read_graph for function
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import traceback
|
|
|
|
try:
|
|
import unreal # type: ignore
|
|
except ImportError:
|
|
unreal = None
|
|
|
|
|
|
def _lib():
|
|
L = getattr(unreal, "MCPGraphLibrary", None)
|
|
if L is None:
|
|
raise RuntimeError("MCPGraphLibrary missing — rebuild C++ plugin")
|
|
return L
|
|
|
|
|
|
def _load_bp(path: str):
|
|
a = unreal.EditorAssetLibrary.load_asset(path)
|
|
if not isinstance(a, unreal.Blueprint):
|
|
raise RuntimeError(f"not a blueprint: {path!r}")
|
|
return a
|
|
|
|
|
|
def _find_graph(bp, name: str):
|
|
L = _lib()
|
|
if name in (None, "", "EventGraph"):
|
|
return L.find_event_graph(bp)
|
|
return L.find_function_graph(bp, name)
|
|
|
|
|
|
def list_open_bps(args: dict) -> dict:
|
|
return {"ok": True, "open": list(_lib().list_open_blueprints())}
|
|
|
|
|
|
def list_graphs(args: dict) -> dict:
|
|
bp = _load_bp(args["bp_path"])
|
|
raw = _lib().list_graphs_json(bp)
|
|
return {"ok": True, "bp_path": args["bp_path"], **json.loads(raw)}
|
|
|
|
|
|
def describe_bp(args: dict) -> dict:
|
|
bp = _load_bp(args["bp_path"])
|
|
raw = _lib().describe_blueprint_json(bp)
|
|
return {"ok": True, **json.loads(raw)}
|
|
|
|
|
|
def get_selection(args: dict) -> dict:
|
|
bp = _load_bp(args["bp_path"])
|
|
raw = _lib().get_selected_nodes_json(bp)
|
|
return {"ok": True, "bp_path": args["bp_path"], **json.loads(raw)}
|
|
|
|
|
|
def resolve_call(args: dict) -> dict:
|
|
bp = _load_bp(args["bp_path"])
|
|
g = _find_graph(bp, args.get("function") or "EventGraph")
|
|
if g is None:
|
|
return {"ok": False, "error": "graph not found"}
|
|
raw = _lib().resolve_function_source_json(g, args["guid"])
|
|
return {"ok": True, **json.loads(raw)}
|
|
|
|
|
|
def find_var_refs(args: dict) -> dict:
|
|
bp = _load_bp(args["bp_path"])
|
|
raw = _lib().find_variable_references_json(bp, args["name"])
|
|
return {"ok": True, **json.loads(raw)}
|
|
|
|
|
|
def find_fn_refs(args: dict) -> dict:
|
|
bp = _load_bp(args["bp_path"])
|
|
raw = _lib().find_function_references_json(bp, args["name"])
|
|
return {"ok": True, **json.loads(raw)}
|
|
|
|
|
|
def find_bp_by_parent(args: dict) -> dict:
|
|
found = list(_lib().find_blueprints_by_parent(args["parent_class_path"]))
|
|
return {"ok": True, "blueprints": found, "count": len(found)}
|
|
|
|
|
|
def get_refs_of(args: dict) -> dict:
|
|
return {"ok": True, "references": list(_lib().get_referenced_assets(args["asset_path"]))}
|
|
|
|
|
|
def get_referencers(args: dict) -> dict:
|
|
return {"ok": True, "referencers": list(_lib().get_referencers_of_asset(args["asset_path"]))}
|
|
|
|
|
|
def read_function(args: dict) -> dict:
|
|
# Convenience: read_graph for a named function
|
|
import importlib, apply_graph
|
|
importlib.reload(apply_graph)
|
|
return apply_graph.read_graph(args["bp_path"], args.get("function") or "EventGraph")
|
|
|
|
|
|
OPS = {
|
|
"list_open_bps": list_open_bps,
|
|
"list_graphs": list_graphs,
|
|
"describe_bp": describe_bp,
|
|
"get_selection": get_selection,
|
|
"resolve_call": resolve_call,
|
|
"find_var_refs": find_var_refs,
|
|
"find_fn_refs": find_fn_refs,
|
|
"find_bp_by_parent": find_bp_by_parent,
|
|
"get_refs_of": get_refs_of,
|
|
"get_referencers": get_referencers,
|
|
"read_function": read_function,
|
|
}
|
|
|
|
|
|
def entrypoint(payload_json: str) -> str:
|
|
try:
|
|
p = json.loads(payload_json)
|
|
except Exception as e: # noqa: BLE001
|
|
return _tee({"ok": False, "error": f"bad json: {e}"}, p={})
|
|
fn = OPS.get(p.get("op"))
|
|
if not fn:
|
|
return _tee({"ok": False, "error": f"unknown op {p.get('op')!r}", "available": list(OPS)}, p)
|
|
try:
|
|
return _tee(fn(p), p)
|
|
except Exception as e: # noqa: BLE001
|
|
return _tee({"ok": False, "error": str(e), "traceback": traceback.format_exc()}, p)
|
|
|
|
|
|
def _tee(result: dict, p: dict) -> str:
|
|
txt = json.dumps(result)
|
|
try:
|
|
if unreal is not None:
|
|
sd = unreal.Paths.project_saved_dir() + "MCP/"
|
|
os.makedirs(sd, exist_ok=True)
|
|
rid = p.get("__rid") or "last"
|
|
with open(sd + f"{rid}.json", "w", encoding="utf-8") as f:
|
|
f.write(txt)
|
|
unreal.log("[MCP-DISCOVER] " + txt[:1500])
|
|
except Exception:
|
|
pass
|
|
return txt
|