350 lines
16 KiB
Markdown
350 lines
16 KiB
Markdown
# MCP for Unity - Developer Guide
|
||
|
||
| English | [简体中文](https://github.com/CoplayDev/unity-mcp/blob/beta/docs/development/README-DEV-zh.md) |
|
||
|---------------------------|------------------------------|
|
||
|
||
## Contributing
|
||
|
||
**Branch off `beta`** to create PRs. The `main` branch is reserved for stable releases.
|
||
|
||
Before proposing major new features, please reach out to discuss - someone may already be working on it or it may have been considered previously. Open an issue or discussion to coordinate.
|
||
|
||
Keep PRs focused. A small fix with a clear repro and the exact Unity/package versions is much easier to review than a broad cleanup bundled with behavior changes.
|
||
|
||
For bug fixes, include:
|
||
|
||
- Unity version(s) tested
|
||
- package source used (`#beta`, `#main`, tag, fork branch, or `file:`)
|
||
- resolved Git commit from `Packages/packages-lock.json` when the package source is a Git URL
|
||
- commands/tests run locally
|
||
|
||
Avoid mixing release/version bumps with feature or bug-fix PRs. The release workflows handle package and server version updates.
|
||
|
||
## Repository Map
|
||
|
||
- `MCPForUnity/` - the Unity package: Editor UI, C# tool handlers, resources, compatibility shims, package metadata.
|
||
- `Server/` - the Python MCP server, CLI, FastMCP tool/resource registry, transports, and server-side tests.
|
||
- `TestProjects/UnityMCPTests/` - Unity project used for EditMode/PlayMode tests. Its manifest points at `file:../../../MCPForUnity`.
|
||
- `docs/` - user, contributor, release, migration, and reference documentation.
|
||
- `tools/` - release/build helpers, documentation update prompt, stress tools, and publishing scripts.
|
||
- `CustomTools/` - examples and support code for project-defined custom tools.
|
||
|
||
## Local Development Setup
|
||
|
||
### 0. Prepare the Python Server
|
||
|
||
For server work, use the same setup path as CI:
|
||
|
||
```bash
|
||
cd Server
|
||
uv sync
|
||
uv pip install -e ".[dev]"
|
||
uv run pytest tests/ -v --tb=short
|
||
```
|
||
|
||
Most Python unit tests do not need Unity running. Integration tests that talk to the Editor require a Unity instance with the bridge connected.
|
||
|
||
### 1. Point Unity to Your Local Server
|
||
|
||
For the fastest iteration when working on the Python server:
|
||
|
||
1. Open Unity and go to **Window > MCP for Unity**
|
||
2. Open **Settings > Advanced Settings**
|
||
3. Set **Server Source Override** to your local `Server/` directory path
|
||
4. Enable **Dev Mode (Force fresh server install)** - this adds `--refresh` to uvx commands so your changes are picked up on every server start
|
||
|
||
### 2. Switch Package Sources
|
||
|
||
You may want to use the `mcp_source.py` script to quickly switch your Unity project between different MCP package sources [allows you to quickly point your personal project to your local or remote unity-mcp repo, or the live upstream (Coplay) versions of the unity-mcp package]:
|
||
|
||
```bash
|
||
python mcp_source.py
|
||
```
|
||
|
||
Options:
|
||
1. **Upstream main** - stable release (CoplayDev/unity-mcp)
|
||
2. **Upstream beta** - development branch (CoplayDev/unity-mcp#beta)
|
||
3. **Remote branch** - your fork's current branch
|
||
4. **Local workspace** - file: URL to your local MCPForUnity folder
|
||
|
||
After switching, open Package Manager in Unity and Refresh to re-resolve packages.
|
||
|
||
When testing Unity package changes, prefer **Local workspace**. Do not patch `Library/PackageCache/` directly; Unity may overwrite it on the next resolve.
|
||
|
||
`#beta` and `#main` are moving branch names. When debugging a package-source issue, check `Packages/packages-lock.json` as well as `Packages/manifest.json`; the lock file tells you the commit Unity actually resolved.
|
||
|
||
Example Git package lock entry:
|
||
|
||
```json
|
||
"com.coplaydev.unity-mcp": {
|
||
"version": "https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#beta",
|
||
"source": "git",
|
||
"hash": "<resolved-git-commit>"
|
||
}
|
||
```
|
||
|
||
## Adding or Changing Tools and Resources
|
||
|
||
Built-in Unity tools usually have two sides:
|
||
|
||
- a C# handler under `MCPForUnity/Editor/Tools/`
|
||
- a Python-facing tool under `Server/src/services/tools/`
|
||
|
||
Resources follow the same split:
|
||
|
||
- C# resources under `MCPForUnity/Editor/Resources/`
|
||
- Python resources under `Server/src/services/resources/`
|
||
|
||
Use `[McpForUnityTool]` / `[McpForUnityResource]` on the Unity side and `@mcp_for_unity_tool` / `@mcp_for_unity_resource` on the server side. If the tool is long-running, use the existing `PendingResponse` / polling patterns instead of blocking the bridge.
|
||
|
||
Current server tool groups are:
|
||
|
||
- `core`
|
||
- `docs`
|
||
- `vfx`
|
||
- `animation`
|
||
- `ui`
|
||
- `scripting_ext`
|
||
- `testing`
|
||
- `probuilder`
|
||
- `profiling`
|
||
|
||
Tools with `group=None` are server meta-tools and are always visible. The default enabled group is `core`; other groups can be enabled through the Tools tab or `manage_tools`.
|
||
|
||
When adding, removing, or renaming tools/resources, update the public docs and client metadata. Start with `tools/UPDATE_DOCS_PROMPT.md`, then review the changes manually.
|
||
|
||
## Tool Selection & the Meta-Tool
|
||
|
||
MCP for Unity organizes tools into **groups**. You can selectively enable or disable tools to control which capabilities are exposed to AI clients — reducing context window usage and focusing the AI on relevant tools.
|
||
|
||
### Using the Tools Tab in the Editor
|
||
|
||
Open **Window > MCP for Unity** and switch to the **Tools** tab. Each tool group is displayed as a collapsible foldout with:
|
||
|
||
- **Per-tool toggles** — click individual tool toggles to enable or disable them.
|
||
- **Group checkbox** — a checkbox embedded directly in each group's foldout header (next to the group title) enables or disables all tools in that group at once without expanding or collapsing the foldout.
|
||
- **Enable All / Disable All** — global buttons to toggle all tools.
|
||
- **Rescan** — re-discovers tools from assemblies (useful after adding new `[McpForUnityTool]` classes).
|
||
- **Reconfigure Clients** — re-registers tools with the server and reconfigures all detected MCP clients in one click, applying your changes without navigating back to the Clients tab.
|
||
|
||
### How Changes Propagate
|
||
|
||
Tool visibility changes work differently depending on the transport mode:
|
||
|
||
**HTTP mode** (recommended):
|
||
|
||
1. Toggling a tool calls `ReregisterToolsAsync()`, which sends the updated enabled tool list to the Python server over WebSocket.
|
||
2. The server updates its internal tool visibility via `mcp.enable()`/`mcp.disable()` per group.
|
||
3. The server sends a `tools/list_changed` MCP notification to all connected client sessions.
|
||
4. Already-connected clients (Claude Desktop, VS Code, etc.) automatically receive the updated tool list.
|
||
|
||
**Stdio mode**:
|
||
|
||
1. Toggles are persisted locally but cannot be pushed to the server (no WebSocket connection).
|
||
2. The server starts with all groups enabled. After changing toggles, ask the AI to run `manage_tools` with `action='sync'` — this pulls the current tool states from Unity and syncs server visibility.
|
||
3. Alternatively, restart the server to pick up changes.
|
||
|
||
### The `manage_tools` Meta-Tool
|
||
|
||
The server exposes a built-in `manage_tools` tool (always visible, not group-gated) that AIs can call directly:
|
||
|
||
| Action | Description |
|
||
|--------|-------------|
|
||
| `list_groups` | Lists all tool groups with their tools and enable/disable status |
|
||
| `activate` | Enables a tool group by name (e.g., `group="vfx"`) |
|
||
| `deactivate` | Disables a tool group by name |
|
||
| `sync` | Pulls current tool states from Unity and syncs server visibility (essential for stdio mode) |
|
||
| `reset` | Restores default tool visibility |
|
||
|
||
### When You Need to Reconfigure
|
||
|
||
After toggling tools on/off, MCP clients need to learn about the changes:
|
||
|
||
- **HTTP mode**: Changes propagate automatically via `tools/list_changed`. Most clients pick this up immediately. If a client doesn't, click **Reconfigure Clients** on the Tools tab, or go to Clients tab and click Configure.
|
||
- **Stdio mode**: The server process needs to be told about changes. Either ask the AI to call `manage_tools(action='sync')`, or restart the MCP session. Click **Reconfigure Clients** to re-register all clients with updated config.
|
||
|
||
## Running Tests
|
||
|
||
All major new features (and some minor ones) must include test coverage. It's so easy to get LLMs to write tests, ya gotta do it!
|
||
|
||
### Python Tests
|
||
|
||
Located in `Server/tests/`:
|
||
|
||
```bash
|
||
cd Server
|
||
uv run pytest tests/ -v
|
||
```
|
||
|
||
Useful narrower runs:
|
||
|
||
```bash
|
||
uv run pytest tests/test_manage_camera.py -v
|
||
uv run pytest tests/integration/test_run_tests_async.py -v
|
||
uv run pytest tests/ -v --tb=short
|
||
```
|
||
|
||
### Unity C# Tests
|
||
|
||
Located in `TestProjects/UnityMCPTests/Assets/Tests/`.
|
||
|
||
The test project consumes the local Unity package from this repo. Use it for package import/compile checks before opening a PR that touches `MCPForUnity/Runtime/`, `MCPForUnity/Editor/`, or package metadata.
|
||
|
||
**Using the CLI** (requires Unity running with MCP bridge connected):
|
||
|
||
```bash
|
||
cd Server
|
||
|
||
# Run EditMode tests (default)
|
||
uv run python -m cli.main editor tests
|
||
|
||
# Run PlayMode tests
|
||
uv run python -m cli.main editor tests --mode PlayMode
|
||
|
||
# Run async and poll for results (useful for long test runs)
|
||
uv run python -m cli.main editor tests --async
|
||
uv run python -m cli.main editor poll-test <job_id> --wait 60
|
||
|
||
# Show only failed tests
|
||
uv run python -m cli.main editor tests --failed-only
|
||
```
|
||
|
||
**Using MCP tools directly** (from any MCP client):
|
||
|
||
```
|
||
run_tests(mode="EditMode")
|
||
run_tests(mode="PlayMode", init_timeout=120000) # PlayMode may need longer init due to domain reload
|
||
get_test_job(job_id="<id>", wait_timeout=60)
|
||
```
|
||
|
||
### Local headless test harness
|
||
|
||
`python tools/local_harness.py` boots a headless Editor and runs the smoke + EditMode + PlayMode legs over the bridge — the same entrypoint CI uses. See [Testing → Local headless test harness](./testing.md#local-headless-test-harness) for the flags and exit-code contract.
|
||
|
||
### Code Coverage
|
||
|
||
```bash
|
||
cd Server
|
||
uv run pytest tests/ --cov --cov-report=html
|
||
open htmlcov/index.html
|
||
```
|
||
|
||
## Compatibility Notes
|
||
|
||
The Unity package currently declares Unity `2021.3` as its minimum version in `MCPForUnity/package.json`. Code that uses Unity API conditionals (`UNITY_2022_3_OR_NEWER`, `UNITY_2023_1_OR_NEWER`, `UNITY_6000_*`) should be tested against the affected editor line, not just the oldest supported version.
|
||
|
||
For compatibility PRs, note the exact editor versions you tested in the PR body. If a Git package URL is involved, include the resolved `packages-lock.json` hash.
|
||
|
||
## Troubleshooting During Development
|
||
|
||
- **Unity still loads an old Git package**: close Unity, check `Packages/packages-lock.json`, then refresh Package Manager. If needed, remove only the stale `Library/PackageCache/com.coplaydev.unity-mcp@<hash>` folder while Unity is closed.
|
||
- **Unity opens in Safe Mode after changing package source**: the package failed to compile before MCP can start. Fix the compile errors first; the MCP server cannot recover from package compile failures.
|
||
- **Server changes are not picked up**: make sure **Server Source Override** points to your local `Server/` directory and **Dev Mode (Force fresh server install)** is enabled.
|
||
- **Stdio tool visibility looks stale**: call `manage_tools(action="sync")` or restart the MCP session. HTTP mode can push `tools/list_changed` notifications automatically.
|
||
- **Multiple Unity editors are open**: use `mcpforunity://instances` and `set_active_instance` to confirm which project the server is targeting.
|
||
## Unity-version CI matrix
|
||
|
||
CI exercises the package across multiple Unity versions to catch breaks in `#if UNITY_*_OR_NEWER` branches. The matrix is configured in `tools/unity-versions.json` and consumed by `.github/workflows/unity-tests.yml`.
|
||
|
||
**Every PR gets a unity-tests status check on open** (mirrors `python-tests.yml`). For same-repo PRs the default Unity 6 leg actually runs; for fork PRs the workflow appears but skips with a "missing license secrets" notice until a maintainer applies `safe-to-test` (existing secret-safety gate). The full 4-version matrix is opt-in via the `full-matrix` label.
|
||
|
||
**When the full matrix runs (all 4 versions in parallel):**
|
||
|
||
- Push to `beta` (the release gate).
|
||
- `workflow_call` from `beta-release.yml` / `release.yml`.
|
||
- Manual `workflow_dispatch` from the Actions tab.
|
||
- Any PR (in-repo or fork) labeled with **`full-matrix`** — apply when your change touches compat shims, conditional compilation, or anything else version-sensitive. Triggers a full-matrix run on the next `pull_request` or `pull_request_target` event. Cost is ~6-8 min wall clock vs ~3 min for the default leg.
|
||
|
||
Fork PRs still need `safe-to-test` as the base gate (so secrets are exposed against reviewed-only fork code); `full-matrix` is layered on top for fork-PR full-matrix runs.
|
||
|
||
**Default (single leg)** — every other path runs only against `defaultVersion` from `tools/unity-versions.json` (currently Unity 6.0 LTS, `6000.0.75f1`). The `floor` role (`2021.3.45f2`) still identifies the package minimum and runs as part of the full matrix; it's no longer the default-leg version.
|
||
|
||
## Local Unity-version parity check
|
||
|
||
The same `tools/unity-versions.json` drives a local script so you can reproduce CI behavior before pushing.
|
||
|
||
The script has two runners. Use whichever fits your setup:
|
||
|
||
| Runner | When to use | Cost |
|
||
|---|---|---|
|
||
| **Local Unity Hub** (default) | You already have one or more matrix versions installed via Unity Hub. Fastest. | Disk: each editor is 3-6 GB. |
|
||
| **GameCI Docker** (`--docker`) | You don't want to install every editor locally. Same containers CI uses. | One-time pull is 5-15 GB per version. On Apple Silicon, expect ~5-10× slowdown from amd64 emulation. |
|
||
|
||
### Local Unity Hub mode
|
||
|
||
```bash
|
||
# Compile-only check across every locally-installed Unity in the matrix (~30-60s warm per version).
|
||
tools/check-unity-versions.sh
|
||
|
||
# Full EditMode test run — matches what CI runs end-to-end.
|
||
tools/check-unity-versions.sh --full
|
||
|
||
# Filter to one version family.
|
||
tools/check-unity-versions.sh --only 6000.0
|
||
```
|
||
|
||
Windows uses the PowerShell companion:
|
||
|
||
```powershell
|
||
pwsh .\tools\check-unity-versions.ps1
|
||
pwsh .\tools\check-unity-versions.ps1 -Full
|
||
pwsh .\tools\check-unity-versions.ps1 -Only 6000.0
|
||
```
|
||
|
||
Versions not installed via Unity Hub are skipped — the script never forces you to install every editor in the matrix. Install the versions you most care about (typically the floor `2021.3.45f2` and your daily-driver Unity 6) and CI / Docker mode cover the rest.
|
||
|
||
### GameCI Docker mode (no Unity Hub install required)
|
||
|
||
```bash
|
||
tools/check-unity-versions.sh --docker # all matrix versions, compile-only
|
||
tools/check-unity-versions.sh --docker --full # full EditMode run
|
||
tools/check-unity-versions.sh --docker --only 2022.3 # one version family
|
||
```
|
||
|
||
```powershell
|
||
pwsh .\tools\check-unity-versions.ps1 -Docker
|
||
pwsh .\tools\check-unity-versions.ps1 -Docker -Full
|
||
```
|
||
|
||
**One-time setup: get a Unity license**
|
||
|
||
GameCI containers still need an activated Unity license. Free Personal activations work fine and are tied to the machine, not the editor version — so a single `.ulf` covers every version in the matrix.
|
||
|
||
```bash
|
||
# 1. Generate the request file (.alf) — outputs Unity_v<version>.alf in the current directory.
|
||
docker run --rm -v "$PWD":/work unityci/editor:ubuntu-2021.3.45f2-base-3 \
|
||
/opt/unity/Editor/Unity -batchmode -nographics -quit \
|
||
-createManualActivationFile -logFile /dev/stdout
|
||
|
||
# 2. Upload Unity_v<version>.alf at https://license.unity3d.com/manual
|
||
# Choose Personal license → save the resulting .ulf file.
|
||
|
||
# 3. Export the .ulf contents (add to ~/.zshrc or ~/.bashrc to persist):
|
||
export UNITY_LICENSE="$(cat /path/to/Unity_v<version>.ulf)"
|
||
|
||
# 4. Run the check.
|
||
tools/check-unity-versions.sh --docker
|
||
```
|
||
|
||
PowerShell equivalent for step 3: `$env:UNITY_LICENSE = Get-Content C:\path\to\Unity_v<version>.ulf -Raw`.
|
||
|
||
The same `UNITY_LICENSE` secret is what `.github/workflows/unity-tests.yml` uses in CI — once you have it, your local Docker runs and CI behave identically.
|
||
|
||
**Coverage gap on Apple Silicon Macs**: GameCI publishes only `linux/amd64` images. Docker Desktop runs them under Rosetta/QEMU at ~5-10× the native amd64 speed. A compile that takes 30s on Intel takes 3-5 min on M-series. Still faster than installing four Unity editors, but plan for it.
|
||
|
||
**Opt-in pre-push hook**
|
||
|
||
```bash
|
||
tools/install-hooks.sh # installs .git/hooks/pre-push (idempotent)
|
||
tools/install-hooks.sh --uninstall # removes our hooks
|
||
```
|
||
|
||
Once installed, `git push` runs the compile-only check first when the push touches `MCPForUnity/Editor/**`, `MCPForUnity/Runtime/**`, `TestProjects/UnityMCPTests/**`, `tools/unity-versions.json`, or `.github/workflows/unity-tests.yml`. Pushes that touch only docs, Server/, or unrelated files skip the check.
|
||
|
||
To bypass for a single push: `git push --no-verify`.
|
||
|
||
**Bumping the matrix**
|
||
|
||
Edit `tools/unity-versions.json` and update CI + local scripts both in one commit. The file is the single source of truth.
|
||
|