c43efe301a
* Fix editor state always reporting stale when Unity is backgrounded EditorStateCache.GetSnapshot() returned a cached clone with the observed_at_unix_ms from the last OnUpdate tick. When Unity is backgrounded, OnUpdate is throttled, so the timestamp grows stale even though the data is current and Unity is responsive. Now stamps the clone at serve time so the server-side staleness check (>2s = stale) reflects when the snapshot was served, not when the update loop last ran. This fixes ready_for_tools being false for every backgrounded Unity editor on HTTP. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Only re-stamp editor state when Unity is backgrounded Addresses CodeRabbit review feedback: the unconditional re-stamp defeated the staleness check for genuinely unresponsive focused editors. Now uses InternalEditorUtility.isApplicationActive to conditionally re-stamp only when Unity is backgrounded. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add /mcp-source skill and fix upstream main URL Add Claude Code skill for switching MCP package source in Unity projects. Fix mcp_source.py upstream main URL to include required #main branch suffix. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
177 lines
5.5 KiB
Python
Executable File
177 lines
5.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
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|4]
|
|
|
|
Choices:
|
|
1) Upstream main (CoplayDev/unity-mcp)
|
|
2) Upstream beta (CoplayDev/unity-mcp#beta)
|
|
3) Your remote current branch (derived from `origin` and current branch)
|
|
4) Local repo workspace (file: URL to MCPForUnity in your checkout)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import pathlib
|
|
import subprocess
|
|
import sys
|
|
|
|
PKG_NAME = "com.coplaydev.unity-mcp"
|
|
BRIDGE_SUBPATH = "MCPForUnity"
|
|
|
|
|
|
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: str | None) -> 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: str | None) -> 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_main = "https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#main"
|
|
upstream_beta = "https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#beta"
|
|
# 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_main
|
|
else:
|
|
origin_remote = origin
|
|
return [
|
|
("[1] Upstream main", upstream_main),
|
|
("[2] Upstream beta", upstream_beta),
|
|
(f"[3] Remote {branch}",
|
|
f"{origin_remote}?path=/{BRIDGE_SUBPATH}#{branch}"),
|
|
(f"[4] Local {branch}",
|
|
f"file:{(repo_root / BRIDGE_SUBPATH).as_posix()}"),
|
|
]
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
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", "4"], 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-4: ").strip()
|
|
|
|
if choice not in {"1", "2", "3", "4"}:
|
|
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()
|