"""Audio ops — MetaSounds, SoundCue, Attenuation, SoundClass/Mix. Ops: create_metasound_source {path} create_metasound_patch {path} create_sound_cue {path} create_sound_attenuation {path} create_sound_class {path} create_sound_mix {path} set_property {path, property, value} get_property {path, property} list_sound_assets {folder?} play_sound_2d {path} — preview in editor """ from __future__ import annotations import json import os import traceback try: import unreal # type: ignore except ImportError: unreal = None def _at(): return unreal.AssetToolsHelpers.get_asset_tools() def _load(p): return unreal.EditorAssetLibrary.load_asset(p) def _split(p): folder, name = p.rsplit("/", 1) return folder, name def _create(path, asset_cls_name, factory_name): folder, name = _split(path) cls = getattr(unreal, asset_cls_name, None) factory_cls = getattr(unreal, factory_name, None) if cls is None or factory_cls is None: return {"ok": False, "error": f"{asset_cls_name}/{factory_name} not exposed (plugin disabled?)"} asset = _at().create_asset(name, folder, cls, factory_cls()) if asset is None: return {"ok": False, "error": "create_asset returned None"} unreal.EditorAssetLibrary.save_asset(asset.get_path_name()) return {"ok": True, "path": asset.get_path_name().split(".")[0]} def create_metasound_source(args): return _create(args["path"], "MetaSoundSource", "MetaSoundSourceFactory") def create_metasound_patch(args): return _create(args["path"], "MetaSoundPatch", "MetaSoundPatchFactory") def create_sound_cue(args): return _create(args["path"], "SoundCue", "SoundCueFactoryNew") def create_sound_attenuation(args): return _create(args["path"], "SoundAttenuation", "SoundAttenuationFactory") def create_sound_class(args): return _create(args["path"], "SoundClass", "SoundClassFactory") def create_sound_mix(args): return _create(args["path"], "SoundMix", "SoundMixFactory") def set_property(args): a = _load(args["path"]) if a is None: return {"ok": False, "error": "asset not found"} try: a.set_editor_property(args["property"], args["value"]) unreal.EditorAssetLibrary.save_asset(a.get_path_name()) return {"ok": True} except Exception as e: return {"ok": False, "error": str(e)} def get_property(args): a = _load(args["path"]) if a is None: return {"ok": False, "error": "asset not found"} try: v = a.get_editor_property(args["property"]) return {"ok": True, "value": str(v)} except Exception as e: return {"ok": False, "error": str(e)} def list_sound_assets(args): folder = args.get("folder") or "/Game" ar = unreal.AssetRegistryHelpers.get_asset_registry() classes = ["SoundCue", "SoundWave", "MetaSoundSource", "MetaSoundPatch", "SoundAttenuation", "SoundClass", "SoundMix", "SoundConcurrency"] filt = unreal.ARFilter(package_paths=[folder], recursive_paths=True, class_names=classes) items = [{"path": str(d.package_name), "class": str(d.asset_class)} for d in ar.get_assets(filt)] return {"ok": True, "count": len(items), "items": items} def play_sound_2d(args): a = _load(args["path"]) if a is None: return {"ok": False, "error": "not found"} try: world = unreal.EditorLevelLibrary.get_editor_world() unreal.GameplayStatics.play_sound2d(world, a) return {"ok": True} except Exception as e: return {"ok": False, "error": str(e)} OPS = { "create_metasound_source": create_metasound_source, "create_metasound_patch": create_metasound_patch, "create_sound_cue": create_sound_cue, "create_sound_attenuation": create_sound_attenuation, "create_sound_class": create_sound_class, "create_sound_mix": create_sound_mix, "set_property": set_property, "get_property": get_property, "list_sound_assets": list_sound_assets, "play_sound_2d": play_sound_2d, } def entrypoint(payload_json: str) -> str: try: p = json.loads(payload_json) except Exception as e: return _tee({"ok": False, "error": f"bad json: {e}"}, {}) op = p.get("op") fn = OPS.get(op) if not fn: return _tee({"ok": False, "error": f"unknown op {op!r}", "available": list(OPS)}, p) try: return _tee(fn(p), p) except Exception as e: return _tee({"ok": False, "error": str(e), "traceback": traceback.format_exc()}, p) def _tee(result, p): 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 + "last.json", "w", encoding="utf-8") as f: f.write(txt) if rid != "last": with open(sd + f"{rid}.json", "w", encoding="utf-8") as f: f.write(txt) unreal.log("[MCP-AUDIO] " + txt[:1500]) except Exception: pass return txt