Files
dsarno c43efe301a Fix editor state always reporting stale when Unity is backgrounded (#789)
* 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>
2026-02-19 20:29:06 -08:00

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