#!/usr/bin/env python3 """Generate MCPB bundle for Unity MCP. This script creates a Model Context Protocol Bundle (.mcpb) file for distribution as a GitHub release artifact. Usage: python3 tools/generate_mcpb.py VERSION [--output FILE] [--icon PATH] Examples: python3 tools/generate_mcpb.py 9.0.8 python3 tools/generate_mcpb.py 9.0.8 --output unity-mcp-9.0.8.mcpb python3 tools/generate_mcpb.py 9.0.8 --icon docs/images/coplay-logo.png """ from __future__ import annotations import argparse import json import shutil import subprocess import sys import tempfile from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] DEFAULT_ICON = REPO_ROOT / "docs" / "images" / "coplay-logo.png" MANIFEST_TEMPLATE = REPO_ROOT / "manifest.json" def create_manifest(version: str, icon_filename: str) -> dict: """Create manifest.json content with the specified version.""" if not MANIFEST_TEMPLATE.exists(): raise FileNotFoundError(f"Manifest template not found: {MANIFEST_TEMPLATE}") manifest = json.loads(MANIFEST_TEMPLATE.read_text(encoding="utf-8")) manifest["version"] = version manifest["icon"] = icon_filename return manifest def generate_mcpb( version: str, output_path: Path, icon_path: Path, ) -> Path: """Generate MCPB bundle file. Args: version: Semantic version string (e.g., "9.0.8") output_path: Output path for the .mcpb file icon_path: Path to the icon file Returns: Path to the generated .mcpb file """ if not icon_path.exists(): raise FileNotFoundError(f"Icon not found: {icon_path}") with tempfile.TemporaryDirectory() as tmpdir: build_dir = Path(tmpdir) / "mcpb-build" build_dir.mkdir() # Copy icon icon_filename = icon_path.name shutil.copy2(icon_path, build_dir / icon_filename) # Create manifest with version manifest = create_manifest(version, icon_filename) manifest_path = build_dir / "manifest.json" manifest_path.write_text( json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8", ) # Copy LICENSE and README if they exist for filename in ["LICENSE", "README.md"]: src = REPO_ROOT / filename if src.exists(): shutil.copy2(src, build_dir / filename) # Pack using mcpb CLI # Syntax: mcpb pack [directory] [output] try: result = subprocess.run( ["npx", "@anthropic-ai/mcpb", "pack", ".", str(output_path.absolute())], cwd=build_dir, capture_output=True, text=True, check=True, ) print(result.stdout) except subprocess.CalledProcessError as e: print(f"MCPB pack failed:\n{e.stderr}", file=sys.stderr) raise except FileNotFoundError: print( "Error: npx not found. Please install Node.js and npm.", file=sys.stderr, ) raise if not output_path.exists(): raise RuntimeError(f"MCPB file was not created: {output_path}") print(f"Generated: {output_path} ({output_path.stat().st_size:,} bytes)") return output_path def main() -> int: parser = argparse.ArgumentParser( description="Generate MCPB bundle for Unity MCP", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__, ) parser.add_argument( "version", help="Version string for the bundle (e.g., 9.0.8)", ) parser.add_argument( "--output", "-o", type=Path, help="Output path for the .mcpb file (default: unity-mcp-VERSION.mcpb)", ) parser.add_argument( "--icon", type=Path, default=DEFAULT_ICON, help=f"Path to icon file (default: {DEFAULT_ICON.relative_to(REPO_ROOT)})", ) args = parser.parse_args() # Default output name if args.output is None: args.output = Path(f"unity-mcp-{args.version}.mcpb") try: generate_mcpb(args.version, args.output, args.icon) return 0 except Exception as e: print(f"Error: {e}", file=sys.stderr) return 1 if __name__ == "__main__": sys.exit(main())