148 lines
4.3 KiB
Python
148 lines
4.3 KiB
Python
|
|
#!/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())
|