chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:49:17 +08:00
commit 7243d5823b
2201 changed files with 257291 additions and 0 deletions
@@ -0,0 +1,57 @@
---
description: How MCP for Unity tracks adoption with cookieless, aggregate-only analytics — a public PyPI downloads badge plus a private, maintainer-only dashboard.
---
# External analytics
## What is tracked and why
MCP for Unity tracks adoption with several aggregate signals, ordered from most to least honest about *real users*:
- **In-product DAU / WAU** — the true active-user count, deduplicated per anonymous install UUID by the in-product telemetry (`Server/src/core/telemetry.py`). Surfacing it on this dashboard needs a read API on the Coplay telemetry backend.
- **Unique repo cloners / viewers (14-day)** — from the GitHub repository traffic API; a strong proxy for developers actually pulling the code. Only collaborators can read it (needs a token), so it is private by nature.
- **GitHub stars / forks** — real accounts that starred or forked; public, an interest (not usage) signal.
- **PyPI installs** — daily/weekly/monthly download counts for `mcpforunityserver` from the public [pypistats.org](https://pypistats.org/packages/mcpforunityserver) API. **Heavily inflated** by CI, mirrors, `uvx` re-fetches, and Docker rebuilds — an install-event *reach* number, not a user count. A downloads badge appears in the README.
- **Docs traffic** — aggregate pageview totals from [GoatCounter](https://www.goatcounter.com/) when provisioned.
All sources are **cookieless, store no personal data, and expose only aggregates**. The DAU/WAU source is the [in-product telemetry](./telemetry) that runs inside the Unity Editor and is controlled by the user from the MCP for Unity settings window.
## What is public vs private
- **Public:** the PyPI **downloads badge** in the README (download counts are public on PyPI regardless of what we do).
- **Private (maintainer-only):** the unified install + docs-traffic **dashboard**. It is posted to the GitHub Actions run summary, which only repo collaborators can see. There is **no public stats page and no stats file published to the site**.
## Privacy stance
- No cookies, no fingerprinting, no user IDs.
- No PII is transmitted or stored.
- GoatCounter is a privacy-first analytics service; its [privacy policy](https://www.goatcounter.com/help/privacy) commits to not selling data. The GoatCounter dashboard should be kept **private** so traffic numbers stay maintainer-only.
- The maintainer summary contains only counts and a timestamp.
## How data flows
1. A GitHub Actions workflow (`.github/workflows/stats.yml`) runs daily at 06:00 UTC, and on demand via **Run workflow**.
2. `website/scripts/fetch-stats.mjs` fetches PyPI recent-download counts and, when GoatCounter secrets are present, the total pageview count.
3. The script renders a Markdown table that the workflow appends to `$GITHUB_STEP_SUMMARY`.
4. Collaborators read the numbers on the **Actions → Adoption stats** run page. Nothing is committed to the repo or published to the docs site.
## Maintainer provisioning
Stars, forks, and PyPI numbers work out of the box. The two highest-signal rows need setup:
**Unique repo cloners / viewers (GitHub traffic):**
1. Create a fine-grained PAT scoped to `CoplayDev/unity-mcp` with **Repository permissions → Administration: read** (the traffic API requires it; the default `GITHUB_TOKEN` returns 401/403).
2. Add it as the repository **secret** `STATS_GITHUB_TOKEN`.
**In-product DAU / WAU (the true active-user count):**
- The data is already collected by `Server/src/core/telemetry.py` (deduplicated per anonymous install UUID) and POSTed to the Coplay telemetry backend. Surfacing it here needs a **read / aggregate API** on that backend plus a token — back-end work owned by Coplay. Once available, add a `COPLAY_STATS_TOKEN` secret and a fetch in `fetch-stats.mjs`.
**Docs traffic (GoatCounter):**
1. Create a free [goatcounter.com](https://www.goatcounter.com/) site (e.g. code `mcp-for-unity`); keep its dashboard **private**.
2. Generate an API token with read access to stats.
3. Add secrets `GOATCOUNTER_TOKEN` (token) and `GOATCOUNTER_SITE` (site code), and an **Actions variable** `GOATCOUNTER_CODE` (same site code) so the docs build injects the cookieless beacon — collection happens on the public site, the numbers stay private.
Then run **Actions → Adoption stats → Run workflow**. The `stats` workflow needs only `contents: read` — it posts to the run summary and never commits.
+110
View File
@@ -0,0 +1,110 @@
# manage_physics — Complete Feature Patch
A new `manage_physics` MCP tool that gives AI assistants full control over Unity's 3D and 2D physics systems. **21 actions** across 9 categories, with C# Editor implementation, Python MCP service, CLI commands, and tests.
## Actions by Category
### Settings
| Action | Description |
|--------|-------------|
| `ping` | Health check — returns gravity, solver settings, simulation mode |
| `get_settings` | Read physics project settings (3D or 2D) |
| `set_settings` | Write physics project settings (gravity, solver iterations, thresholds, etc.) |
### Collision Matrix
| Action | Description |
|--------|-------------|
| `get_collision_matrix` | Read per-layer collision matrix |
| `set_collision_matrix` | Enable/disable collision between layer pairs |
### Materials
| Action | Description |
|--------|-------------|
| `create_physics_material` | Create PhysicMaterial (3D) or PhysicsMaterial2D assets with friction/bounciness/combine modes |
| `configure_physics_material` | Update properties on an existing physics material asset |
| `assign_physics_material` | Assign a physics material to a GameObject's collider |
### Joints
| Action | Description |
|--------|-------------|
| `add_joint` | Add a joint (hinge, spring, fixed, configurable, etc.) with optional connected body |
| `configure_joint` | Configure motor, limits, spring, drive, and direct properties on an existing joint |
| `remove_joint` | Remove joint(s) from a GameObject by type or all |
### Queries
| Action | Description |
|--------|-------------|
| `raycast` | Single-hit raycast from origin along direction |
| `raycast_all` | Multi-hit raycast returning all intersections |
| `linecast` | Check if anything intersects the line between two points |
| `shapecast` | Cast a shape (sphere, box, capsule) along a direction |
| `overlap` | Find all colliders within a shape (sphere, box, capsule) at a position |
### Forces
| Action | Description |
|--------|-------------|
| `apply_force` | Apply force, torque (or both), force-at-position, or explosion force to Rigidbodies. Supports all ForceModes for 3D and 2D |
### Rigidbody
| Action | Description |
|--------|-------------|
| `get_rigidbody` | Read full Rigidbody state: mass, velocity, position, rotation, damping, constraints, sleep state, centerOfMass |
| `configure_rigidbody` | Set Rigidbody properties (mass, damping, gravity, kinematic, interpolation, collision detection, constraints) |
### Validation
| Action | Description |
|--------|-------------|
| `validate` | Scan scene (or a single target) for physics issues. Paginated results (`page_size`/`cursor`/`next_cursor`) with per-category summary. 7 check categories: non-convex mesh, missing rigidbody, non-uniform scale, fast object with discrete detection, missing physics material, collision matrix, mixed 2D/3D. Smart warning levels — static colliders without Rigidbodies are downgraded to `[Info]` unless the object has an Animator |
### Simulation
| Action | Description |
|--------|-------------|
| `simulate_step` | Step physics simulation in Edit mode (1100 steps). Returns positions, velocities, and angular velocities of active Rigidbodies after stepping. Optional `target` to filter to a specific object |
## Architecture
### C# — 10 files in `MCPForUnity/Editor/Tools/Physics/`
| File | Purpose |
|------|---------|
| `ManagePhysics.cs` | Action dispatcher with `[McpForUnityTool]` registration |
| `PhysicsSettingsOps.cs` | ping, get_settings, set_settings |
| `CollisionMatrixOps.cs` | get_collision_matrix, set_collision_matrix |
| `PhysicsMaterialOps.cs` | create, configure, assign physics materials |
| `JointOps.cs` | add_joint, configure_joint, remove_joint |
| `PhysicsQueryOps.cs` | raycast, raycast_all, linecast, shapecast, overlap |
| `PhysicsForceOps.cs` | apply_force (normal, explosion, torque) |
| `PhysicsRigidbodyOps.cs` | get_rigidbody, configure_rigidbody |
| `PhysicsValidationOps.cs` | validate with pagination and smart warnings |
| `PhysicsSimulationOps.cs` | simulate_step with state reporting |
### Python — 3 files
| File | Purpose |
|------|---------|
| `Server/src/services/tools/manage_physics.py` | MCP tool definition with 21-action Literal type, forwards all params to Unity |
| `Server/src/cli/commands/physics.py` | Full CLI with commands for every action category |
| `Server/tests/test_manage_physics.py` | 19 unit tests covering action forwarding and validation |
### Unity Tests
| File | Purpose |
|------|---------|
| `TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManagePhysicsTests.cs` | 973-line EditMode test suite |
## Key Design Decisions
- **Modular ops classes**: Each physics domain (settings, materials, joints, queries, forces, etc.) gets its own C# class rather than one monolithic handler
- **Auto-dimension detection**: All actions auto-detect 2D vs 3D based on Rigidbody/Rigidbody2D component presence, with optional `dimension` override
- **Unity version compatibility**: Uses `#if UNITY_6000_0_OR_NEWER` for renamed APIs (drag → linearDamping, angularDrag → angularDamping)
- **Pagination on validate**: Large scenes can produce hundreds of warnings — paginated by default (50 per page) with a summary that always shows category counts regardless of page
- **Smart warning levels**: "Collider without Rigidbody" only escalates to a warning if the object has an Animator (suggesting runtime movement); otherwise downgraded to `[Info]`
- **Enriched responses**: Force/explosion actions echo back all applied values. Simulation returns Rigidbody states. Validation returns category breakdowns
## Stats
- **Net change**: +5,985 / 2,965 lines
- **New C# files**: 10 (+ 10 .meta)
- **New Python files**: 3
- **Actions**: 21
- **Tests**: 19 Python + EditMode C# suite
@@ -0,0 +1,82 @@
---
id: project-roadmap
slug: /architecture/project-roadmap
title: Project Roadmap
sidebar_label: Project Roadmap
description: Living document — goals, current focus, mid-term and long-term plans for MCP for Unity. Maintained alongside the GitHub wiki.
---
# Project Roadmap
A living document outlining MCP for Unity's high-level goals, priorities, and planned features. It evolves with community feedback, technical discoveries, and shifting priorities.
For the deep-research **2026 Feature Roadmap** (per-tool API coverage analysis), see [Feature Roadmap 2026](/architecture/roadmap).
**Want to contribute or discuss?** See [How to Contribute or Provide Feedback](#how-to-contribute-or-provide-feedback).
## Overall Vision & Goals
The primary goal is to provide a robust, flexible, and easy-to-use bridge between the Unity Editor and external MCP clients (Claude Desktop, Cursor, and beyond).
Key objectives:
1. **Make onboarding effortless** — setting up the MCP server is a known pain point for the ecosystem and this project; we'll make it easier to get started.
2. **Improve speed & efficiency** — reduce latency and token usage for faster workflows.
3. **Expand integrations** — more MCP clients, better MCP server discovery, improved auth.
4. **Improve developer experience** — clean APIs, clearer docs, maintainable architecture.
5. **Align with real user needs** — prioritize community- and customer-driven improvements from feedback.
## Current Focus
Legend: **Feat** = feature, **Fix** = bug fix, **Imp** = improvement, **Doc** = docs, **Test** = tests, **Tech** = tech debt / refactor, **Arch** = architecture.
- **Doc** — Documentation overhaul to improve clarity, onboarding, and discoverability
- **Imp** — Per-call instance routing — target specific Unity instances on any tool call ([#772](https://github.com/CoplayDev/unity-mcp/pull/772))
- **Fix** — Code Coverage dependency guard for fresh installs ([#540](https://github.com/CoplayDev/unity-mcp/issues/540))
## Mid-Term Plans
Items aimed for further out. Details and priorities are less defined.
- **Feat** — Explore Runtime MCP Operation ([Discussion #781](https://github.com/CoplayDev/unity-mcp/discussions/781))
- **Feat** — Explore adding GenAI plugins for 2D and 3D assets ([Discussion #778](https://github.com/CoplayDev/unity-mcp/discussions/778))
- **Tech** — Re-evaluate script editing capabilities and consolidate
## Long-Term Ideas & Future Directions
Bigger ideas or major features further down the line (>69 months) or requiring significant research / design.
- **Arch** — Dependency Injection to improve testability
- **Feat** — Add more play mode functionality — support MCP during runtime with custom tools would let LLMs interact with user-created games / experiences
## Maybe / Icebox / Backlog
Suggested or considered but not currently planned. May be revisited later.
- Visual scripting integration (e.g., Bolt / PlayMaker)
- **Test** — Test coverage for Tools, networking, and ideally end-to-end
- **Imp** — Docker support for running the MCP server ([Discussion #776](https://github.com/CoplayDev/unity-mcp/discussions/776))
- **Imp** — Tool search / tool filtering to reduce context ([Discussion #777](https://github.com/CoplayDev/unity-mcp/discussions/777))
## Recently Completed
- **Feat** — Per-call `unity_instance` routing via tool arguments ([#772](https://github.com/CoplayDev/unity-mcp/pull/772)) — supersedes CLI flags approach
- **Feat** — HTTP Server Authentication ([#433](https://github.com/CoplayDev/unity-mcp/issues/433))
- **Feat** — Flag to make custom tools available globally or by project ([#416](https://github.com/CoplayDev/unity-mcp/issues/416))
- **Fix** — High resource costs when not in use ([#577](https://github.com/CoplayDev/unity-mcp/issues/577))
- **Imp** — Use MCP for Unity via the CLI ([#544](https://github.com/CoplayDev/unity-mcp/pull/544))
- **Imp** — OpenCode Support ([#498](https://github.com/CoplayDev/unity-mcp/pull/498))
For details on past releases, see [Release Notes](/releases) or the [GitHub Releases page](https://github.com/CoplayDev/unity-mcp/releases).
## How to Contribute or Provide Feedback
1. **Discuss ideas:** use [GitHub Discussions](https://github.com/CoplayDev/unity-mcp/discussions) to discuss roadmap items or propose new ones.
2. **Request features:** [open a new issue](https://github.com/CoplayDev/unity-mcp/issues/new) using the Feature Request template. **Check existing issues first.**
3. **Report bugs:** [open a bug report](https://github.com/CoplayDev/unity-mcp/issues/new). Provide clear steps to reproduce.
4. **Contribute code / docs:** see [CONTRIBUTING.md](https://github.com/CoplayDev/unity-mcp/blob/beta/CONTRIBUTING.md). Look for issues tagged `help wanted` or `good first issue`. Review open [Pull Requests](https://github.com/CoplayDev/unity-mcp/pulls).
5. **Comment on issues / PRs:** provide feedback directly on the issues and PRs linked above.
## Disclaimer
This roadmap is a high-level overview of potential future direction. It is not a commitment or guarantee. Priorities and timelines may change based on community feedback, resource availability, technical challenges, and strategic shifts. Refer to specific GitHub Issues and Milestones for the most granular, up-to-date status.
@@ -0,0 +1,71 @@
---
id: python-layers
slug: /architecture/python-layers
title: Three-Layer Python Design
sidebar_label: Three-Layer Python Design
description: The Python server has three independent surfaces — MCP tools, CLI commands, and resources — that all funnel into the same C# Editor handlers.
---
# Three-Layer Python Design
The Python server (`Server/src/`) exposes three distinct surfaces. They look similar but serve different consumers and are **not auto-generated from each other**.
| Layer | Where | Framework | Consumer | Transport to Unity |
|---|---|---|---|---|
| **MCP Tools** | `Server/src/services/tools/` | FastMCP (`@mcp_for_unity_tool`) | AI assistants via MCP | WebSocket (`send_with_unity_instance`) |
| **CLI Commands** | `Server/src/cli/commands/` | Click (`@click.command`) | Developers in a terminal | HTTP (`run_command`) |
| **Resources** | `Server/src/services/resources/` | FastMCP (`@mcp_for_unity_resource`) | AI assistants, read-only | WebSocket |
Both MCP tools and CLI commands eventually call the same C# `HandleCommand` methods inside `MCPForUnity/Editor/Tools/`. Resources are read-only — they observe state without mutating it.
## Why three layers, not one
Each surface has a different shape of consumer:
- **MCP tools** need rich type annotations (`Annotated[Type, "description"]`) because they're handed to an LLM. The descriptions are the prompt the LLM reads.
- **CLI commands** need composable flags, shell-friendly defaults, and graceful error messages. Click gives those for free.
- **Resources** need to be cheap to call repeatedly because the LLM polls them. They use a lighter decorator and skip the routing middleware.
Trying to autogenerate one layer from another erodes the ergonomics of all three. The cost is keeping the three in sync — which is mostly a discipline problem solved by domain symmetry.
## Domain symmetry
When you add a new domain (say, `manage_navigation`), you write **three** files:
```
Server/src/services/tools/manage_navigation.py # @mcp_for_unity_tool
Server/src/cli/commands/navigation.py # @click.command
MCPForUnity/Editor/Tools/ManageNavigation.cs # [McpForUnityTool]
```
The Python tool and CLI command both invoke the C# handler — they just take different paths to it.
## Tool registration
Tools are auto-discovered by walking `Server/src/services/tools/`. Each `.py` file with `@mcp_for_unity_tool`-decorated functions is imported at server startup; the decorator side-effects populate a global registry (`services.registry`). The registry is also what `tools/generate_docs_reference.py` reads to emit the [tool reference](/reference/tools).
A tool's `group` parameter controls per-session visibility — see [Tool Groups](/guides/tool-groups). `group=None` means the tool is always visible (server meta-tools like `set_active_instance` and `manage_tools`).
## Where the layers diverge from "just call the C# handler"
- **MCP tools** add parameter normalization (camelCase → snake_case via `ParamNormalizerMiddleware`), telemetry, and per-session routing.
- **CLI commands** add `@handle_unity_errors` for terminal-friendly stack traces, and synchronous wrappers around the async core.
- **Resources** skip middleware entirely — they're meant to be hot-path.
## Server entry point
`Server/src/main.py` (~935 lines) is the orchestrator:
1. Builds the FastMCP server
2. Calls `register_all_tools(mcp)` — auto-discovery
3. Calls `register_all_resources(mcp)` — same pattern, different decorator
4. Sets up the WebSocket hub for HTTP transport
5. Configures middleware (telemetry, normalization, instance routing)
6. Starts the transport (`http`/`stdio` from `core.config`)
## Where to read more
- Tool/CLI handler shape: `Server/src/services/tools/manage_material.py` is a canonical example
- Registry: `Server/src/services/registry/tool_registry.py` (~130 LOC, the single source the docs reference generator reads)
- Transport: `Server/src/transport/` — plugin hub (`plugin_hub.py`), websocket client, legacy stdio bridge
- C# side: `MCPForUnity/Editor/Tools/ManageMaterial.cs` is the C# half of `manage_material`
+363
View File
@@ -0,0 +1,363 @@
# Remote Server Auth: Architecture
This document describes the internal design of the API key authentication system used when the MCP for Unity server runs in remote-hosted mode. It is intended for contributors and maintainers.
## Overview
```
MCP Client MCP Server External Auth
(Cursor, etc.) (Python) Service
| | |
| X-API-Key: abc123 | |
| POST /mcp (tool call) | |
|-------------------------->| |
| | |
| UnityInstanceMiddleware.on_call_tool |
| | |
| _resolve_user_id() |
| | |
| | POST /validate |
| | {"api_key": "abc123"} |
| |------------------------------>|
| | |
| | {"valid":true, |
| | "user_id":"user-42"} |
| |<------------------------------|
| | |
| Cache result (TTL) |
| | |
| ctx.set_state("user_id", "user-42") |
| ctx.set_state("unity_instance", "Proj@hash") |
| | |
| PluginHub.send_command_for_instance |
| (user_id scoped session lookup) |
| | |
| Tool result | |
|<--------------------------| |
Unity Plugin MCP Server External Auth
(C# WebSocket) (Python) Service
| | |
| WS /hub/plugin | |
| X-API-Key: abc123 | |
|-------------------------->| |
| | |
| PluginHub.on_connect |
| | POST /validate |
| |------------------------------>|
| | {"valid":true, ...} |
| |<------------------------------|
| | |
| accept() | |
| websocket.state.user_id = "user-42" |
|<--------------------------| |
| | |
| {"type":"register", ...} | |
|-------------------------->| |
| | |
| PluginRegistry.register( |
| ..., user_id="user-42") |
| _user_hash_to_session[("user-42","hash")] = sid |
| | |
| {"type":"registered"} | |
|<--------------------------| |
```
## Components
### ApiKeyService
**File:** `Server/src/services/api_key_service.py`
Singleton service that validates API keys against an external HTTP endpoint.
- **Singleton access:** `ApiKeyService.get_instance()` / `ApiKeyService.is_initialized()`
- **Initialization:** Constructed in `create_mcp_server()` when `config.http_remote_hosted` and `config.api_key_validation_url` are both set.
- **Validation:** `async validate(api_key) -> ValidationResult`
- **Caching:** In-memory dict keyed by raw API key. Entries store `(valid, user_id, metadata, expires_at)`.
- **Retry:** 1 retry with 100ms backoff on timeouts and connection errors.
- **Fail-closed:** Any unrecoverable error returns `ValidationResult(valid=False)`.
### PluginHub (WebSocket Auth Gate)
**File:** `Server/src/transport/plugin_hub.py`
The `on_connect` method validates the API key from the WebSocket handshake headers before accepting the connection.
- Reads `X-API-Key` from `websocket.headers`
- Validates via `ApiKeyService.validate()`
- Stores `user_id` and `api_key_metadata` on `websocket.state` for use during registration
- Rejects with close codes: `4401` (missing), `4403` (invalid), `1013` (service unavailable)
The `_handle_register` method reads `websocket.state.user_id` and passes it to `PluginRegistry.register()`.
The `get_sessions(user_id=None)` and `_resolve_session_id(unity_instance, user_id=None)` methods accept an optional `user_id` to scope session queries in remote-hosted mode.
### PluginRegistry (Dual-Index Session Storage)
**File:** `Server/src/transport/plugin_registry.py`
In-memory registry of connected Unity plugin sessions. Maintains two parallel index maps:
| Index | Key | Used In |
|-------|-----|---------|
| `_hash_to_session` | `project_hash -> session_id` | Local mode |
| `_user_hash_to_session` | `(user_id, project_hash) -> session_id` | Remote-hosted mode |
Both indexes are updated during `register()` and cleaned up during `unregister()`.
Key methods:
- `register(session_id, project_name, project_hash, unity_version, user_id=None)` - Registers a session and updates the appropriate index. If an existing session claims the same key, it is evicted.
- `get_session_id_by_hash(project_hash)` - Local-mode lookup.
- `get_session_id_by_hash(project_hash, user_id)` - Remote-mode lookup.
- `list_sessions(user_id=None)` - Returns sessions filtered by user. Raises `ValueError` if `user_id` is `None` while `config.http_remote_hosted` is `True`, preventing accidental cross-user leaks.
### UnityInstanceMiddleware
**File:** `Server/src/transport/unity_instance_middleware.py`
FastMCP middleware that intercepts all tool and resource calls to inject the active Unity instance and user identity into the request-scoped context.
Entry points:
- `on_call_tool(context, call_next)` - Intercepts tool calls.
- `on_read_resource(context, call_next)` - Intercepts resource reads.
Both delegate to `_inject_unity_instance(context)`, which:
1. Calls `_resolve_user_id()` to extract the user identity from the HTTP request.
2. If remote-hosted mode is active and no `user_id` is resolved, raises `RuntimeError` (surfaces as MCP error).
3. Sets `ctx.set_state("user_id", user_id)`.
4. Looks up or auto-selects the active Unity instance.
5. Sets `ctx.set_state("unity_instance", active_instance)`.
### _resolve_user_id_from_request
**File:** `Server/src/transport/unity_transport.py`
Extracts the `user_id` from the current HTTP request's `X-API-Key` header.
```
_resolve_user_id_from_request()
-> if not config.http_remote_hosted: return None
-> if not ApiKeyService.is_initialized(): return None
-> get_http_headers() from FastMCP dependencies
-> extract "x-api-key" header
-> ApiKeyService.validate(api_key)
-> return result.user_id if valid, else None
```
The middleware calls this indirectly through `_resolve_user_id()`, which adds an early return when not in remote-hosted mode (avoiding the import of FastMCP internals in local mode).
## Request Lifecycle
A complete authenticated MCP tool call follows this path:
1. **HTTP request arrives** at `/mcp` with `X-API-Key: <key>` header.
2. **FastMCP dispatches** the MCP tool call through its middleware chain.
3. **`UnityInstanceMiddleware.on_call_tool`** is invoked.
4. **`_inject_unity_instance`** runs:
- Calls `_resolve_user_id()`, which calls `_resolve_user_id_from_request()`.
- The request function imports `get_http_headers` from FastMCP and reads the `x-api-key` header.
- `ApiKeyService.validate()` checks the cache or calls the external auth endpoint.
- If valid, `user_id` is returned. If invalid or missing, `None` is returned.
- In remote-hosted mode, `None` causes a `RuntimeError`.
5. **`user_id` stored in context** via `ctx.set_state("user_id", user_id)`.
6. **Session key derived** by `get_session_key(ctx)`:
- Priority: `client_id` (if available) > `user:{user_id}` > `"global"`.
- The `user:{user_id}` fallback ensures session isolation when MCP transports don't provide stable client IDs.
7. **Active Unity instance looked up** from `_active_by_key` dict using the session key. If none is set, `_maybe_autoselect_instance` is called (but returns `None` in remote-hosted mode).
8. **Instance injected** via `ctx.set_state("unity_instance", active_instance)`.
9. **Tool executes**, reading the instance from `ctx.get_state("unity_instance")`.
10. **Command routed** through `PluginHub.send_command_for_instance(unity_instance, ..., user_id=user_id)`, which resolves the session using `PluginRegistry.get_session_id_by_hash(project_hash, user_id)`.
## WebSocket Auth Flow
When a Unity plugin connects via WebSocket:
```
Plugin -> WS /hub/plugin (with X-API-Key header)
|
v
PluginHub.on_connect()
|
+-- config.http_remote_hosted && ApiKeyService.is_initialized()?
| |
| +-- No -> accept() (local mode, no auth needed)
| |
| +-- Yes -> read X-API-Key from headers
| |
| +-- No key -> close(4401, "API key required")
| |
| +-- ApiKeyService.validate(key)
| |
| +-- valid=True -> websocket.state.user_id = user_id
| | accept()
| |
| +-- valid=False, "unavailable" in error
| | -> close(1013, "Try again later")
| |
| +-- valid=False -> close(4403, "Invalid API key")
```
After acceptance, when the plugin sends a `register` message, `_handle_register` reads `websocket.state.user_id` and passes it to `PluginRegistry.register()`.
## Session Registry Design
### Local Mode
```
project_hash -> session_id
"abc123" -> "uuid-1"
"def456" -> "uuid-2"
```
A single `_hash_to_session` dict. Any user can see any session. `list_sessions(user_id=None)` returns all sessions.
### Remote-Hosted Mode
```
(user_id, project_hash) -> session_id
("user-A", "abc123") -> "uuid-1"
("user-B", "abc123") -> "uuid-3" (same project, different user)
("user-A", "def456") -> "uuid-2"
```
A separate `_user_hash_to_session` dict with composite keys. Two users working on cloned repos (same `project_hash`) get independent sessions.
### Reconnect Handling
When a Unity editor reconnects (e.g., after domain reload), `register()` detects the existing mapping for the same key and evicts the old session before inserting the new one. This ensures the latest WebSocket connection always wins.
### list_sessions Guard
`list_sessions(user_id=None)` raises `ValueError` when `config.http_remote_hosted` is `True`. This prevents code paths from accidentally listing all users' sessions. Every call site in remote-hosted mode must pass an explicit `user_id`.
## Caching Strategy
`ApiKeyService` maintains an in-memory cache:
```python
# api_key -> (valid, user_id, metadata, expires_at)
_cache: dict[str, tuple[bool, str | None, dict | None, float]]
```
### What Gets Cached
| Response | Cached? | Rationale |
|----------|---------|-----------|
| 200 + `valid: true` | Yes | Definitive valid result |
| 200 + `valid: false` | Yes | Definitive invalid result |
| 401 status | Yes | Definitive rejection |
| 5xx status | No | Transient; retry on next request |
| Timeout | No | Transient; retry on next request |
| Connection error | No | Transient; retry on next request |
| Unexpected exception | No | Transient; retry on next request |
Non-cacheable results use `ValidationResult(cacheable=False)`.
### Cache Lifecycle
- **TTL:** Configurable via `--api-key-cache-ttl` (default: 300 seconds).
- **Expiry:** Checked on read. Expired entries are deleted and re-validated.
- **Invalidation:** `invalidate_cache(api_key)` removes a single key. `clear_cache()` removes all.
- **Concurrency:** Protected by `asyncio.Lock`.
### Revocation Latency
A revoked key continues to work for up to `cache_ttl` seconds. Lower the TTL for faster revocation at the cost of more validation requests.
## Fail-Closed Behaviour
The system fails closed at every boundary:
| Component | Failure | Behaviour |
|-----------|---------|-----------|
| `ApiKeyService._validate_external` | Timeout after retries | `valid=False, cacheable=False` |
| `ApiKeyService._validate_external` | Connection error after retries | `valid=False, cacheable=False` |
| `ApiKeyService._validate_external` | 5xx status | `valid=False, cacheable=False` |
| `ApiKeyService._validate_external` | Unexpected exception | `valid=False, cacheable=False` |
| `PluginHub.on_connect` | Auth service unavailable | Close `1013` (retry hint) |
| `UnityInstanceMiddleware._inject_unity_instance` | No user_id in remote-hosted mode | `RuntimeError` |
API keys are never logged in full. Keys longer than 8 characters are redacted to `xxxx...yyyy` in log messages.
## Session Key Derivation
`UnityInstanceMiddleware.get_session_key(ctx)` determines which dict key to use for storing/retrieving the active Unity instance per session:
```
1. client_id (string, non-empty) -> return client_id
2. ctx.get_state("user_id") -> return "user:{user_id}"
3. fallback -> return "global"
```
- **`client_id`:** Stable per MCP client connection. Preferred when available.
- **`user:{user_id}`:** Used in remote-hosted mode when the MCP transport doesn't provide a stable client ID. Ensures different users don't share instance selections.
- **`"global"`:** Local-dev fallback for single-user scenarios. Unreachable in remote-hosted mode because the auth enforcement raises `RuntimeError` before this point if no `user_id` is available.
## Disabled Features in Remote-Hosted Mode
| Feature | Local Mode | Remote-Hosted Mode | Reason |
|---------|-----------|-------------------|--------|
| Auto-select sole instance | Enabled | Disabled | Implicit behaviour is dangerous with multiple users |
| CLI REST routes | Enabled | Disabled | No auth layer on these endpoints |
| `list_sessions(user_id=None)` | Returns all | Raises `ValueError` | Prevents accidental cross-user session leaks |
## Configuration Flow
```
CLI args / env vars
|
v
main.py: parser.parse_args()
|
+-- config.http_remote_hosted = args or env
+-- config.api_key_validation_url = args or env
+-- config.api_key_login_url = args or env
+-- config.api_key_cache_ttl = args or env (float)
+-- config.api_key_service_token_header = args or env
+-- config.api_key_service_token = args or env
|
+-- Validate: remote-hosted requires validation URL
| (exits with code 1 if missing)
|
v
create_mcp_server()
|
+-- get_unity_instance_middleware() -> registers middleware
|
+-- if remote-hosted + validation URL:
| ApiKeyService(
| validation_url, cache_ttl,
| service_token_header, service_token
| )
|
+-- WebSocketRoute("/hub/plugin", PluginHub)
|
+-- if not remote-hosted:
register CLI routes (/api/command, /api/instances, /api/custom-tools)
```
## Key Files
| File | Role |
|------|------|
| `Server/src/core/config.py` | `ServerConfig` dataclass with auth fields |
| `Server/src/main.py` | CLI argument parsing, startup validation, service initialization |
| `Server/src/services/api_key_service.py` | API key validation singleton with caching and retry |
| `Server/src/transport/plugin_hub.py` | WebSocket auth gate, user-scoped session queries |
| `Server/src/transport/plugin_registry.py` | Dual-index session storage (local + user-scoped) |
| `Server/src/transport/unity_instance_middleware.py` | Per-request user_id and instance injection |
| `Server/src/transport/unity_transport.py` | `_resolve_user_id_from_request` helper |
+307
View File
@@ -0,0 +1,307 @@
# Unity MCP Feature Roadmap 2026
## Research Summary
Six parallel research agents investigated 12+ domains across Unity's API surface. Every domain was assessed for: API stability, implementation complexity, developer value, dead ends, and dependencies.
### Current Tool Coverage (19 tools)
Animation, Asset, Audio, Camera, Components, Editor, GameObjects, Graphics, Lighting, Material, Prefabs, ProBuilder, Scene, Script, ScriptableObject, Shader, Texture, UI, VFX
---
## Prioritized Implementation Plan
### Tier 1: Foundation (Unblocks Everything Else)
#### 1. `manage_packages` — Package Management
| Dimension | Assessment |
|-----------|-----------|
| **Value** | Very High |
| **Complexity** | Medium |
| **Actions** | ~14 |
| **Dependencies** | None (core Unity) |
| **Audience** | 100% of users |
**Why first**: Directly unblocks XR, Addressables, Input System, and any workflow requiring optional packages. Currently the #1 gap — AI can detect missing packages but cannot install them.
**Key APIs**: `PackageManager.Client.Add/Remove/List/Search/Embed` (all public, async). Scoped registries via `manifest.json` editing. Assembly definitions as JSON files.
**Actions**: `add_package`, `remove_package`, `add_and_remove`, `list_packages`, `search_packages`, `get_package_info`, `embed_package`, `resolve_packages`, `list_registries`, `add_registry`, `remove_registry`, `list_assemblies`, `create_asmdef`, `ping`
**Challenge**: Domain reload after install/remove kills state. Solution: `PendingResponse` + `McpJobStateStore` (existing patterns).
#### 2. QoL: Extend Existing Tools
| Dimension | Assessment |
|-----------|-----------|
| **Value** | High |
| **Complexity** | Low |
| **Effort** | 1-2 days each |
**2a. Multi-scene editing** (extend `manage_scene`):
- `open_additive`, `close_scene`, `set_active_scene`, `get_loaded_scenes`, `save_setup`, `restore_setup`
- `add_to_build`, `remove_from_build`, `set_build_enabled`
**2b. Scene validation** (extend `manage_scene` or new resource):
- `validate` — detect missing scripts, broken prefab references, null references
- `repair` — auto-fix missing scripts
- Uses `GameObjectUtility.GetMonoBehavioursWithMissingScriptCount()`, `PrefabUtility.GetPrefabInstanceStatus()`
**2c. Undo/Redo** (extend `manage_editor`):
- `undo`, `redo``Undo.PerformUndo()` / `Undo.PerformRedo()`
**2d. Scene templates** (extend `manage_scene`):
- `create_from_template` — presets: `3d_basic`, `3d_urp`, `2d_basic`, `empty`
---
### Tier 2: Core Game Systems (High Value, Low-Medium Complexity)
#### 3. `manage_physics` — Physics System
| Dimension | Assessment |
|-----------|-----------|
| **Value** | High |
| **Complexity** | Low-Medium |
| **Actions** | ~18 |
| **Dependencies** | None (core Unity) |
| **Audience** | ~90% of games |
**Why**: Almost every game uses physics. Existing `manage_components` can add Rigidbody/Collider but lacks global settings, collision matrix, raycasting, and simulation.
**Key APIs**: All public and stable since Unity 5+. `Physics.*` static class, all component properties, `PhysicsMaterial` asset creation.
**Actions by category**:
- Rigidbody: `add_rigidbody`, `configure_rigidbody`
- Colliders: `add_collider`, `configure_collider`, `fit_collider`
- Materials: `create_physics_material`, `configure_physics_material`
- Joints: `add_joint`, `configure_joint`, `remove_joint`
- Global: `get_settings`, `set_settings`, `get_collision_matrix`, `set_collision_matrix`
- Simulation: `simulate_step`, `sync_transforms`
- Queries: `raycast`, `overlap_sphere`
**Dead ends**: Physics callbacks don't fire in editor simulation. Can't simulate individual objects. Physics debug viz is internal.
#### 4. `manage_input` — Input System
| Dimension | Assessment |
|-----------|-----------|
| **Value** | High |
| **Complexity** | Medium-Low |
| **Actions** | ~18 |
| **Dependencies** | `com.unity.inputsystem` (must install) |
| **Audience** | ~95% of games |
**Why**: Every game needs input. Tedious manual setup. Perfect for presets ("set up FPS controls with gamepad support").
**Key APIs**: `InputActionSetupExtensions` — clean fluent API. `InputActionAsset` is a ScriptableObject serialized as JSON. All public.
**Actions**: `create_input_actions`, `get_input_actions_info`, `add_action_map`, `remove_action_map`, `add_action`, `remove_action`, `set_action_properties`, `add_binding`, `add_composite_binding`, `remove_binding`, `add_control_scheme`, `remove_control_scheme`, `setup_player_input`, `create_preset` (fps, third_person, platformer, vehicle, ui), `ping`
**Dead ends**: InputSettings modification is fragile. Active input handler switching requires restart. Generated C# wrapper class toggling is fragile.
#### 5. `manage_navigation` — NavMesh & AI Navigation
| Dimension | Assessment |
|-----------|-----------|
| **Value** | High |
| **Complexity** | Low-Medium |
| **Actions** | ~22 |
| **Dependencies** | None (core) + `com.unity.ai.navigation` (optional) |
| **Audience** | ~70% of 3D games |
**Why**: Navigation is fundamental. NavMesh baking is a pain point for beginners. Enables complete AI character workflow.
**Key APIs**: Core `NavMesh.*` static methods are built-in (no package). `NavMeshSurface`/`NavMeshLink`/`NavMeshModifier` from AI Navigation package (optional).
**Actions**: `navmesh_bake`, `navmesh_clear`, `navmesh_sample_position`, `navmesh_calculate_path`, `navmesh_raycast`, `agent_add`, `agent_configure`, `agent_set_destination`, `obstacle_add`, `obstacle_configure`, `surface_add`, `surface_configure`, `link_add`, `link_configure`, `modifier_add`, `modifier_volume_add`, `ping`, etc.
**Resource**: `mcpforunity://scene/navigation`
**Dead ends**: Can't create new NavMesh area types programmatically. No visual NavMesh debugging API.
---
### Tier 3: Content Creation (High Value, Medium Complexity)
#### 6. `manage_terrain` — Terrain System
| Dimension | Assessment |
|-----------|-----------|
| **Value** | High |
| **Complexity** | Medium |
| **Actions** | ~24 |
| **Dependencies** | None (core Unity) |
| **Audience** | ~40% of 3D games |
**Why**: Enables prompt-driven terrain generation. "Create mountainous terrain with snow above 80% height" becomes possible. Procedural generation (Perlin, height/slope-based painting) is the killer feature.
**Key APIs**: `TerrainData.SetHeights/GetHeights`, `SetAlphamaps`, `SetHoles`, tree/detail placement. All public.
**Actions**: `create_terrain`, `get_info`, `get_heights`, `set_heights`, `generate_heights` (Perlin, ridged, fbm), `flatten`, `smooth_heights`, `add_layer`, `paint_layer`, `paint_layer_by_height`, `paint_layer_by_slope`, `set_tree_prototypes`, `add_trees`, `scatter_trees`, `set_detail_prototypes`, `scatter_details`, `set_holes`, `set_neighbors`, `set_settings`, `ping`
**Critical design**: Heightmap operations must be region-based (never full map). Procedural generation runs C#-side. Large data stays server-side.
#### 7. `manage_timeline` — Timeline System
| Dimension | Assessment |
|-----------|-----------|
| **Value** | High |
| **Complexity** | Medium |
| **Actions** | ~25 |
| **Dependencies** | `com.unity.timeline` (included by default) |
| **Audience** | ~35% of games |
**Why**: Unlocks programmatic cutscene/sequence construction — a unique AI workflow. "Create a 10-second cutscene where the camera pans, the door opens at 2s, and music fades in at 5s."
**Key APIs**: `TimelineAsset.CreateTrack<T>()`, `TrackAsset.CreateClip<T>()`, `PlayableDirector.SetGenericBinding()`, `SignalAsset/Emitter/Receiver`. All public, remarkably complete.
**Actions**: `create_timeline`, `get_timeline_info`, `add_track`, `remove_track`, `add_clip`, `set_clip_properties`, `move_clip`, `setup_director`, `set_binding`, `get_bindings`, `set_director_properties`, `create_signal`, `add_signal_emitter`, `setup_signal_receiver`, `add_group`, `ping`
**Synergies**: `manage_animation` (clips), `manage_camera` (Cinemachine bindings), `manage_scene`.
#### 8. `manage_netcode` — Networking / Multiplayer
| Dimension | Assessment |
|-----------|-----------|
| **Value** | High |
| **Complexity** | Medium |
| **Actions** | ~20 |
| **Dependencies** | `com.unity.netcode.gameobjects` |
| **Audience** | ~64% of developers |
**Why**: Largest untapped audience. Zero competition in MCP space. Networking setup is error-prone and boilerplate-heavy.
**Key APIs**: `NetworkManager`, `NetworkObject`, `NetworkTransform`, `NetworkAnimator`, `NetworkRigidbody` — all standard MonoBehaviours. `NetworkPrefabsList` — ScriptableObject.
**Actions**: `setup_create_manager`, `setup_configure_manager`, `prefab_create_list`, `prefab_register`, `prefab_make_network`, `component_add_network_object`, `component_add_network_transform`, `component_add_network_animator`, `component_add_network_rigidbody`, `codegen_network_behaviour`, `codegen_player_controller`, `validate`, `list_network_objects`, `ping`
**Unique value**: Code generation for `NetworkBehaviour` scripts with RPCs, `NetworkVariable<T>` declarations. The `validate` action catches common misconfigurations before runtime.
---
### Tier 4: Build & Deploy (Very High Value, Higher Complexity)
#### 9. `manage_build` — Build Pipeline
| Dimension | Assessment |
|-----------|-----------|
| **Value** | Very High |
| **Complexity** | Medium-High |
| **Actions** | ~15 |
| **Dependencies** | None (Addressables optional) |
**Why**: Essential for CI/CD workflows, platform management, and project configuration.
**Actions**: `build_player`, `build_status`, `switch_platform`, `get_platform`, `get_player_settings`, `set_player_settings`, `get_define_symbols`, `set_define_symbols`, `get_build_scenes`, `set_build_scenes`, `get_build_profile`, `set_build_profile`, `list_build_profiles`, `ping`
**Challenge**: `BuildPipeline.BuildPlayer` is synchronous and blocks the editor thread. Non-blocking actions (PlayerSettings, define symbols) are straightforward. Build triggering needs `EditorApplication.delayCall` + poll-based status.
#### 10. `manage_addressables` — Addressable Assets
| Dimension | Assessment |
|-----------|-----------|
| **Value** | High |
| **Complexity** | Medium |
| **Actions** | ~23 |
| **Dependencies** | `com.unity.addressables` |
**Why**: Unity's recommended asset management for any project needing dynamic loading, DLC, or reduced build sizes.
**Actions**: `group_create`, `group_remove`, `group_list`, `entry_add`, `entry_remove`, `entry_move`, `entry_set_address`, `entry_find`, `label_add`, `label_remove`, `label_list`, `label_set`, `profile_list`, `profile_get`, `profile_set`, `profile_set_active`, `build_content`, `build_update`, `build_clean`, `get_settings`, `set_settings`, `ping`
**Resources**: `mcpforunity://addressables/groups`, `mcpforunity://addressables/settings`
---
### Tier 5: Specialized Domains (Medium-High Value, Higher Complexity)
#### 11. `manage_xr` — XR / VR / AR
| Dimension | Assessment |
|-----------|-----------|
| **Value** | Medium-High |
| **Complexity** | HIGH |
| **Actions** | ~25 |
| **Dependencies** | 3-6 packages |
| **Audience** | ~18% of developers |
**Why**: XR setup is notoriously painful. Meta has 10 tools but only covers Meta-specific SDK — cross-platform XRI, AR Foundation, and project-level setup are our opportunity.
**Recommendation**: Target XRI 3.0+ only. Focus on project setup + XR Origin creation first. Defer AR Foundation to later phase. Requires `manage_packages` first.
#### 12. `manage_tilemap` — 2D Tilemap (split from broader 2D)
| Dimension | Assessment |
|-----------|-----------|
| **Value** | Medium-High |
| **Complexity** | Medium |
| **Actions** | ~15 |
| **Dependencies** | None (core) + optional extras |
**Why**: Tilemap is the highest-value subset of 2D tooling. AI-assisted tilemap population is a strong use case.
**Actions**: `create`, `set_tile`, `set_tiles`, `fill_region`, `clear`, `get_info`, `swap_tile`, `add_collider`, `tile_create`, `tile_create_rule`, `ping`
#### 13. `manage_optimization` — Scene Optimization
| Dimension | Assessment |
|-----------|-----------|
| **Value** | Medium |
| **Complexity** | Medium |
| **Actions** | ~10 |
| **Dependencies** | None (core Unity) |
**Actions**: `set_static_flags`, `get_static_flags`, `set_static_flags_recursive`, `occlusion_bake`, `occlusion_cancel`, `occlusion_status`, `occlusion_clear`, `configure_lod`, `get_lod_info`, `auto_lod`
---
## Master Priority Matrix
| # | Tool | Value | Complexity | Deps | Actions | Audience |
|---|------|-------|-----------|------|---------|----------|
| 1 | `manage_packages` | Very High | Medium | 0 | 14 | 100% |
| 2 | QoL extensions | High | Low | 0 | ~15 | 100% |
| 3 | `manage_physics` | High | Low-Med | 0 | 18 | 90% |
| 4 | `manage_input` | High | Med-Low | 1 | 18 | 95% |
| 5 | `manage_navigation` | High | Low-Med | 0-1 | 22 | 70% |
| 6 | `manage_terrain` | High | Medium | 0 | 24 | 40% |
| 7 | `manage_timeline` | High | Medium | 1 | 25 | 35% |
| 8 | `manage_netcode` | High | Medium | 1-2 | 20 | 64% |
| 9 | `manage_build` | Very High | Med-High | 0 | 15 | 100% |
| 10 | `manage_addressables` | High | Medium | 1 | 23 | 30% |
| 11 | `manage_xr` | Med-High | HIGH | 3-6 | 25 | 18% |
| 12 | `manage_tilemap` | Med-High | Medium | 0-2 | 15 | 25% |
| 13 | `manage_optimization` | Medium | Medium | 0 | 10 | 50% |
## Confirmed Dead Ends (Do Not Implement)
| Feature | Reason |
|---------|--------|
| Shader Graph node creation | Internal API, no public editor scripting |
| VFX Graph node editing | Internal visual graph API |
| Terrain brush stroke simulation | UI-based painting system, no programmatic API |
| XR runtime testing | Requires headset or Play mode |
| Multiplayer session testing | Requires multiple Play mode instances |
| Physics callbacks in editor sim | Unity blocks for safety |
| Tile Palette editing | No public API |
| 2D Animation bone rigging | Deep, undocumented editor API |
| Unity Cloud Build | Separate REST API, not Editor scripting |
| Frame Debugger window | Internal editor utility |
| Custom NavMesh area creation | Hard-coded to 32 slots, no creation API |
## Dependencies Graph
```
manage_packages (Tier 1)
|
+---> manage_input (requires com.unity.inputsystem)
+---> manage_netcode (requires com.unity.netcode.gameobjects)
+---> manage_addressables (requires com.unity.addressables)
+---> manage_xr (requires 3-6 packages)
+---> manage_timeline (usually pre-installed)
+---> manage_navigation (optional com.unity.ai.navigation)
No package dependency:
manage_physics, manage_terrain, manage_build,
manage_optimization, QoL extensions, manage_tilemap (core)
```
## Estimated Total Scope
- **13 new tools/extensions** across 5 tiers
- **~250+ new actions** total
- **~5 new resources**
- Zero internal/private API hacks needed — all public, stable Unity APIs
---
*Generated 2026-03-08 by 6 parallel research agents analyzing Unity 6 APIs, documentation, forums, and source references.*
+172
View File
@@ -0,0 +1,172 @@
# MCP for Unity Telemetry
MCP for Unity includes privacy-focused, anonymous telemetry to help us improve the product. This document explains what data is collected, how to opt out, and our privacy practices.
## 🔒 Privacy First
- **Anonymous**: We use randomly generated UUIDs - no personal information
- **Non-blocking**: Telemetry never interferes with your Unity workflow
- **Easy opt-out**: Simple environment variable or Unity Editor setting
- **Transparent**: All collected data types are documented here
## 📊 What We Collect
### Usage Analytics
- **Tool Usage**: Which MCP tools you use (manage_script, manage_scene, etc.)
- **Performance**: Execution times and success/failure rates
- **System Info**: Unity version, platform (Windows/Mac/Linux), MCP version
- **Milestones**: First-time usage events (first script creation, first tool use, etc.)
### Technical Diagnostics
- **Connection Events**: Bridge startup/connection success/failures
- **Error Reports**: Anonymized error messages (truncated to 200 chars)
- **Server Health**: Startup time, connection latency
### What We **DON'T** Collect
- ❌ Your code or script contents
- ❌ Project names, file names, or paths
- ❌ Personal information or identifiers
- ❌ Sensitive project data
- ❌ IP addresses (beyond what's needed for HTTP requests)
## 🚫 How to Opt Out
### Method 1: Environment Variable (Recommended)
Set any of these environment variables to `true`:
```bash
# Disable all telemetry
export DISABLE_TELEMETRY=true
# MCP for Unity specific
export UNITY_MCP_DISABLE_TELEMETRY=true
# MCP protocol wide
export MCP_DISABLE_TELEMETRY=true
```
### Method 2: Unity Editor (Coming Soon)
In Unity Editor: `Window > MCP for Unity > Settings > Disable Telemetry`
### Method 3: Manual Config
Add to your MCP client config:
```json
{
"env": {
"DISABLE_TELEMETRY": "true"
}
}
```
## 🔧 Technical Implementation
### Architecture
- **Python Server**: Core telemetry collection and transmission
- **Unity Bridge**: Local event collection from Unity Editor
- **Anonymous UUIDs**: Generated per-installation for aggregate analytics
- **Thread-safe**: Non-blocking background transmission
- **Fail-safe**: Errors never interrupt your workflow
### Data Storage
Telemetry data is stored locally in:
- **Windows**: `%APPDATA%\UnityMCP\`
- **macOS**: `~/Library/Application Support/UnityMCP/`
- **Linux**: `~/.local/share/UnityMCP/`
Files created:
- `customer_uuid.txt`: Anonymous identifier
- `milestones.json`: One-time events tracker
### Data Transmission
- **Endpoint**: `https://api-prod.coplay.dev/telemetry/events`
- **Method**: HTTPS POST with JSON payload
- **Retry**: Background thread with graceful failure
- **Timeout**: 10 second timeout, no retries on failure
## 📈 How We Use This Data
### Product Improvement
- **Feature Usage**: Understand which tools are most/least used
- **Performance**: Identify slow operations to optimize
- **Reliability**: Track error rates and connection issues
- **Compatibility**: Ensure Unity version compatibility
### Development Priorities
- **Roadmap**: Focus development on most-used features
- **Bug Fixes**: Prioritize fixes based on error frequency
- **Platform Support**: Allocate resources based on platform usage
- **Documentation**: Improve docs for commonly problematic areas
### What We Don't Do
- ❌ Sell data to third parties
- ❌ Use data for advertising/marketing
- ❌ Track individual developers
- ❌ Store sensitive project information
## 🛠️ For Developers
### Custom Telemetry Events
```python
core.telemetry import record_telemetry, RecordType
record_telemetry(RecordType.USAGE, {
"custom_event": "my_feature_used",
"metadata": "optional_data"
})
```
### Telemetry Status Check
```python
core.telemetry import is_telemetry_enabled
if is_telemetry_enabled():
print("Telemetry is active")
else:
print("Telemetry is disabled")
```
## 📋 Data Retention Policy
- **Aggregated Data**: Retained indefinitely for product insights
- **Raw Events**: Automatically purged after 90 days
- **Personal Data**: None collected, so none to purge
- **Opt-out**: Immediate - no data sent after opting out
## 🤝 Contact & Transparency
- **Questions**: [Discord Community](https://discord.gg/y4p8KfzrN4)
- **Issues**: [GitHub Issues](https://github.com/CoplayDev/unity-mcp/issues)
- **Privacy Concerns**: Create a GitHub issue with "Privacy" label
- **Source Code**: All telemetry code is open source in this repository
## 📊 Example Telemetry Event
Here's what a typical telemetry event looks like:
```json
{
"record": "tool_execution",
"timestamp": 1704067200,
"customer_uuid": "550e8400-e29b-41d4-a716-446655440000",
"session_id": "abc123-def456-ghi789",
"version": "3.0.2",
"platform": "posix",
"data": {
"tool_name": "manage_script",
"success": true,
"duration_ms": 42.5
}
}
```
Notice:
- ✅ Anonymous UUID (randomly generated)
- ✅ Tool performance metrics
- ✅ Success/failure tracking
- ❌ No code content
- ❌ No project information
- ❌ No personal data
---
*MCP for Unity Telemetry is designed to respect your privacy while helping us build a better tool. Thank you for helping improve MCP for Unity!*
+98
View File
@@ -0,0 +1,98 @@
---
id: transports
slug: /architecture/transports
title: Transport Modes
sidebar_label: Transport Modes
description: HTTP versus stdio — when to use each, what the trade-offs are, and how multi-agent isolation works.
---
# Transport Modes
MCP for Unity supports two transports between the MCP client and the Python server. The choice affects multi-agent capability, configuration shape, and a few subtle behaviors around instance routing.
## Quick decision
| If you want… | Use |
|---|---|
| Multiple MCP clients sharing one Unity instance | **HTTP** |
| Multiple Unity instances driven from one client | either |
| Lowest setup friction | **stdio** (Claude Desktop default) |
| Remote-hosted server (cloud, Docker) | **HTTP** |
| Marketplace distribution that can't ship Python | **HTTP** (remote URL) |
## HTTP (default)
**Architecture:** one Python process, one shared WebSocket hub at `/hub/plugin`, multiple MCP clients can connect concurrently. Each client gets a `client_id` and session-keyed state.
**Endpoint:** `http://localhost:8080/mcp`
**MCP client config:**
```json
{
"mcpServers": {
"unityMCP": { "url": "http://localhost:8080/mcp" }
}
}
```
**What you gain:**
- Multi-agent: Claude Code and Cursor open at the same time, both seeing the same Unity Editor
- Session isolation: each client's active instance, tool-group visibility, and middleware state are independent
- Remote hosting: the server can run on a different machine or in a container
**What you give up:**
- Port-number shorthand for `set_active_instance` (HTTP enforces `Name@hash`)
- A small amount of setup complexity if you bind to LAN — see [Security](https://github.com/CoplayDev/unity-mcp/blob/beta/SECURITY.md)
## Stdio
**Architecture:** the MCP client spawns a dedicated Python process via `stdio`, communicating over stdin/stdout. The Python process talks to Unity over a legacy TCP bridge.
**MCP client config (macOS/Linux):**
```json
{
"mcpServers": {
"unityMCP": {
"command": "uvx",
"args": ["--from", "mcpforunityserver", "mcp-for-unity", "--transport", "stdio"]
}
}
}
```
**What you gain:**
- Lowest configuration friction; works without HTTP port allocation
- Port-number shorthand: `set_active_instance(instance="6401")`
- Claude Desktop only supports stdio — that's why MCP for Unity silently selects stdio when configuring Claude Desktop, even if you have HTTP picked elsewhere
**What you give up:**
- Single-agent: a new MCP client connection replaces the previous one
- No native session isolation: switching the active Unity instance in one client affects what the next client sees
- Cannot host remotely
## What "instance" means in each mode
- **HTTP**: instance state is keyed by `client_id` in middleware. Two clients can hold different active instances concurrently against the same Unity Editor pool.
- **Stdio**: instance state is process-local. Since there's one Python process per client, isolation is implicit — but switching processes loses the old state.
See [Multi-Instance Routing](/guides/multi-instance) for the routing API.
## Switching transport
In the Unity Editor: **Window → MCP for Unity → Settings**, pick `HTTP` or `stdio`, click **Configure All Detected Clients**. The configurator rewrites each client's MCP config to match.
Claude Desktop is the exception — it's always written as stdio regardless of your selection, because it doesn't support HTTP.
## Network security (HTTP only)
By default, HTTP binds to loopback (`127.0.0.1` / `::1`). Binding to all interfaces (`0.0.0.0` / `::`) requires explicit opt-in: **Advanced Settings → Allow LAN Bind (HTTP Local)**.
Remote endpoints require `https://`. To allow plaintext `http://` for a remote URL, opt in via **Allow Insecure Remote HTTP**. Both guards are fail-closed: if you don't flip the switch, the server refuses the unsafe configuration.
## Where this is implemented
- Python: `Server/src/transport/` (plugin hub, websocket transport, legacy stdio bridge)
- C#: `MCPForUnity/Editor/Services/` (transport clients, server management, stdio bridge host)
- v8 migration notes: [/migrations/v8](/migrations/v8) — the architectural story of HTTP arriving
+65
View File
@@ -0,0 +1,65 @@
---
id: unity-compat
slug: /architecture/unity-compat
title: Unity API Compatibility Shims
sidebar_label: Unity Compat Shims
description: How MCP for Unity supports Unity 2021 LTS through 6.x and the CoreCLR 6.8 path without sprinkling version gates across every call site.
---
# Unity API Compatibility Shims
MCP for Unity targets a wide Unity version range — **2021.3 LTS → Unity 6.x → CoreCLR 6.8**. Unity has renamed, deprecated, and threatened to remove a handful of APIs across that window. Rather than sprinkle `#if UNITY_*_OR_NEWER` at every call site, MCP for Unity routes the friction through a small set of **shims** under `MCPForUnity/Runtime/Helpers/`.
## The catalog
The canonical list lives in `MCPForUnity/Runtime/Helpers/UnityCompatShims.cs` (an intentionally empty marker class — its XML doc is the source of truth and ships inside the UPM package, so end-users can `F12` into it).
| Shim | Wraps | Deprecated / Removed |
|---|---|---|
| `UnityFindObjectsCompat` | `Object.FindObjectsOfType``FindObjectsByType` | 2023.1 |
| `UnityObjectIdCompat` | `InstanceID``EntityId` | 6000.3 → 6000.6 (CS0619) |
| `UnityPhysicsCompat` | `Physics{,2D}.autoSyncTransforms`, `autoSimulation``simulationMode` | 6000.0 / 2022.2 |
| `UnityAssembliesCompat` | `AppDomain.GetAssemblies``UnityEngine.Assemblies.CurrentAssemblies` | Unity 6.8 CoreCLR |
## When to add a new shim
One of these must be true:
1. The API is marked `[Obsolete]` **and** the call site can't simply be deleted, **or**
2. Three or more call sites need version gating for the same API, **or**
3. A future Unity version has publicly announced rename or removal of the API.
If only one or two call sites are affected and the rename isn't on the roadmap, a localized `#if UNITY_*_OR_NEWER` is fine. Don't pre-shim speculatively.
## What does **not** belong in a shim
- Hot-path engine APIs (`Transform.position`, `Vector3.*`, `GetComponent<T>`) — version gating these is noise, and they don't move
- APIs Unity has not threatened to break (`Mathf`, `Quaternion`, most of `AssetDatabase`) — adding a shim implies maintenance forever
- Editor-internal undocumented APIs — those *should* break loudly so the package maintainers notice immediately
## The pattern
Two implementation styles, picked by what the SDK exposes:
- **Static dispatch** (`#if UNITY_*_OR_NEWER`): use when the new API exists in the SDK you compile against. The shim picks the right call site at compile time, with no runtime cost.
- **Reflection with a cached `MethodInfo` / `PropertyInfo`**: use when the new API is in a version you don't yet target, or when the old API may eventually be removed (CS0619). One reflection lookup at static-init time, then plain delegate invocation forever after.
In both cases, **fail-soft**: callers should treat a missing API as a no-op, never throw. This keeps the package compiling and behaving sensibly on every supported Unity version, including ones the maintainer hasn't tested yet.
## Compile-checking across versions locally
`tools/check-unity-versions.sh` runs a compile-only check across the same Unity versions CI runs. The matrix is in `tools/unity-versions.json`.
```bash
tools/check-unity-versions.sh # compile-only across installed Unity Hub editors
tools/check-unity-versions.sh --full # full EditMode test run
```
The pre-push hook (installed via `tools/install-hooks.sh`) runs this automatically when your push touches `MCPForUnity/`, `TestProjects/`, or the version matrix.
## Source pointers
- Catalog + policy: `MCPForUnity/Runtime/Helpers/UnityCompatShims.cs`
- Individual shim files: same directory, named `Unity*Compat.cs`
- Unity 6.x deprecation list: Unity upgrade guides
- CoreCLR 6.8 path: [Unity discussion thread](https://discussions.unity.com/t/path-to-coreclr-2026-upgrade-guide/1714279)