"""Call the installed Unity MCP server through the standard MCP SDK. Run with the Python environment belonging to the installed MCP for Unity server. Scene mutations and builds stay inside the Unity MCP tools. """ import argparse import asyncio import json import sys from pathlib import Path from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client ROOT = Path(__file__).resolve().parents[2] sys.stdout.reconfigure(encoding="utf-8") def unpack(result): data = getattr(result, "structuredContent", None) if data is None: texts = [x.text for x in result.content if x.type == "text"] try: data = json.loads("\n".join(texts)) except json.JSONDecodeError: data = {"text": "\n".join(texts)} payload = data.get("result", data) if isinstance(data, dict) else data failed = isinstance(payload, dict) and payload.get("success") is False return {"isError": bool(result.isError or failed), "result": data} async def run(args): executable = Path.home() / ".local/share/unity-mcp/.venv/Scripts/mcp-for-unity.exe" logs = ROOT / "Logs/MCP" logs.mkdir(parents=True, exist_ok=True) params = StdioServerParameters(command=str(executable), args=["--transport", "stdio", "--default-instance", ROOT.name]) with (logs / "server.log").open("w", encoding="utf-8") as errlog: async with stdio_client(params, errlog=errlog) as (read, write): async with ClientSession(read, write) as session: await session.initialize() project = await session.read_resource("mcpforunity://project/info") project_text = "\n".join(x.text for x in project.contents if hasattr(x, "text")) if str(ROOT).replace("\\", "/").lower() not in project_text.replace("\\\\", "/").replace("\\", "/").lower(): raise RuntimeError("MCP target is not the workspace: " + project_text) if args.list: available = await session.list_tools() wanted = args.list.split(",") out = [t.model_dump() for t in available.tools if t.name in wanted or args.list == "all"] elif args.resource: res = await session.read_resource(args.resource) out = [x.text for x in res.contents if hasattr(x, "text")] else: commands = json.loads(Path(args.file).read_text(encoding="utf-8-sig")) if isinstance(commands, dict): commands = [commands] out = [] jobs_path = logs / "jobs.json" jobs = json.loads(jobs_path.read_text(encoding="utf-8")) if jobs_path.exists() else {} for command in commands: arguments = command.get("arguments", {}) job_reference = arguments.get("job_id", "") if job_reference.startswith("$"): arguments["job_id"] = jobs[job_reference[1:]] result = unpack(await session.call_tool(command["tool"], arguments)) out.append(result) print(json.dumps({"tool": command["tool"], **result}, ensure_ascii=False), flush=True) payload = result["result"] while isinstance(payload, dict) and isinstance(payload.get("result"), dict) and "data" not in payload: payload = payload["result"] job_id = payload.get("data", {}).get("job_id") if isinstance(payload, dict) and isinstance(payload.get("data"), dict) else None if job_id: category = "test" if command["tool"] in ("run_tests", "get_test_job") else "build" if command["tool"] == "manage_build" else "package" jobs[category] = job_id jobs_path.write_text(json.dumps(jobs), encoding="utf-8") if result["isError"]: break (logs / "last-result.json").write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8") if args.list or args.resource: print(json.dumps(out, ensure_ascii=False, indent=2), flush=True) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--list") parser.add_argument("--resource") parser.add_argument("--file") args = parser.parse_args() asyncio.run(asyncio.wait_for(run(args), timeout=58))