diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f6d021215..d62e8bffd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -188,3 +188,43 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: Server/dist/ + + publish_mcpb: + name: Generate and publish MCPB bundle + needs: + - bump + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Check out the repo + uses: actions/checkout@v6 + with: + ref: ${{ needs.bump.outputs.tag }} + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Generate MCPB bundle + env: + NEW_VERSION: ${{ needs.bump.outputs.new_version }} + shell: bash + run: | + set -euo pipefail + python3 tools/generate_mcpb.py "$NEW_VERSION" \ + --output "unity-mcp-${NEW_VERSION}.mcpb" \ + --icon docs/images/coplay-logo.png + + - name: Upload MCPB to release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.bump.outputs.tag }} + files: unity-mcp-${{ needs.bump.outputs.new_version }}.mcpb diff --git a/.mcpbignore b/.mcpbignore new file mode 100644 index 000000000..cd911c3d9 --- /dev/null +++ b/.mcpbignore @@ -0,0 +1,82 @@ +# MCPB Ignore File +# This bundle uses uvx pattern - package downloaded from PyPI at runtime +# Only manifest.json, icon.png, README.md, and LICENSE are needed + +# Server source code (downloaded via uvx from PyPI) +Server/ + +# Unity Client plugin (separate installation) +MCPForUnity/ + +# Test projects +TestProjects/ + +# Documentation folder +docs/ + +# Custom Tools (shipped separately) +CustomTools/ + +# Development scripts at root +scripts/ +tools/ + +# Claude skill zip (separate distribution) +claude_skill_unity.zip + +# Development batch files +deploy-dev.bat +restore-dev.bat + +# Test files at root +test_unity_socket_framing.py +mcp_source.py +prune_tool_results.py + +# Docker +docker-compose.yml +.dockerignore +Dockerfile + +# Chinese README (keep English only) +README-zh.md + +# GitHub and CI +.github/ +.claude/ + +# IDE +.vscode/ +.idea/ + +# Python artifacts +*.pyc +__pycache__/ +.pytest_cache/ +.mypy_cache/ +*.egg-info/ +dist/ +build/ + +# Environment +.env* +*.local +.venv/ +venv/ + +# Git +.git/ +.gitignore +.gitattributes + +# Package management +uv.lock +poetry.lock +requirements*.txt +pyproject.toml + +# Logs and temp +*.log +*.tmp +.DS_Store +Thumbs.db diff --git a/manifest.json b/manifest.json new file mode 100644 index 000000000..f77655de8 --- /dev/null +++ b/manifest.json @@ -0,0 +1,58 @@ +{ + "manifest_version": "0.3", + "name": "Unity MCP", + "version": "9.0.7", + "description": "AI-powered Unity Editor automation via MCP - manage GameObjects, scripts, materials, scenes, prefabs, VFX, and run tests", + "author": { + "name": "Coplay", + "url": "https://www.coplay.dev" + }, + "repository": { + "type": "git", + "url": "https://github.com/CoplayDev/unity-mcp" + }, + "homepage": "https://www.coplay.dev", + "documentation": "https://github.com/CoplayDev/unity-mcp#readme", + "support": "https://github.com/CoplayDev/unity-mcp/issues", + "icon": "coplay-logo.png", + "server": { + "type": "python", + "entry_point": "Server/src/main.py", + "mcp_config": { + "command": "uvx", + "args": ["--from", "mcpforunityserver", "mcp-for-unity"], + "env": {} + } + }, + "tools": [ + {"name": "batch_execute", "description": "Execute multiple Unity operations in a single batch"}, + {"name": "debug_request_context", "description": "Debug and inspect MCP request context"}, + {"name": "execute_custom_tool", "description": "Execute custom Unity Editor tools registered by the project"}, + {"name": "execute_menu_item", "description": "Execute Unity Editor menu items"}, + {"name": "find_gameobjects", "description": "Find GameObjects in the scene by various criteria"}, + {"name": "find_in_file", "description": "Search for content within Unity project files"}, + {"name": "manage_asset", "description": "Create, modify, search, and organize Unity assets"}, + {"name": "manage_components", "description": "Add, remove, and configure GameObject components"}, + {"name": "manage_editor", "description": "Control Unity Editor state, play mode, and preferences"}, + {"name": "manage_gameobject", "description": "Create, modify, transform, and delete GameObjects"}, + {"name": "manage_material", "description": "Create and modify Unity materials and shaders"}, + {"name": "manage_prefabs", "description": "Create, instantiate, unpack, and modify prefabs"}, + {"name": "manage_scene", "description": "Load, save, query hierarchy, and manage Unity scenes"}, + {"name": "manage_script", "description": "Create, read, and modify C# scripts"}, + {"name": "manage_scriptable_object", "description": "Create and modify ScriptableObjects"}, + {"name": "manage_shader", "description": "Work with Unity shaders"}, + {"name": "manage_vfx", "description": "Manage Visual Effects, particle systems, and trails"}, + {"name": "read_console", "description": "Read Unity Editor console output (logs, warnings, errors)"}, + {"name": "refresh_unity", "description": "Refresh Unity Editor asset database"}, + {"name": "run_tests", "description": "Run Unity Test Framework tests"}, + {"name": "get_test_job", "description": "Get status of async test job"}, + {"name": "script_apply_edits", "description": "Apply code edits to C# scripts with validation"}, + {"name": "set_active_instance", "description": "Set the active Unity Editor instance for multi-instance workflows"}, + {"name": "apply_text_edits", "description": "Apply text edits to script content"}, + {"name": "create_script", "description": "Create new C# scripts"}, + {"name": "delete_script", "description": "Delete C# scripts"}, + {"name": "validate_script", "description": "Validate C# script syntax and compilation"}, + {"name": "manage_script_capabilities", "description": "Query script management capabilities"}, + {"name": "get_sha", "description": "Get SHA hash of script content"} + ] +} diff --git a/tools/generate_mcpb.py b/tools/generate_mcpb.py new file mode 100755 index 000000000..7194218b8 --- /dev/null +++ b/tools/generate_mcpb.py @@ -0,0 +1,147 @@ +#!/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())