#!/usr/bin/env python3
from __future__ import annotations
import argparse
import datetime as dt
import re
import shutil
import tempfile
from pathlib import Path
REPO_ROOT_DEFAULT = Path(__file__).resolve(
).parents[1] # adjust if you place elsewhere
def read_text(path: Path) -> str:
return path.read_text(encoding="utf-8")
def write_text(path: Path, text: str) -> None:
path.write_text(text, encoding="utf-8")
def replace_once(path: Path, pattern: str, repl: str) -> None:
"""
Regex replace exactly once, else raise.
"""
original = read_text(path)
new, n = re.subn(pattern, repl, original, flags=re.MULTILINE)
if n != 1:
raise RuntimeError(
f"{path}: expected 1 replacement for pattern, got {n}")
if new != original:
write_text(path, new)
def remove_line_exact(path: Path, line: str) -> None:
original = read_text(path)
lines = original.splitlines(keepends=True)
removed = 0
kept: list[str] = []
for l in lines:
if l.strip() == line:
removed += 1
continue
kept.append(l)
if removed != 1:
raise RuntimeError(
f"{path}: expected to remove exactly 1 line '{line}', removed {removed}")
write_text(path, "".join(kept))
def backup_dir(src: Path, backup_root: Path) -> Path:
ts = dt.datetime.now().strftime("%Y%m%d-%H%M%S")
backup_path = backup_root / f"{src.name}.backup.{ts}"
shutil.copytree(src, backup_path)
return backup_path
def main() -> int:
parser = argparse.ArgumentParser(
description="Prepare MCPForUnity for Asset Store upload.")
parser.add_argument(
"--repo-root",
default=str(REPO_ROOT_DEFAULT),
help="Path to unity-mcp repo root (default: inferred from script location).",
)
parser.add_argument(
"--asset-project",
default=None,
help="Path to the Unity project used for Asset Store uploads.",
)
parser.add_argument(
"--backup",
action="store_true",
help="Backup existing Assets/MCPForUnity before replacing.",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Only validate that operations would succeed; do not write/copy/delete.",
)
args = parser.parse_args()
repo_root = Path(args.repo_root).expanduser().resolve()
asset_project = Path(args.asset_project).expanduser().resolve(
) if args.asset_project else (repo_root / "TestProjects" / "AssetStoreUploads")
source_mcp = repo_root / "MCPForUnity"
if not source_mcp.is_dir():
raise RuntimeError(
f"Source MCPForUnity folder not found: {source_mcp}")
assets_dir = asset_project / "Assets"
if not assets_dir.is_dir():
raise RuntimeError(f"Assets folder not found: {assets_dir}")
dest_mcp = assets_dir / "MCPForUnity"
if args.dry_run:
print("[dry-run] Validated paths. No changes applied.")
print("[dry-run] Would stage a temporary copy of MCPForUnity and apply Asset Store edits there.")
print(
f"[dry-run] Would replace:\n- {dest_mcp}\n with\n- {source_mcp}")
return 0
# 1) Stage a temporary copy of MCPForUnity and apply Asset Store-specific edits there.
with tempfile.TemporaryDirectory(prefix="mcpforunity_assetstore_") as tmpdir:
staged_mcp = Path(tmpdir) / "MCPForUnity"
shutil.copytree(source_mcp, staged_mcp)
setup_service = staged_mcp / "Editor" / "Setup" / "SetupWindowService.cs"
menu_file = staged_mcp / "Editor" / "MenuItems" / "MCPForUnityMenu.cs"
http_util = staged_mcp / "Editor" / "Helpers" / "HttpEndpointUtility.cs"
connection_section = staged_mcp / "Editor" / "Windows" / \
"Components" / "Connection" / "McpConnectionSection.cs"
for f in (setup_service, menu_file, http_util, connection_section):
if not f.is_file():
raise RuntimeError(f"Expected file not found: {f}")
# Remove auto-popup setup window for Asset Store packaging
remove_line_exact(setup_service, "[InitializeOnLoad]")
# Set default base URL to the hosted endpoint
replace_once(
http_util,
r'private const string DefaultBaseUrl = "http://localhost:8080";',
'private const string DefaultBaseUrl = "https://mc-0cb5e1039f6b4499b473670f70662d29.ecs.us-east-2.on.aws/";',
)
# Default transport to HTTP Remote and persist inferred scope when missing
replace_once(
connection_section,
r'transportDropdown\.Init\(TransportProtocol\.HTTPLocal\);',
'transportDropdown.Init(TransportProtocol.HTTPRemote);',
)
# 2) Replace Assets/MCPForUnity in the target project
if dest_mcp.exists():
if args.backup:
backup_root = asset_project / "AssetStoreBackups"
backup_root.mkdir(parents=True, exist_ok=True)
backup_path = backup_dir(dest_mcp, backup_root)
print(f"Backed up existing folder to: {backup_path}")
shutil.rmtree(dest_mcp)
shutil.copytree(staged_mcp, dest_mcp)
print("Done.")
print(f"- Source (unchanged): {source_mcp}")
print(f"- Updated Asset Store project folder: {dest_mcp}")
return 0
if __name__ == "__main__":
raise SystemExit(main())