unity-mcp/mcp_source.py

168 lines
5.3 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
Rename namespace and public facing plugin output from "Unity MCP" to "MCP for Unity" (#225) * refactor: rename namespace from UnityMcpBridge to MCPForUnity across all files See thread in #6, we can't use Unity MCP because it violates their trademark. That name makes us look affiliated. We can use MCP for Unity * Change package display name, menu item and menu titles These are front facing so has to change for Unity asset store review * Misc name changes in logs and comments for better consistency * chore: update editor window title from 'MCP Editor' to 'MCP for Unity' * refactor: update branding from UNITY-MCP to MCP-FOR-UNITY across all log messages and warnings * chore: rename Unity MCP to MCP For Unity across all files and bump version to 2.1.2 * docs: update restore script title to clarify Unity MCP naming * Fix usage instructions * chore: update log messages to use MCP For Unity branding instead of UnityMCP * Add a README inside plugin, required for distributing via the asset store * docs: update Unity port description and fix typo in troubleshooting section * Address Rabbit feedback * Update Editor prefs to use new name Prevents overlap with other Unity MCPs, happy to revert if it's too much * refactor: rename server logger and identifier from unity-mcp-server to mcp-for-unity-server * Standardize casing of renamed project to "MCP for Unity", as it is on the asset store * Remove unused folder * refactor: rename Unity MCP to MCP for Unity across codebase * Update dangling references * docs: update product name from UnityMCP to MCP for Unity in README * Update log and comments for new name
2025-08-21 03:59:49 +08:00
Generic helper to switch the MCP for Unity package source in a Unity project's
Packages/manifest.json. This is useful for switching between upstream and local repos while working on the MCP.
Usage:
python mcp_source.py [--manifest /abs/path/to/manifest.json] [--repo /abs/path/to/unity-mcp] [--choice 1|2|3]
Choices:
1) Upstream main (CoplayDev/unity-mcp)
2) Your remote current branch (derived from `origin` and current branch)
3) Local repo workspace (file: URL to UnityMcpBridge in your checkout)
"""
from __future__ import annotations
import argparse
import json
import os
import pathlib
import re
import subprocess
import sys
from typing import Optional
PKG_NAME = "com.coplaydev.unity-mcp"
BRIDGE_SUBPATH = "UnityMcpBridge"
def run_git(repo: pathlib.Path, *args: str) -> str:
result = subprocess.run([
"git", "-C", str(repo), *args
], capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(result.stderr.strip() or f"git {' '.join(args)} failed")
return result.stdout.strip()
def normalize_origin_to_https(url: str) -> str:
"""Map common SSH origin forms to https for Unity's git URL scheme."""
if url.startswith("git@github.com:"):
owner_repo = url.split(":", 1)[1]
if owner_repo.endswith(".git"):
owner_repo = owner_repo[:-4]
return f"https://github.com/{owner_repo}.git"
# already https or file: etc.
return url
def detect_repo_root(explicit: Optional[str]) -> pathlib.Path:
if explicit:
return pathlib.Path(explicit).resolve()
# Prefer the git toplevel from the script's directory
here = pathlib.Path(__file__).resolve().parent
try:
top = run_git(here, "rev-parse", "--show-toplevel")
return pathlib.Path(top)
except Exception:
return here
def detect_branch(repo: pathlib.Path) -> str:
return run_git(repo, "rev-parse", "--abbrev-ref", "HEAD")
def detect_origin(repo: pathlib.Path) -> str:
url = run_git(repo, "remote", "get-url", "origin")
return normalize_origin_to_https(url)
def find_manifest(explicit: Optional[str]) -> pathlib.Path:
if explicit:
return pathlib.Path(explicit).resolve()
# Walk up from CWD looking for Packages/manifest.json
cur = pathlib.Path.cwd().resolve()
for parent in [cur, *cur.parents]:
candidate = parent / "Packages" / "manifest.json"
if candidate.exists():
return candidate
raise FileNotFoundError("Could not find Packages/manifest.json from current directory. Use --manifest to specify a path.")
def read_json(path: pathlib.Path) -> dict:
with path.open("r", encoding="utf-8") as f:
return json.load(f)
def write_json(path: pathlib.Path, data: dict) -> None:
with path.open("w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
f.write("\n")
def build_options(repo_root: pathlib.Path, branch: str, origin_https: str):
upstream = "git+https://github.com/CoplayDev/unity-mcp.git?path=/UnityMcpBridge"
# Ensure origin is https
origin = origin_https
# If origin is a local file path or non-https, try to coerce to https github if possible
if origin.startswith("file:"):
# Not meaningful for remote option; keep upstream
origin_remote = upstream
else:
origin_remote = origin
return [
("[1] Upstream main", upstream),
("[2] Remote current branch", f"{origin_remote}?path=/{BRIDGE_SUBPATH}#{branch}"),
("[3] Local workspace", f"file:{(repo_root / BRIDGE_SUBPATH).as_posix()}"),
]
def parse_args() -> argparse.Namespace:
Rename namespace and public facing plugin output from "Unity MCP" to "MCP for Unity" (#225) * refactor: rename namespace from UnityMcpBridge to MCPForUnity across all files See thread in #6, we can't use Unity MCP because it violates their trademark. That name makes us look affiliated. We can use MCP for Unity * Change package display name, menu item and menu titles These are front facing so has to change for Unity asset store review * Misc name changes in logs and comments for better consistency * chore: update editor window title from 'MCP Editor' to 'MCP for Unity' * refactor: update branding from UNITY-MCP to MCP-FOR-UNITY across all log messages and warnings * chore: rename Unity MCP to MCP For Unity across all files and bump version to 2.1.2 * docs: update restore script title to clarify Unity MCP naming * Fix usage instructions * chore: update log messages to use MCP For Unity branding instead of UnityMCP * Add a README inside plugin, required for distributing via the asset store * docs: update Unity port description and fix typo in troubleshooting section * Address Rabbit feedback * Update Editor prefs to use new name Prevents overlap with other Unity MCPs, happy to revert if it's too much * refactor: rename server logger and identifier from unity-mcp-server to mcp-for-unity-server * Standardize casing of renamed project to "MCP for Unity", as it is on the asset store * Remove unused folder * refactor: rename Unity MCP to MCP for Unity across codebase * Update dangling references * docs: update product name from UnityMCP to MCP for Unity in README * Update log and comments for new name
2025-08-21 03:59:49 +08:00
p = argparse.ArgumentParser(description="Switch MCP for Unity package source")
p.add_argument("--manifest", help="Path to Packages/manifest.json")
p.add_argument("--repo", help="Path to unity-mcp repo root (for local file option)")
p.add_argument("--choice", choices=["1", "2", "3"], help="Pick option non-interactively")
return p.parse_args()
def main() -> None:
args = parse_args()
try:
repo_root = detect_repo_root(args.repo)
branch = detect_branch(repo_root)
origin = detect_origin(repo_root)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
options = build_options(repo_root, branch, origin)
try:
manifest_path = find_manifest(args.manifest)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
print("Select MCP package source by number:")
for label, _ in options:
print(label)
if args.choice:
choice = args.choice
else:
choice = input("Enter 1-3: ").strip()
if choice not in {"1", "2", "3"}:
print("Invalid selection.", file=sys.stderr)
sys.exit(1)
idx = int(choice) - 1
_, chosen = options[idx]
data = read_json(manifest_path)
deps = data.get("dependencies", {})
if PKG_NAME not in deps:
print(f"Error: '{PKG_NAME}' not found in manifest dependencies.", file=sys.stderr)
sys.exit(1)
print(f"\nUpdating {PKG_NAME}{chosen}")
deps[PKG_NAME] = chosen
data["dependencies"] = deps
write_json(manifest_path, data)
print(f"Done. Wrote to: {manifest_path}")
print("Tip: In Unity, open Package Manager and Refresh to re-resolve packages.")
if __name__ == "__main__":
main()