feat: Add MCPB bundle for Claude Desktop installation (#580)

* feat: Add MCPB bundle for Claude Desktop installation

Add Model Context Protocol Bundle (MCPB) support enabling single-click
installation in Claude Desktop and other MCP hosts.

Changes:
- Add manifest.json with uvx-based server configuration
- Add icon.png for display in Claude Desktop
- Add .mcpbignore for build exclusions
- Add pre-built unity-mcp.mcpb bundle

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix: Generate MCPB as release artifact instead of committing to repo

Address review feedback from @msanatan:
- Remove pre-built unity-mcp.mcpb and icon.png from repository
- Add tools/generate_mcpb.py script for bundle generation
- Update release.yml with new publish_mcpb job that:
  - Generates bundle using generate_mcpb.py
  - Uploads MCPB file as release artifact
- Update manifest.json to reference icon from docs/images/

The bundle is now generated automatically on each release,
keeping the repository clean while providing download artifacts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* fix: Use correct mcpb pack CLI syntax (positional args)

The mcpb CLI uses 'mcpb pack [directory] [output]' syntax,
not '--output' flag. Tested locally - bundle generates correctly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: triepod-ai <199543909+triepod-ai@users.noreply.github.com>
main
Bryan Thompson 2026-01-22 13:46:35 -06:00 committed by GitHub
parent 30d5bc254e
commit 1238eb1e84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 327 additions and 0 deletions

View File

@ -194,3 +194,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

82
.mcpbignore Normal file
View File

@ -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

58
manifest.json Normal file
View File

@ -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"}
]
}

147
tools/generate_mcpb.py Executable file
View File

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