chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
---
|
||||
id: docs
|
||||
slug: /contributing/docs
|
||||
title: Docs Workflow
|
||||
sidebar_label: Docs Workflow
|
||||
description: How the documentation site is built, what's auto-generated, and how to ship a docs change.
|
||||
---
|
||||
|
||||
# Docs Workflow
|
||||
|
||||
The documentation site you're reading is built with **Docusaurus 3** and lives under `/website` in the same repo as the code. Every doc change goes through normal PRs.
|
||||
|
||||
## What's hand-written vs. auto-generated
|
||||
|
||||
| Section | Source | Authored by |
|
||||
|---|---|---|
|
||||
| Getting Started, Guides, Architecture, Contributing, Migrations | `website/docs/<section>/*.md` | hand-written |
|
||||
| **Tool reference** (`/reference/tools/**`) | generated from `Server/src/services/tools/` | `tools/generate_docs_reference.py` |
|
||||
| **Resource catalog** (`/reference/resources/`) | generated from `Server/src/services/resources/` | same generator |
|
||||
|
||||
The generator owns everything inside the front-matter banner. The **examples block** between `<!-- examples:start -->` and `<!-- examples:end -->` is hand-written and **preserved across regenerations**.
|
||||
|
||||
## Editing a hand-written page
|
||||
|
||||
1. Find the file under `website/docs/`.
|
||||
2. Edit the markdown.
|
||||
3. Local preview: `cd website && npm run start` → http://localhost:3000/unity-mcp/
|
||||
4. Commit and PR against `beta`.
|
||||
|
||||
CI runs `npm run build` on every PR via `.github/workflows/docs-deploy.yml`. The PR check fails if the build fails, so dead links and missing pages surface before merge.
|
||||
|
||||
## Adding examples to a generated tool page
|
||||
|
||||
Find the page at `website/docs/reference/tools/<group>/<tool-name>.md`. Look for:
|
||||
|
||||
```html
|
||||
<!-- examples:start -->
|
||||
*No examples yet. Add usage examples here — they will be preserved across regenerations.*
|
||||
<!-- examples:end -->
|
||||
```
|
||||
|
||||
Replace the placeholder text with your example. **Don't move or rename the markers** — the generator uses them to know where your content is.
|
||||
|
||||
Run the generator to verify your examples survive a regeneration round-trip:
|
||||
|
||||
```bash
|
||||
cd Server && uv run python ../tools/generate_docs_reference.py
|
||||
git diff website/docs/reference/tools/<group>/<tool-name>.md
|
||||
# Should show only your additions; the rest of the file is regenerated identically.
|
||||
```
|
||||
|
||||
## Adding a new doc page
|
||||
|
||||
1. Create the `.md` file under the appropriate `website/docs/<section>/`. Front-matter at minimum:
|
||||
|
||||
```yaml
|
||||
---
|
||||
id: my-page
|
||||
slug: /<section>/my-page
|
||||
title: Page Title
|
||||
sidebar_label: Short Label
|
||||
description: One-line description (used in search and OG).
|
||||
---
|
||||
```
|
||||
|
||||
2. Add the page to `website/sidebars.js` under the right category.
|
||||
3. Use **brand-neutral slugs** — never put `mcp-for-unity` or `unity-mcp` in a URL path. The product name lives in `docusaurus.config.js`.
|
||||
|
||||
## When a slug must change
|
||||
|
||||
URL changes break external links. Always add a redirect:
|
||||
|
||||
```js
|
||||
// website/docusaurus.config.js → plugins → @docusaurus/plugin-client-redirects
|
||||
{
|
||||
redirects: [
|
||||
{ from: '/old/slug', to: '/new/slug' },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
## When tool/resource registries change
|
||||
|
||||
The pre-commit hook (installed via `tools/install-hooks.sh`) auto-regenerates `website/docs/reference/` whenever you stage a change under `Server/src/services/{tools,resources,registry}/`. Without the hook:
|
||||
|
||||
```bash
|
||||
cd Server && uv run python ../tools/generate_docs_reference.py
|
||||
git add website/docs/reference/
|
||||
```
|
||||
|
||||
CI (`.github/workflows/docs-generate.yml`) fails the PR if the committed reference is stale.
|
||||
|
||||
## Release notes sync
|
||||
|
||||
`website/docs/releases.md` and the README's "Recent Updates" block are **both** generated from the GitHub Releases API by `tools/sync_release_notes.py`. The script:
|
||||
|
||||
- Uses `gh api` when available, falls back to `urllib` (with `certifi` if installed) otherwise.
|
||||
- Renders the full release history into `releases.md`, grouped by minor version, with each release body in a collapsible `<details>` block.
|
||||
- Replaces the block between `<!-- recent-updates:start -->` and `<!-- recent-updates:end -->` in the root `README.md` with the latest five releases.
|
||||
|
||||
CI keeps both in sync automatically via `.github/workflows/sync-releases.yml`. Triggers are intentionally narrow so the workflow never blocks outsider PRs:
|
||||
|
||||
| Trigger | What happens |
|
||||
|---|---|
|
||||
| `release.{published,edited,unpublished,deleted}` | Sync fires within ~30 seconds and commits directly to `beta` with `[skip ci]`. This is the canonical entry point — the only time the synced files can legitimately go stale. |
|
||||
| `workflow_dispatch` | Manual escape hatch (re-run after a one-off UI edit, or to backfill). |
|
||||
|
||||
**Not triggered on `pull_request`.** A drift check at PR time would fail outsider PRs that edit README for unrelated reasons (typo fix, citation tweak), and the contributor wouldn't have push access to regenerate. The synced files are maintained by the release pipeline, not by PR authors.
|
||||
|
||||
To sync manually:
|
||||
|
||||
```bash
|
||||
python tools/sync_release_notes.py # write
|
||||
python tools/sync_release_notes.py --check # exit non-zero on drift
|
||||
```
|
||||
|
||||
Do not hand-edit `releases.md` or the `recent-updates` block in `README.md` — your change will be overwritten on the next sync.
|
||||
|
||||
## Deploy
|
||||
|
||||
The live site at `https://coplaydev.github.io/unity-mcp/` deploys automatically on push to `beta`. No manual step per change.
|
||||
|
||||
### First-time setup (maintainers only)
|
||||
|
||||
The first deploy requires GitHub Pages to be enabled for the repo:
|
||||
|
||||
1. **Settings → Pages → Source** → choose **GitHub Actions** (not "Deploy from a branch").
|
||||
2. Push to `beta` (or run the `Docs — Build & Deploy` workflow via **Actions → Run workflow**).
|
||||
3. After the deploy job succeeds, the URL appears under **Settings → Pages**.
|
||||
|
||||
The workflow uses `actions/configure-pages@v5` + `actions/deploy-pages@v4`, so once Pages is set to "GitHub Actions" source, the deploy step provisions everything else automatically.
|
||||
|
||||
### Custom domain
|
||||
|
||||
When ready, add a `CNAME` file at `website/static/CNAME` containing the domain (e.g. `unitymcp.dev`), update `url` and `baseUrl` in `docusaurus.config.js`, and configure the DNS provider per [GitHub's custom-domain guide](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site).
|
||||
|
||||
## Markdown format
|
||||
|
||||
Pages use **CommonMark** (Docusaurus `format: 'detect'` mode), not full MDX. That means `{...}` and `<tag>` in body text are literals — no JS-expression parsing. If you need React components, name the file `.mdx` instead of `.md`.
|
||||
|
||||
## Translations
|
||||
|
||||
Chinese versions of `README-DEV.md` and the overview live under `docs/i18n/` and `docs/development/README-DEV-zh.md`. Full Docusaurus i18n setup (with `locales: ['en', 'zh']`) is deferred until the English site is stable.
|
||||
|
||||
## Search
|
||||
|
||||
Local lunr search ships today via `@easyops-cn/docusaurus-search-local`. The Algolia DocSearch application is queued (free for OSS, 4–6 week approval); the swap-in is a config change when the time comes.
|
||||
|
||||
## Typography
|
||||
|
||||
The site uses **Satoshi** (Fontshare) for body and headings and **JetBrains Mono** (Google Fonts) for code. Both load via stylesheet links in `docusaurus.config.js`. Theme overrides live in `website/src/css/custom.css`.
|
||||
@@ -0,0 +1,111 @@
|
||||
# Releasing (Maintainers)
|
||||
|
||||
This repo uses a two-branch flow to keep `main` stable for users:
|
||||
|
||||
- `beta`: integration branch where feature PRs land
|
||||
- `main`: stable branch that should match the latest release tag
|
||||
|
||||
## Release checklist
|
||||
|
||||
### 1) Promote `beta` to `main` via PR
|
||||
|
||||
- Create a PR with:
|
||||
- base: `main`
|
||||
- compare: `beta`
|
||||
- Ensure required CI checks are green.
|
||||
- Merge the PR.
|
||||
|
||||
Release note quality depends on how you merge:
|
||||
|
||||
- Squash-merging feature PRs into `beta` is OK.
|
||||
- Avoid squash-merging the `beta -> main` promotion PR. Prefer a merge commit (or rebase merge) so GitHub can produce better auto-generated release notes.
|
||||
|
||||
### 2) Run the Release workflow (manual)
|
||||
|
||||
- Go to **GitHub → Actions → Release**
|
||||
- Click **Run workflow**
|
||||
- Select:
|
||||
- `patch`, `minor`, or `major`
|
||||
- Run it on branch: `main`
|
||||
|
||||
What the workflow does:
|
||||
|
||||
1. Creates a temporary `release/vX.Y.Z` branch with the version bump commit
|
||||
2. Opens a PR from that branch into `main`
|
||||
3. Auto-merges the PR (or waits for required checks, then merges)
|
||||
4. Creates an annotated tag `vX.Y.Z` on the merged commit
|
||||
5. Creates a GitHub Release for the tag
|
||||
6. Publishes artifacts (Docker / PyPI / MCPB)
|
||||
7. Opens a PR to merge `main` back into `beta` (so `beta` gets the bump)
|
||||
8. Auto-merges the sync PR
|
||||
9. Cleans up the temporary release branch
|
||||
|
||||
### 3) Verify release outputs
|
||||
|
||||
- Confirm a new tag exists: `vX.Y.Z`
|
||||
- Confirm a GitHub Release exists for the tag
|
||||
- Confirm artifacts:
|
||||
- Docker image published with version `X.Y.Z`
|
||||
- PyPI package published (if configured)
|
||||
- `unity-mcp-X.Y.Z.mcpb` attached to the GitHub Release
|
||||
|
||||
## Required repo settings
|
||||
|
||||
### Branch protection (Rulesets)
|
||||
|
||||
The release workflow uses PRs instead of direct pushes, so it works with strict branch protection. No bypass actors are required.
|
||||
|
||||
Recommended ruleset for `main`:
|
||||
|
||||
- Require PR before merging
|
||||
- Allowed merge methods: `merge`, `rebase` (no squash for promotion PRs)
|
||||
- Required approvals: `0` (so automated PRs can merge without human review)
|
||||
- Optionally require status checks
|
||||
|
||||
Recommended ruleset for `beta`:
|
||||
|
||||
- Require PR before merging
|
||||
- Allowed merge methods: `squash` (for feature PRs)
|
||||
- Required approvals: `0` (so the sync PR can auto-merge)
|
||||
|
||||
### Enable auto-merge (required)
|
||||
|
||||
The workflow uses `gh pr merge --auto` to automatically merge PRs once checks pass.
|
||||
|
||||
To enable:
|
||||
|
||||
1. Go to **Settings → General**
|
||||
2. Scroll to **Pull Requests**
|
||||
3. Check **Allow auto-merge**
|
||||
|
||||
Without this setting, the workflow will fall back to direct merge attempts, which may fail if branch protection requires checks.
|
||||
|
||||
## Failure modes and recovery
|
||||
|
||||
### Tag already exists
|
||||
|
||||
The workflow fails if the computed tag already exists. Pick a different bump type or investigate why a tag already exists for that version.
|
||||
|
||||
### Bump PR fails to merge
|
||||
|
||||
If the version bump PR cannot be merged (e.g., required checks fail):
|
||||
|
||||
- The workflow will fail before creating a tag.
|
||||
- Fix the issue, then either:
|
||||
- Manually merge the PR and create the tag/release, or
|
||||
- Close the PR, delete the `release/vX.Y.Z` branch, and re-run the workflow.
|
||||
|
||||
### Sync PR (`main -> beta`) fails
|
||||
|
||||
If the sync PR has merge conflicts:
|
||||
|
||||
- The workflow will fail after the release is published (artifacts are already out).
|
||||
- Manually resolve conflicts in the sync PR and merge it.
|
||||
|
||||
### Leftover release branch
|
||||
|
||||
If the workflow fails mid-run, a `release/vX.Y.Z` branch may remain. Delete it manually before re-running:
|
||||
|
||||
```bash
|
||||
git push origin --delete release/vX.Y.Z
|
||||
```
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
id: testing
|
||||
slug: /contributing/testing
|
||||
title: Testing
|
||||
sidebar_label: Testing
|
||||
description: How to run Python and Unity tests locally, what CI runs, and how to add new tests.
|
||||
---
|
||||
|
||||
# Testing
|
||||
|
||||
Three test suites cover MCP for Unity: Python unit tests, Unity EditMode/PlayMode tests, and a multi-version Unity compile matrix. CI runs all three; you should run the two relevant to your change locally before pushing.
|
||||
|
||||
## Python tests
|
||||
|
||||
Location: `Server/tests/`
|
||||
|
||||
```bash
|
||||
# All tests
|
||||
cd Server && uv run pytest tests/ -v
|
||||
|
||||
# Single file
|
||||
cd Server && uv run pytest tests/test_manage_material.py -v
|
||||
|
||||
# Single test by name pattern
|
||||
cd Server && uv run pytest tests/ -k "test_create_material" -v
|
||||
```
|
||||
|
||||
CI workflow: `.github/workflows/python-tests.yml`. Coverage is uploaded to Codecov on every run.
|
||||
|
||||
### Adding a Python test
|
||||
|
||||
For a new tool `manage_<domain>`, add `Server/tests/test_manage_<domain>.py`. Existing tests are the best template — most are integration-style: they spin up a fake Unity bridge, call the tool, and assert on the dispatched payload.
|
||||
|
||||
## Unity tests
|
||||
|
||||
Location: `TestProjects/UnityMCPTests/Assets/Tests/`
|
||||
|
||||
- **EditMode** (60+ files): tool validation, parser edge cases, scene paging, domain reload resilience, batch execution, AI property matching, scriptable objects, animation, physics, gameobject lifecycle
|
||||
- **PlayMode**: basic integration smoke tests
|
||||
|
||||
To run locally, open `TestProjects/UnityMCPTests` in Unity, then **Window → General → Test Runner**.
|
||||
|
||||
CI runs both modes across a multi-Unity matrix via `.github/workflows/unity-tests.yml`.
|
||||
|
||||
### Local headless test harness
|
||||
|
||||
One command boots a headless Hub-licensed Editor against `TestProjects/UnityMCPTests` and runs the smoke + EditMode + PlayMode legs over the bridge, then tears down:
|
||||
|
||||
```bash
|
||||
python tools/local_harness.py
|
||||
```
|
||||
|
||||
This is the same entrypoint CI uses — `.github/workflows/e2e-bridge.yml` collapses its boot/wait/discover/run-smoke shell into this invocation.
|
||||
|
||||
Key flags:
|
||||
|
||||
- `--legs smoke,editmode,playmode` — subset of legs to run.
|
||||
- `--project-path TestProjects/UnityMCPTests` — Unity project to boot (repo-relative or absolute).
|
||||
- `--reuse` — attach to an already-resident bridge instead of booting one.
|
||||
- `--keep-alive` — leave the Editor running after the legs (no teardown).
|
||||
- `--no-warmup` — skip the warm-up import phase.
|
||||
|
||||
Exit-code contract: `0` all blocking legs passed, `1` a blocking-leg regression, `2` bridge unreachable / setup failure, `3` project does not compile, `4` no Unity license / Hub seat, `5` Editor binary/version not found.
|
||||
|
||||
It needs a Hub-activated Editor locally (no ULF/serial); none of the CI license staging applies.
|
||||
|
||||
### Adding a Unity test
|
||||
|
||||
Mirror the C# tool you're adding. For `ManageNavigation.cs` in `MCPForUnity/Editor/Tools/`, create `TestProjects/UnityMCPTests/Assets/Tests/EditMode/Editor/ManageNavigationTests.cs`. Use the existing assembly definition (`MCPForUnityTests.Editor.asmdef`) so the suite picks it up automatically.
|
||||
|
||||
## Multi-version compile matrix
|
||||
|
||||
This is the most common pre-push surprise: code that builds on your Unity version fails on another supported version because of an API rename. The local matrix check prevents that.
|
||||
|
||||
```bash
|
||||
tools/check-unity-versions.sh # compile-only across installed Unity Hub editors
|
||||
tools/check-unity-versions.sh --full # full EditMode test run on each version
|
||||
```
|
||||
|
||||
The matrix is `tools/unity-versions.json`. The script discovers Unity installations via Unity Hub's standard locations on macOS, Windows, and Linux.
|
||||
|
||||
When you touch anything in `MCPForUnity/Runtime/Helpers/Unity*Compat.cs` or any `#if UNITY_*_OR_NEWER` block, run this. The [Unity Compat Shims](/architecture/unity-compat) doc explains the policy.
|
||||
|
||||
## Pre-push hook
|
||||
|
||||
`tools/install-hooks.sh` installs a pre-push hook that runs `check-unity-versions.sh` in compile-only mode when your push touches Unity-relevant paths. One-time setup:
|
||||
|
||||
```bash
|
||||
tools/install-hooks.sh
|
||||
```
|
||||
|
||||
To bypass for a single push: `git push --no-verify`. Use this when you're pushing docs-only or pure Python changes.
|
||||
|
||||
## Pre-commit hook (docs reference)
|
||||
|
||||
The same install script wires a pre-commit hook that regenerates `website/docs/reference/` whenever you stage a change under `Server/src/services/{tools,resources,registry}/`. CI fails if you skip this and the committed reference drifts — see `.github/workflows/docs-generate.yml`.
|
||||
|
||||
## Stress / load testing
|
||||
|
||||
Two scripts under `tools/`:
|
||||
|
||||
- `stress_mcp.py` — concurrent MCP tool calls; surfaces middleware contention
|
||||
- `stress_editor_state.py` — hammers the `editor_state` resource; surfaces serialization hotspots
|
||||
|
||||
These are not part of CI; run them when you change transport, middleware, or hot-path serialization.
|
||||
|
||||
## What CI actually runs on every PR
|
||||
|
||||
| Workflow | Trigger | Duration | What it asserts |
|
||||
|---|---|---|---|
|
||||
| `python-tests.yml` | `Server/**` changes | ~2 min | `pytest` clean, coverage uploaded |
|
||||
| `unity-tests.yml` | `MCPForUnity/**` / `TestProjects/**` changes | ~15 min × N versions | EditMode + PlayMode tests clean across the matrix |
|
||||
| `docs-deploy.yml` | `website/**`, `docs/**`, tool/resource registry changes | ~1 min (build) | Docusaurus build succeeds; on push to `beta`, deploys to GitHub Pages |
|
||||
| `docs-generate.yml` | same triggers as docs-deploy | ~1 min | Reference docs are not stale; decorator count matches MD count |
|
||||
|
||||
Skip-equivalent: if the only files you changed are README, governance, or unrelated metadata, only the relevant subset of these fires.
|
||||
Reference in New Issue
Block a user