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
parent
30d5bc254e
commit
1238eb1e84
|
|
@ -194,3 +194,43 @@ jobs:
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
with:
|
with:
|
||||||
packages-dir: Server/dist/
|
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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -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())
|
||||||
Loading…
Reference in New Issue