- C# 98.5%
- Python 1.5%
|
All checks were successful
Release / release (push) Successful in 5s
- assets/size_report, assets/set_labels, assets/apply_preset - scene_view/get + scene_view/set (aim the Scene view camera for screenshots) - physics/settle (edit-mode global physics settle with Undo) - vrchat/setup_layers - editor/kill_import_workers: shut down AssetImportWorker child processes whose hidden container windows turn into white not-responding ghost windows; graceful ForceToDesiredWorkerCount first, then kill, parent-PID scoped to this editor instance - terrain: lean default detail/alpha/base map resolutions; drop Undo.RecordObject on TerrainData (froze the editor serializing all maps) - bridge: send tools/list_changed so hosts pick up new tools without restart |
||
|---|---|---|
| .forgejo/workflows | ||
| bridge | ||
| docs | ||
| .gitignore | ||
| CLAUDE.md | ||
| LICENSE | ||
| OttLabs.OTTLabsMCP.Editor.asmdef | ||
| OttLabs.OTTLabsMCP.Editor.asmdef.meta | ||
| OTTLabsMCPAnimationHandlers.cs | ||
| OTTLabsMCPAnimationHandlers.cs.meta | ||
| OTTLabsMCPAssetHandlers.cs | ||
| OTTLabsMCPAssetHandlers.cs.meta | ||
| OTTLabsMCPAvatarOptHandlers.cs | ||
| OTTLabsMCPAvatarOptHandlers.cs.meta | ||
| OTTLabsMCPEditorOpsHandlers.cs | ||
| OTTLabsMCPEditorOpsHandlers.cs.meta | ||
| OTTLabsMCPEvalHandlers.cs | ||
| OTTLabsMCPEvalHandlers.cs.meta | ||
| OTTLabsMCPHandlers.cs | ||
| OTTLabsMCPHandlers.cs.meta | ||
| OTTLabsMCPMeshHandlers.cs | ||
| OTTLabsMCPMeshHandlers.cs.meta | ||
| OTTLabsMCPOTTHandlers.cs | ||
| OTTLabsMCPOTTHandlers.cs.meta | ||
| OTTLabsMCPPrefabHandlers.cs | ||
| OTTLabsMCPPrefabHandlers.cs.meta | ||
| OTTLabsMCPServer.cs | ||
| OTTLabsMCPServer.cs.meta | ||
| OTTLabsMCPSettings.cs | ||
| OTTLabsMCPSettings.cs.meta | ||
| OTTLabsMCPSettingsProvider.cs | ||
| OTTLabsMCPSettingsProvider.cs.meta | ||
| OTTLabsMCPTerrainHandlers.cs | ||
| OTTLabsMCPTerrainHandlers.cs.meta | ||
| OTTLabsMCPTextureHandlers.cs | ||
| OTTLabsMCPTextureHandlers.cs.meta | ||
| OTTLabsMCPToolsRegistry.cs | ||
| OTTLabsMCPValueConverter.cs | ||
| OTTLabsMCPValueConverter.cs.meta | ||
| OTTLabsMCPVRCFuryHandlers.cs | ||
| OTTLabsMCPVRCFuryHandlers.cs.meta | ||
| OTTLabsMCPWindowHandlers.cs | ||
| OTTLabsMCPWindowHandlers.cs.meta | ||
| package.json | ||
| README.md | ||
OTTLabs MCP Server
Gives any MCP-compatible AI assistant (Claude Code, Cursor, etc.) direct access to the Unity Editor. The assistant can inspect the scene hierarchy, move objects, create materials, bake lighting, manage VRCFury components, and much more -- all without leaving the chat.
How it works
AI assistant <--- MCP (stdio) ----> Python bridge <--- HTTP/JSON ----> Unity Editor
(Claude Code) bridge/unity_bridge.py this package
- Unity Editor runs an HTTP server on a per-project port (a sticky random port in the
20000-60000range, chosen once and kept for that project). The server only runs in the interactive editor process — batch-mode processes (AssetImportWorkers, CI) never bind a port or touch the port file. - Python bridge (
bridge/unity_bridge.py) is a tiny MCP server that reads the port file and forwards tool calls. - AI assistant talks to the bridge over stdio using the standard MCP protocol.
The port (and a few other options) are configurable under Project Settings → OTT Labs → MCP Server.
Installation
Step 1 -- Install the Unity package
Via VCC / VPM (recommended for VRChat projects): add the OT&T Labs repository to the VRChat Creator Companion (or ALCOM), then add OTTLabs MCP Server to your project:
https://ott-labs.pages.ott-labs.de/vpm/index.json
Via Unity Package Manager: use Add package from git URL:
https://git.ott-labs.de/OTT-Labs/OTTLabsMCP.git
Or add it manually to Packages/manifest.json:
{
"dependencies": {
"de.ott-labs.ottlabs-mcp": "https://git.ott-labs.de/OTT-Labs/OTTLabsMCP.git"
}
}
Open or restart the Unity Editor. The server starts automatically and logs its port:
[OTTLabsMCP] Listening on http://localhost:41485/
(The port is per-project — see Configuration.)
Use OTT Labs -> OTTLabs MCP -> Server Status to confirm it is running.
Step 2 -- Install the Python bridge
Requires Python 3.11+.
# with pip
pip install mcp httpx
# or with uv (recommended)
uv pip install mcp httpx
Step 3 -- Configure your MCP client
Claude Code
Register the bridge with the claude mcp CLI:
claude mcp add unity --env UNITY_PROJECT_PATH=/absolute/path/to/your/unity/project \
-- python /absolute/path/to/OTTLabsMCP/bridge/unity_bridge.py
Or create a .mcp.json at the root of the workspace you run Claude Code from:
{
"mcpServers": {
"unity": {
"command": "python",
"args": ["/absolute/path/to/OTTLabsMCP/bridge/unity_bridge.py"],
"env": {
"UNITY_PROJECT_PATH": "/absolute/path/to/your/unity/project"
}
}
}
}
Replace both paths with the real absolute paths on your machine.
Other MCP clients
Configure a stdio MCP server with:
- Command:
python /path/to/bridge/unity_bridge.py - Environment:
UNITY_PROJECT_PATH=/path/to/your/unity/project
Verification
With Unity running and the bridge configured, ask Claude Code:
"Show me the scene hierarchy."
Claude will call the scene_hierarchy tool and return the full tree.
Environment variables
| Variable | Required | Description |
|---|---|---|
UNITY_PROJECT_PATH |
Yes | Absolute path to the Unity project root. The bridge reads Temp/OTTLabsMCPPort.txt from here. |
Configuration
Open Project Settings → OTT Labs → MCP Server (or edit ProjectSettings/OTTLabsMCP.json directly):
| Option | Default | Description |
|---|---|---|
| Port | random 20000-60000, sticky |
TCP port for this project. Chosen once on first run and reused every session. Use Randomize to pick a new one. If the port is busy the server scans upward a few and warns. |
| Auto-start | true |
Start the server automatically when the editor loads. |
| Loopback only | true |
Bind localhost/127.0.0.1 only (recommended). |
| Require auth token | false |
Generate a per-session bearer token and require it on every request. |
| Allow C# eval | false |
Allow the editor_eval endpoint to compile and run one-shot C# snippets (via the editor's bundled compiler). editor_invoke_static is always available. |
| Verbose logging | false |
Log every handled request path to the Console. |
ProjectSettings/OTTLabsMCP.json is machine-local — add it to your project's .gitignore so two checkouts on one machine don't fight over the same port.
Security
The server only binds to localhost and never accepts remote connections (unless you explicitly turn off Loopback only).
When Require auth token is enabled, a fresh random token is generated each time the server starts and written alongside the port to Temp/OTTLabsMCPPort.txt as {port}:{token}. The bridge reads it automatically and sends it as an Authorization: Bearer <token> header; requests without a valid token receive a 401. The discovery endpoints (/openapi.json, /swagger, /editor/ping) stay reachable without a token. The token rotates on every restart, so restart the bridge (restart your MCP client) after toggling auth or restarting Unity.
OpenAPI / Swagger
The same tool catalogue is exposed as an OpenAPI 3.0 document for REST/Swagger clients:
GET http://localhost:<port>/openapi.json— the specGET http://localhost:<port>/swagger— a browsable Swagger UI page
Every endpoint is a POST with a JSON body and returns the standard {success, result} / {success, error} envelope.
Troubleshooting
Bridge says "OTTLabs MCP server not found"
- Make sure the Unity Editor is open.
- Check that the OTTLabs MCP package is installed (Package Manager -> In Project).
- Confirm
UNITY_PROJECT_PATHpoints to the correct project folder. - Look in
Temp/OTTLabsMCPPort.txt-- it should contain aport:tokenpair.
"Unauthorized" errors
- The token rotates on every Unity restart. Restart the bridge (restart Claude Code / your MCP client) so it re-reads the port file.
Port binding failed in Unity
- Check the Unity Console for
[OTTLabsMCP]log lines. - Use OTT Labs -> OTTLabs MCP -> Restart Server to retry.
Long operations time out (lighting bake, VRC build)
- The default HTTP timeout in the bridge is 90 seconds. These operations can take longer. Use the cancel endpoint (
lighting_bake_cancel) to abort, or increasetimeoutin_post()insideunity_bridge.py.
Tool calls fail right after a recompile
- A domain reload tears the HTTP listener down for a few seconds. The bridge keeps the last-known tool registry and transparently retries connection-refused for up to 30 s; if the editor is still restarting it returns a structured
{"retryable": true, "error": "editor restarting..."}response — just retry the call.
New tools after a package upgrade don't appear
- The bridge polls Unity's registry every ~10 s and emits the MCP
tools/list_changednotification when the tool set changes, so hosts that honor it (Claude Code does) pick up new tools without a restart. If your host ignores the notification, restart it once.
Available tools
Tools are discovered dynamically from Unity via tools/list -- no hard-coding in the bridge. The full list is always up-to-date with the installed package version.
| Category | Tools |
|---|---|
| Meta | tools_list, openapi |
| Scene query | scene_hierarchy (subtree + depth params), scene_object, scene_find, scene_info |
| Scene files | scene_open, scene_save_as, scene_close, scene_list_loaded, scene_set_active (object paths resolve across all loaded scenes) |
| Prefabs | prefab_get, prefab_create, prefab_overrides, prefab_apply_overrides, prefab_revert_overrides, prefab_unpack |
| Scene modify | scene_create, scene_delete, scene_set_transform, scene_set_field, scene_set_field_reference, component_set_serialized_property, component_get_serialized_property, scene_add_component, scene_remove_component, scene_instantiate, scene_reparent, scene_rename, scene_invoke_method, component_copy, scene_set_static_flags, scene_find_missing_scripts, scene_find_missing_references, scene_find_asset_usage |
| Udon | udon_add_behaviour (UdonSharp-aware, no focus dance) |
| Settings | settings_get, settings_set (tags/layers, physics, quality, graphics, player, ...) |
| Animation | animator_get, animator_create, animator_add_layer, animator_add_parameter, animator_remove_parameter, animator_add_state, animator_set_state, animator_remove_state, animator_add_transition, animator_add_blend_tree, animation_get, animation_create, animation_set_curve |
| Meta ops | batch (up to 100 calls per round trip), packages_list |
| Assets | assets_list, assets_find (name/type/label), assets_create_material, asset_read_material, assets_move, assets_rename, assets_delete, assets_duplicate, assets_create_folder, assets_guid_lookup, assets_dependencies, assets_references, assets_create_instance, asset_get_object, asset_get_importer, asset_set_importer, assets_size_report, assets_set_labels, assets_apply_preset |
| Materials | renderer_materials, renderer_validate, renderer_blendshapes, renderer_set_blendshape, material_poiyomi_status, material_set_locked, shader_get, asset_set_material, texture_export |
| Editor | editor_save, editor_refresh, editor_select, editor_get_selection, editor_ping, editor_play_start, editor_play_stop (both with wait), editor_play_status, editor_logs, editor_logs_clear, editor_wait_for_log, editor_recompile, editor_compile_status, editor_wait_idle, editor_invoke_static, editor_eval, editor_reflect, editor_screenshot, editor_undo, editor_redo, editor_undo_snapshot, editor_undo_revert_to, editor_undo_collapse, editor_stats, editor_focus, scene_view_get, scene_view_set, physics_settle |
| Windows | editor_window_open, editor_window_close, editor_window_list, editor_execute_menu, ott_window_list, ott_window_open |
| Lighting | lighting_get, lighting_set, lighting_bake, lighting_bake_cancel, lighting_clear |
| Occlusion | occlusion_bake, occlusion_cancel, occlusion_clear, occlusion_status |
| VRChat SDK | vrchat_info, vrchat_validate, vrchat_descriptor_get, vrchat_descriptor_set, vrchat_setup_layers |
| Build | build_settings, build_switch_platform |
| Avatar | avatar_strip_unused_bones, avatar_build_test_copy, avatar_perf_report |
| Texture | texture_create (6 generation modes) |
| Mesh | mesh_create (5 modes), mesh_get, mesh_modify |
| Terrain | terrain_create, terrain_get, terrain_resize, terrain_set_heights, terrain_apply_craters, terrain_add_layer, terrain_to_mesh, terrain_bake_height_texture |
| VRCFury | vrcfury_list, vrcfury_get, vrcfury_list_feature_types, vrcfury_add_toggle, vrcfury_add_feature, vrcfury_remove, vrcfury_set_field, vrcfury_add_object_action, vrcfury_add_blendshape_action |
| OT&T MQTT | ott_mqtt_manager_status/inject/refresh, ott_mqtt_driver_status/connect/disconnect/publish/fault |
| OT&T VR Phone | ott_vrphone_* (22 tools for layout editing, atlas baking, canvas build) |
| OT&T ScribanGen | ott_scriban_generate_all, ott_scriban_generate_asset, ott_scriban_list_bindings |
Note: This package deliberately does not include VRChat build or upload automation. Automating SDK builds/uploads violates the VRChat Terms of Service, so no such tools have been added. Building and uploading worlds or avatars must be done manually through the VRChat SDK control panel.
License
MIT -- see LICENSE.