Files

89 lines
4.6 KiB
Python
Raw Permalink Normal View History

"""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))