t1k:unity:base:mcp-skill
| Field | Value |
|---|---|
| Module | base |
| Version | 2.2.2 |
| Effort | high |
| Tools | — |
Keywords: bridge, MCP, tool, unity MCP
How to invoke
Section titled “How to invoke”/t1k:unity:base:mcp-skillUnity-MCP Operator Guide
Section titled “Unity-MCP Operator Guide”Use Unity Editor via MCP (Model Context Protocol) tools. Always read relevant resources before using tools.
Template Notice
Section titled “Template Notice”Examples in references/ are reusable templates. Validate targets/components first; treat names, enum values, and property payloads as placeholders to adapt.
⚠️ Step 0 — Multi-Instance Targeting (MANDATORY when >1 editor may be open)
Section titled “⚠️ Step 0 — Multi-Instance Targeting (MANDATORY when >1 editor may be open)”Before ANY MCP tool/resource call, confirm which Unity project you are connected to. When two or more editors are open, the bridge exposes ALL of them and a call with no pinned active instance is ambiguous — the server errors, OR (worse, in some builds) silently routes to the wrong project, producing baffling symptoms like an empty scene hierarchy / empty entity query in edit mode that look like a real bug but are just “you queried the other game.”
Pre-flight every session (and re-run if you suspect drift):
1. List instances → read resource mcpforunity://instances2. Pin the target → set_active_instance(instance="<ProjectName>@<hash>") # or hash prefix / port3. VERIFY the pin → read resource mcpforunity://project/info └─ assert data.projectRoot / projectName is the project you intendRules:
- Never assume a single instance. Even if you only opened one, a teammate/another session may have a second editor open. Always list first.
- The pin is
set_active_instance(session-global), or passunity_instance="<hash>"per-call to route just one call without changing the session default. - Re-verify after any long gap or after a reported “empty/null” result — if a query that should return data comes back empty, your FIRST hypothesis is “wrong instance / lost pin”, not “the data is gone”. Re-read
mcpforunity://project/infobefore debugging the supposed bug. - Symptom cheat-sheet: empty
find_gameobjects, empty entity queries, “scene has no objects”, or edits landing in a project you didn’t touch → check the active instance FIRST. - Instances differ by
path,hash, andport(e.g. DOTS-AI on 6400 vs another game on 6401). Match onpathcontaining your project root, not on name alone.
Resource-First Workflow
Section titled “Resource-First Workflow”0. Pin + verify instance → mcpforunity://instances → set_active_instance → mcpforunity://project/info (see Step 0 above)1. Check editor state → mcpforunity://editor/state2. Understand the scene → mcpforunity://scene/gameobject-api3. Find what you need → find_gameobjects or resources4. Take action → tools (manage_gameobject, create_script, etc.)5. Verify results → read_console, capture_screenshot, resourcesCritical Best Practices
Section titled “Critical Best Practices”After writing/editing scripts — always refresh and check console:
refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True)read_console(types=["error"], count=10, include_stacktrace=True)⛔ Forbidden — Assets/Reimport All
Section titled “⛔ Forbidden — Assets/Reimport All”NEVER call execute_menu_item(menu_path="Assets/Reimport All") or any equivalent (AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate), etc.).
Why it is forbidden:
- Triggers a from-scratch reimport of every asset in the project (textures, models, shaders, scripts, audio, prefabs). On a non-trivial project this is 30+ minutes of CPU/GPU work and multiple shader compiler workers spawn.
- Cannot be cancelled from MCP once issued — Unity processes the queue regardless of subsequent commands. The MCP bridge typically goes unresponsive while the reimport runs.
- The legitimate reason to use it (rebuilding stale Burst caches or recovering from a corrupted asset DB) is satisfied by the targeted alternatives below at a fraction of the cost.
When you think you need Reimport All, you actually want one of these:
| Goal | Correct command |
|---|---|
| Recompile scripts after edits | refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) |
| Re-bake DOTS SubScenes after entity-type changes | rm -rf Library/EntityScenes/ then enter Play mode |
| Clear stale Burst cache after type changes | rm -rf Library/BurstCache Library/Bee/artifacts Library/ScriptAssemblies then refresh_unity |
| Reimport ONE asset (texture, model, prefab) | manage_asset(action="reimport", path="Assets/...") |
| Recover from “everything looks broken” | Restart the Unity Editor (faster than Reimport All) |
Hard rule: The only path to legitimately calling Reimport All is (1) the user issues a direct order to do so, OR (2) a referenced document explicitly requires it for a specific recovery procedure. If neither applies, do not call it. If you are about to call it, stop and choose one of the targeted alternatives above.
Use batch_execute for multiple operations (10–100x faster):
batch_execute(commands=[...], parallel=True) # Max 25 per batch (configurable, max 100)Screenshots for visual verification:
manage_scene(action="screenshot", include_image=True, max_resolution=512)manage_scene(action="screenshot", batch="surround", look_at="Player", max_resolution=256)Always check editor_state before complex operations — wait if is_compiling=true or is_domain_reload_pending=true.
🔧 Install / Re-Install — Always The1Studio Fork on beta
Section titled “🔧 Install / Re-Install — Always The1Studio Fork on beta”UnityMCP must be registered from the The1Studio fork on the beta branch — never the upstream PyPI package mcpforunityserver, never any other branch. This matches the Unity-side UPM submodule (Packages/com.coplaydev.unity-mcp/), which is pinned to the same fork+branch.
Canonical install (user scope so it survives across projects):
# Idempotent — clear any prior bad config in either scope first.claude mcp remove UnityMCP -s user || trueclaude mcp remove UnityMCP -s local || true
claude mcp add UnityMCP -s user -- \ uvx --from "git+https://github.com/The1Studio/unity-mcp.git@beta#subdirectory=Server" \ mcp-for-unityAlways verify after install (both commands, same turn):
claude mcp get UnityMCP # expect: ✓ Connected, Scope: User config, Args: --from git+https://github.com/The1Studio/unity-mcp.git@beta#subdirectory=Server mcp-for-unityclaude mcp list | grep -i unity # expect: ✓ ConnectedIf claude mcp get shows ANY of the following, the install is wrong — remove and re-add:
mcpforunityserveranywhere in args → upstream PyPI package, banned.--offlineflag → the Editor’s “Configure All Detected Clients” button wrote this; permanent connect failure if the package isn’t cached.Scope: Local configinstead ofUser config→ the Editor’s Configure button writes local; user scope is required.- Branch other than
betaafter@(e.g.,@master) → the submodule pinsbeta.
⛔ Forbidden installation paths
Section titled “⛔ Forbidden installation paths”| Path | Why banned |
|---|---|
| MCP For Unity Editor panel → “Configure All Detected Clients” button (Claude Code client) | Writes uvx --offline --prerelease explicit --from "mcpforunityserver>=0.0.0a0" mcp-for-unity to local scope. --offline + uncached package = unrecoverable connect failure (documented 2026-05-18). |
| MCP For Unity Editor panel → “Install Skills” button | Same broken config as above. |
uvx --from mcpforunityserver ... (any version, any flag combination) | Upstream registry package — not validated against the The1Studio fork’s tool patches. |
Any branch of The1Studio/unity-mcp other than beta | The Unity-side submodule pins beta; the Python server must match. |
Any third-party fork of unity-mcp | Only the The1Studio fork carries our tool patches. |
If the Editor panel shows Configured with Claude Code but claude mcp get shows the broken PyPI command, click the panel’s Unregister button, then re-run the canonical install commands above. NEVER click “Install Skills” or “Configure All Detected Clients” to recover — those re-write the broken config every time.
Gotcha — claude mcp get/list returns NOTHING but tools still load (broken entry hiding in another project’s scope)
Section titled “Gotcha — claude mcp get/list returns NOTHING but tools still load (broken entry hiding in another project’s scope)”Symptom (observed 2026-06-10): claude mcp get UnityMCP → No MCP server found, claude mcp list | grep -i unity → empty, yet the session has mcp__UnityMCP__* tools loaded and the SessionStart hook prints [t1k:mcp] action=ok name="UnityMCP" scope=user. Looks like there’s no config at all, but there is — a stale, broken UnityMCP registered at project scope under a different project (e.g. ~/.claude.json → projects["…/DOTS-AI"].mcpServers.UnityMCP with the banned --offline mcpforunityserver args). claude mcp get/list only inspect the current project + user scope, so a foreign project-scoped entry is invisible to them while the live session connects from a stale snapshot.
Diagnose by grepping the whole config, not just the CLI:
grep -n 'UnityMCP\|mcpforunity\|mcp-for-unity\|--offline' ~/.claude.jsonpython3 -c "import json;d=json.load(open('$HOME/.claude.json'));[print(p,'->',c.get('mcpServers',{}).get('UnityMCP',{}).get('args')) for p,c in d.get('projects',{}).items() if 'UnityMCP' in c.get('mcpServers',{})]"Fix: re-register at user scope from the beta fork (canonical install above), then delete the foreign project-scoped entry so it can’t shadow/confuse later. The user-scope registration is the SSOT — never rely on a project-scoped one.
🔍 When MCP doesn’t respond — diagnose Unity status WITHOUT MCP
Section titled “🔍 When MCP doesn’t respond — diagnose Unity status WITHOUT MCP”A failed MCP call (timeout, “Unity is reloading”, “No Unity Editor instances”) is almost never a bridge connectivity problem. It usually means Unity’s main thread is busy (compiling, importing, baking, domain-reloading, lightmapping, etc.) and the bridge thread is blocked behind it. Asking the user to “Start Session” is the wrong escalation — the bridge IS connected, Unity just isn’t yet ready to service requests.
Before asking the user for help, run these CLI probes to see what Unity is actually doing:
| Signal | Command | What it means |
|---|---|---|
| Editor process alive | pgrep -af "Unity.*<projectname>" | Editor PID + count of AssetImportWorker* children. >2 workers = active import. |
| Lock file present | ls Temp/UnityLockfile | File exists → editor is running on the project. Absent → editor closed. |
| Live import activity | tail -10 Logs/AssetImportWorker0.log | ”Importing X” / “Reloading scripts” = busy. “shutdown with reason: Scaling down because of idle timeout” = idle. |
| Compile recency | stat Library/ScriptAssemblies/<assembly>.dll | mtime = last successful compile. If older than your most recent script edit → still compiling. |
| Bee build state | ls Library/Bee/artifacts/ count | Growing count = active build. Stable count + recent mtime = build settling. |
| Domain reload | tail Logs/AssetImportWorker0.log | grep -i "reload" | ”Begin MonoManager ReloadAssembly” = reloading; “End MonoManager” = done. |
On Linux, Logs/Editor.log does NOT exist at this path — Unity 6 puts the equivalent main-editor log somewhere else (or only writes to stderr). Use Logs/AssetImportWorker*.log instead; they tail the same compile/import activity from the worker side.
Recovery decision tree
Section titled “Recovery decision tree”- MCP call fails → run probes above.
- Editor process exists + workers active → Unity is BUSY. Wait. Retry
refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True)after Unity reports ready — do not orchestrate DLL-mtime polling loops in shell. - Editor process exists + no workers + DLLs are recent + lock file present → bridge socket is genuinely dropped. Ask user to click
Window > MCP for Unity > Start Session. - Editor process gone + lock file gone → user closed Unity. Ask them to reopen.
- Editor process gone + lock file present → Unity crashed. Ask user to relaunch.
Never ask the user to click “Start Session” before completing step 1. False escalation breaks user trust and wastes their time. Prove the bridge is dead by elimination, not by assumption.
Core Tool Categories
Section titled “Core Tool Categories”| Category | Key Tools |
|---|---|
| Scene | manage_scene, find_gameobjects |
| Objects | manage_gameobject, manage_components |
| Scripts | create_script, script_apply_edits, manage_script, refresh_unity |
| Assets | manage_asset, manage_prefabs, manage_material, manage_texture |
| Editor | manage_editor, execute_menu_item, read_console |
| Testing | run_tests, get_test_job |
| Batch | batch_execute |
| Camera | manage_camera, manage_cinemachine |
| Graphics | manage_graphics, manage_render_pipeline, manage_shader |
| Packages | query_packages, manage_packages |
| ProBuilder | manage_probuilder |
| UI | manage_ui, manage_ui_toolkit |
| DOTS | manage_dots, manage_dots_graphics, manage_dots_physics, manage_dots_subscene |
| Physics | manage_physics, manage_physics2d |
| Navigation | manage_navigation |
| Media | manage_animation, manage_audio, manage_video, manage_vfx, manage_timeline |
| World | manage_terrain, manage_tilemap, manage_splines, manage_lighting, manage_mesh |
| Systems | manage_addressables, manage_build, manage_input_system, manage_localization, manage_netcode |
| AI | manage_behavior, manage_asset_hunter |
| Performance | manage_profiler, rendering_stats, validation_snapshot |
| Code | manage_scriptable_object, find_in_file |
→ See reference files below for full parameter schemas and examples.
Common Workflows
Section titled “Common Workflows”→ See references/workflow-script-lifecycle.md, references/workflow-scene-objects.md, references/workflow-testing.md, references/workflow-assets-prefabs.md, references/workflow-batch-operations.md, references/workflow-camera-probuilder.md, references/workflow-ui-creation.md, references/workflow-ui-advanced.md for extended patterns.
Quick patterns:
# New script → attach:create_script(path="Assets/Scripts/Foo.cs", contents="...")refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True)manage_gameobject(action="modify", target="Player", components_to_add=["Foo"])
# Run tests (async):result = run_tests(mode="EditMode")get_test_job(job_id=result["job_id"], wait_timeout=60, include_failed_tests=True)Parameter Type Conventions
Section titled “Parameter Type Conventions”- Vectors:
position=[1,2,3]or"[1,2,3]"(both accepted) - Colors:
[255,0,0,255](0–255) or[1.0,0,0,1.0](normalized, auto-converted) - Paths:
"Assets/Scripts/Foo.cs"(Assets-relative) or"mcpforunity://path/..."(URI)
→ See references/error-recovery-guide.md for error recovery table, auto-start setup, cache gotchas, and the 6-step asset refresh hierarchy.
Reference Files
Section titled “Reference Files”| File | Contents |
|---|---|
references/tools-scene-objects.md | manage_scene, find_gameobjects, manage_gameobject, manage_components |
references/tools-scripts-assets.md | create_script, script_apply_edits, manage_asset, manage_prefabs, materials |
references/tools-editor-testing.md | manage_editor, execute_menu_item, read_console, run_tests, find_in_file |
references/tools-camera-graphics.md | manage_camera (all tiers), manage_graphics |
references/tools-batch-packages.md | batch_execute, set_active_instance, query_packages, manage_packages, manage_ui |
references/tools-probuilder.md | manage_probuilder (all actions, known bugs) |
references/workflow-script-lifecycle.md | Create, edit, attach, validate C# scripts |
references/workflow-scene-objects.md | Fresh builds, grids, clone/arrange, physics triggers |
references/workflow-testing.md | Run tests, TDD, diagnose errors, domain reload recovery |
references/workflow-assets-prefabs.md | Materials, textures, folder structure, prefab workflows |
references/workflow-camera-probuilder.md | Camera setup, Cinemachine, ProBuilder scene building |
references/workflow-batch-operations.md | Mass operations, multi-instance, input systems, pagination |
references/workflow-ui-creation.md | UI Toolkit, uGUI Canvas, RectTransform, EventSystem |
references/workflow-ui-advanced.md | Slider, Toggle, Input Field, Layout Group, TMP alignment |
references/tools-dots-physics-nav.md | manage_dots, manage_dots_graphics/physics/subscene, manage_physics/2d, manage_navigation, manage_mesh |
references/tools-media-world.md | manage_animation, manage_audio, manage_video, manage_vfx, manage_timeline, manage_terrain, manage_tilemap, manage_splines, manage_lighting |
references/tools-systems-code.md | manage_addressables, manage_build, manage_input_system, manage_localization, manage_netcode, manage_script, manage_scriptable_object, manage_shader |
references/tools-perf-ai-misc.md | manage_profiler, rendering_stats, manage_cinemachine, manage_render_pipeline, manage_behavior, manage_asset_hunter, validation_snapshot, manage_ui_toolkit |
references/error-recovery-guide.md | Error diagnosis table, auto-start setup, cache gotchas, asset refresh hierarchy |
references/upm-gotchas.md | UPM diagnostic recipes: duplicate-scope registry failure, embed-override pattern, submodule GUID conflict, refresh_unity timeout vs bridge disconnect |
references/workflow-scriptableobject-creation.md | Custom ScriptableObject .asset creation — manage_asset limitation, execute_code Windows path-length failure, direct YAML write fallback |
Gotchas
Section titled “Gotchas”- Prefer direct file edits over Unity APIs that may prompt the user. When you have a choice — and you usually do — modify the underlying asset on disk yourself (YAML
.asset, JSON, XML, CSV, TextAsset payloads) and then callAssetDatabase.Refresh()instead of going through Unity APIs that pop confirmation dialogs (EditorUtility.DisplayDialog,EditorUtility.DisplayDialogComplex, save / open file panels), modal progress bars, “Are you sure?” prompts onPlayerSettingsmutations, or any importer that asks “Apply / Revert” on focus loss. Why: every interactive prompt freezes Unity’s main thread, freezes the MCP bridge, and turns an autonomous agent run into a “please click this for me” round-trip with the user. Concrete pattern: to flip a value inside a ScriptableObject under version control, preferFile.ReadAllText→ string-replace the YAML field →File.WriteAllText→AssetDatabase.Refresh()overLoad → SetDirty → SaveAssetspaths in YOUR populator/migration code that includeDisplayDialogconfirmations. Same principle forProjectSettings/*.asset,Packages/manifest.json, addressables.assetgroup settings, and any[ScriptedImporter]output. When you DO write editor code that mutates data, gate anyDisplayDialogbehind anApplication.isBatchModecheck, or replace it with aDebug.LogWarningplus a--forcestyle explicit confirm-flag in the menu item name (e.g.,Tools/X/Repopulate (force overwrite)). - ANY open editor modal blocks ALL MCP calls — not just the one that opened it.
EditorUtility.DisplayDialog,EditorUtility.DisplayDialogComplex,EditorUtility.SaveFilePanel,EditorUtility.OpenFilePanel, and progress bars (EditorUtility.DisplayProgressBarleft un-cleared) all run on Unity’s main thread. While that thread is parked on the modal, EVERY subsequent MCP call —refresh_unity,read_console,editor/stateresource,execute_menu_item,run_tests,manage_scene, etc. — times out withMCP error -32001: Request timed out. The dialog does NOT have to be opened by the MCP call you’re currently making — a leftover modal from a priorexecute_menu_item, OR a modal opened by editor code reacting to a file change (e.g., an[InitializeOnLoad]script, aScriptedImporter, anAssetPostprocessor, or YOUR OWN populator that runs on first save) all stall the bridge identically. Diagnosis pattern (3 signals together): (1) MCP calls returnRequest timed outon multiple consecutive different tools (not just the one that “caused” it); (2) AssetImportWorker processes are alive (pgrep -af "AssetImportWorker.*<project>") butLibrary/ScriptAssemblies/<assembly>.dllmtime does NOT advance; (3)Logs/AssetImportWorker0.logshows no new work. Recovery (you cannot dismiss the modal from MCP — the modal IS the blocker): ask the user to look at the Unity Editor window and click the dialog button (Cancel/Overwrite/OK). Then re-establish the MCP bridge via instances poll. Prevention in editor code you author: never callEditorUtility.DisplayDialogfrom aMenuItemthat an agent is expected to invoke headlessly — gate dialogs behind anApplication.isBatchModecheck or useDebug.LogWarning+ an explicit--forceconfirm-flag pattern instead. - Modal-blocked
execute_menu_itemis the narrow case of the above. Same root cause; the menu DID fire, but downstream work is gated behind a UI prompt. Recovery: useexecute_codeto invoke the menu’s underlying static method via reflection (most editor tools delegate to aprivate staticworker that bypasses any UI gate), OR bootstrap whatever prerequisite the modal is complaining about and then re-run the menu item. - Addressables setup chicken-and-egg. Any project-side
Tools/Addressables/Create*menu item requiresAddressableAssetSettings.assetto exist FIRST. If missing, the migration tool opens a modal (“Addressables not initialized — Open Window > Asset Management > Addressables > Groups and click ‘Create Addressables Settings’”) and the MCP call times out. Bootstrap canonically viaexecute_code:Then re-run the migration menu item. Thevar t = System.AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).First(x => x.FullName == "UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject");t.GetMethod("GetSettings", new[] { typeof(bool) }).Invoke(null, new object[] { true });UnityEditor.AssetDatabase.SaveAssets();manage_addressablesMCP tool has actionslist_groups | get_group | list_entries | get_entry | list_labels | build | analyzebut noinit/create_settings— file an MCP-gap issue if the team wants this. - Editor MCP server runs in the same process as the editor — a tool that hangs hangs the editor; never call long-running operations synchronously.
- Scene mutations via MCP must be wrapped in
Undo.RecordObjectfor Ctrl+Z support — MCP edits without Undo are silently lost on Ctrl+Z. - MCP tool result size cap (
maxResultSizeChars) is per-tool — unbounded scene exports blow context window. - Tool schema can be newer than the active Unity package — if a tool action from the client schema returns
Unknown action, fall back to the matching resource or supported action list from the error instead of retrying the same action. run_testsvia MCP injects FALSE failures into log-asserting tests (bridge-disposal artifact). A large-suiterun_teststriggers a domain reload at the start of the run; during that reload the MCP bridge’s socket briefly drops and logs[Error] MCP-FOR-UNITY: Client handler error: Cannot access a disposed object. Object name: 'System.Net.Sockets.NetworkStream'(sometimes surfacing asInvalidOperationException: Collection was modifiedfrom the bridge’s handler). Unity’s NUnit global log-assertion attributes that editor-emitted Error to whatever test is inSetUp/OneTimeSetUpat that instant, failing it with a message that is NOT one of the test’s own assertions. Signature (how to recognize it is NOT a code bug): (1) the failure message containsMCP-FOR-UNITY/NetworkStream/disposed/Collection was modified, never a realAssert.*diff; (2) it lands on a different random test each run (e.g. CombatStripBackdrop one run, RespawnReadyCleanup the next); (3) it correlates with aninstance_count:0or transientUnityPackagesEnvironment@...flicker inmcpforunity://instancesmid-run. (4) Tests whoseSetUpdoesnew GameObject+AddComponentof a trivial POCO are common victims — they have no logic that can fail. Verify-green WITHOUT closing the editor (preferred — batchmode requires the exclusive project lock so you’d have to close the editor; this avoids that): run per-assembly or in small batches (run_tests assembly_names=[...]) instead of the whole suite in one shot. A tight per-assembly reload rarely coincides with a log-assertingSetUp, so isolated runs come back clean. To prove a full-suite run was green: re-run ONLY the assemblies that showed artifact-failures, in isolation — they pass, confirming every genuine test passed. A single full-suite run withfailures_capped:falsewhose entire failure set is the bridge artifact + clean isolated re-runs of those assemblies = conclusive green. Do NOT “fix” victim tests withLogAssert.Expect/ignoreFailingMessages— the tests are fine; the bridge is the noise source, and it would never fire in CI/batchmode (no bridge).- Prompting / interaction-requiring actions to AVOID (no autonomous use — they freeze the bridge). Use the alternatives in the right column unless the user explicitly says “I’ll click it.”
| ❌ Avoid (needs human click) | ✅ Use instead |
|---|---|
EditorUtility.DisplayDialog / DisplayDialogComplex in agent-invoked code | Debug.LogWarning + a --force confirm-flag in the menu name (Tools/X/Repopulate (force)); or gate behind if (Application.isBatchMode) |
EditorUtility.DisplayCancelableProgressBar left un-cleared | Always EditorUtility.ClearProgressBar() in finally; for headless flows, just Debug.Log progress |
EditorUtility.SaveFilePanel / OpenFilePanel / SaveFolderPanel / OpenFolderPanel | Hard-code the path in the MenuItem (or accept it via a serialized config) — the populator pattern in BalanceCsvImporter.cs |
EditorUtility.SaveFilePanelInProject | Direct AssetDatabase.CreateAsset(obj, "Assets/path/...") with the path baked into the populator |
AssetDatabase.ImportPackage(path, interactive: true) | AssetDatabase.ImportPackage(path, interactive: false) — the false overload is silent |
File / Save As… menu item | Direct file write + AssetDatabase.Refresh() |
File / Build And Run / File / Build Settings… → “Build” button (Save Panel) | BuildPipeline.BuildPlayer(...) directly via execute_code with explicit locationPathName |
Edit / Preferences… (modal window) | Edit ~/.config/unity3d/Editor.prefs / EditorPrefs.SetX(...) via execute_code |
Assets / Reimport All | Banned per rules/unity-forbidden-operations.md. Single-asset manage_asset(action="reimport") or targeted Library/{BurstCache,ScriptAssemblies,Bee/artifacts} rm |
Assets / Open C# Project (IDE launch popup) | Don’t invoke; the agent edits files directly anyway |
Window / Package Manager → install via UI | Edit Packages/manifest.json directly + AssetDatabase.Refresh() |
| Entering Play mode with compile errors (modal “Cannot enter Play Mode”) | Always read_console types=["error"] and assert empty BEFORE manage_editor(action="play") |
EditorApplication.Quit() while scenes are dirty (“Save changes?”) | Banned per rules/unity-forbidden-operations.md — never quit Unity from the agent |
PlayerSettings.colorSpace = Linear/Gamma (prompts “Restart required”) | Set via execute_code then warn user to restart manually if needed — do NOT call EditorApplication.Exit |
PlayerSettings.Android.keystorePass = ... setters (some pop a password prompt) | Set via execute_code only when the value comes from EditorPrefs already (don’t pop a fresh prompt) |
EditorSceneManager.SaveOpenScenes() after a structural change (may prompt per-scene) | EditorSceneManager.SaveScene(scene, path, saveAsCopy: false) per scene with explicit path |
| Addressables UI buttons (“Configure All Detected Clients”, “Install Skills”) | Banned per rules/unitymcp-the1studio-fork-only.md for the MCP client itself; for Addressables groups use AddressableAssetSettings.CreateGroup(...) via execute_code |
| MK Toon Shader auto-configure popup on first import | Pre-create the config asset, or live with the noise (third-party — read-only) |
Unity Hub update / sign-in dialogs | Never invoked from the agent; ignore if surfaced |
Detection rule of thumb: if the API name contains Dialog, Panel, Picker, Wizard, Prompt, Confirm, Browse, or Show, assume it pops a modal. If the API takes an interactive: bool overload, always pass false from agent code. If a third-party tool’s [MenuItem] invokes any of the above, treat the whole menu item as unsafe for execute_menu_item and reach for execute_code against the underlying static method instead.
-
UPM “Registry configuration is invalid” / mass package-missing / GUID conflicts /
refresh_unitytimeout → all four have surgical fixes documented inreferences/upm-gotchas.md. Always grep the Editor log for"defined by more than one registry"andpgrep AssetImportWorkerBEFORE concluding “the bridge is broken” or “every package is missing.” -
Parallel teammates issuing MCP asset operations race on
Library/Artifacts/moves. Symptom: Unity opens a modal"Moving file failed — Moving Library/TempArtifacts/Primary/<hash> to Library/Artifacts/<hash>/<hash>"with [Try Again / Force Quit / Cancel] buttons. Root cause: Unity’sAssetImportWorkerprocesses hold filesystem locks while moving freshly-imported artifacts fromTempArtifacts/toArtifacts/; when two MCP-triggered imports race on the same artifact (or its parent directory), the OS-level move fails withEBUSY/ “file in use” and Unity falls back to the blocking dialog. Frequency rises sharply with teammate count: 1 active MCP teammate = safe; 2 = occasional; 3+ = frequent. Recovery (user must act on the modal — agent cannot dismiss it via MCP): click Try Again first (usually clears within seconds as the other worker finishes); if Try Again fails 5+ times, click Cancel (aborts THIS one artifact move — Unity re-imports on next refresh, recoverable). NEVER click Force Quit — perrules/unity-forbidden-operations.mdkilling Unity loses unsaved scenes/prefabs/Inspector state. Reopening Unity is NOT needed; the dialog blocks the main thread but Unity itself is fine. Prevention (agent-side): when running multiple teammates in parallel viaTeamCreate, designate ONE teammate at a time as the MCP-actions owner. Other teammates can do file Reads, Grep, Bash, and analysis, but should NOT issuerefresh_unity/execute_menu_item/manage_asset/manage_prefabs/ asset-mutation MCP calls until the active MCP-owner finishes. Alternatively, serialize MCP-heavy phases in waves rather than concurrent fan-out. The race window aligns with Unity’s domain-reload + asset-import cycles, which any MCP write triggers. -
Stale Roslyn/Mono compile cache can lag behind source after many recompile cycles. Symptom: edit a
.csfile, source change is visible,refresh_unity(mode="force", scope="scripts", compile="request")completes,read_consolereports zero CS errors, but the DLL still executes the OLD code path — runtime exception stack traces point to OLD line numbers, or decompilation of the loaded DLL shows old IL. Root cause: Unity’s incremental compile cache (Roslyn +Library/Bee/artifacts/) occasionally fails to invalidate one compilation unit even though source mtime advanced. No error is logged — the cache is technically valid, just stale. Diagnosis signals (all three together): (1) runtime exception cites the OLD line numbers / OLD code path, (2) source has the fix butLibrary/ScriptAssemblies/<asmdef>.dllshows recent mtime, (3) the fix IS in the latest git commit. Recovery:rm -rf Library/Bee/artifacts Library/ScriptAssembliesthenrefresh_unity(mode="force", scope="all", compile="request", wait_for_ready=true)forces a full re-link. Alternatively, ask the user to restart the Unity Editor — cold-start domain reload always picks up fresh DLLs. Anti-pattern: do NOT callAssets/Reimport All— same recovery at 30+ min cost (banned perrules/unity-forbidden-operations.md). When NOT to nuke the cache: don’t reach for this on every Burst error or test failure — only when the source/DLL-IL divergence is verified (e.g., the new fix is committed, the source line matches the expected behavior, but the runtime trace still cites the pre-fix line). Random cache-nukes cost 5-20 min per cycle. -
execute_menu_itemtimeout ≠ menu did not fire — verify via filesystem mtime / scene-state before retrying. Whenexecute_menu_itemreturnsMCP error -32001: Request timed out, the menu often DID fire but a downstream operation (long bake, focus-steal, lightmap rebuild) exceeded the MCP per-tool timeout. Retrying re-fires the menu — duplicate prefabs, doubled spawner registrations, two copies of a populated ScriptableObject. Verify before retry: (1) asset-creating menu →stat -c '%Y %n' <output/path>before and after, mtime advance = menu fired; (2) scene-mutating menu →find_gameobjects(name="ExpectedName")ormanage_scene(action="get_info")for the new objects; (3) build/bake → checkLibrary/com.unity.addressables/*or*-LightingData.assetmtime; (4) fallback →read_console(types=["log"], count=20)for the menu’s typical success log. Naive retry cost is high — a single doubled menu fire can fail downstream gates like no-duplicate-addresses; mtime check costs zero. -
manage_asset(action="reimport")on a freshly-written.csfile silently no-ops — AssetDatabase status staysUnknown, scripts never compile. When an agent writes a brand-new.csfile viaWrite/File.WriteAllTextand immediately callsmanage_asset(action="reimport", path="Assets/Foo.cs"), the call returns success but the AssetDatabase doesn’t import the file (status: "Unknown", guid: null).read_consoleshows no compile errors because Unity never noticed the file. Downstream: the new MonoBehaviour / IComponentData “doesn’t exist”, baker can’t find it, tests fail withTypeNotFound. Root cause:manage_asset(action="reimport")callsAssetDatabase.ImportAsset(path, ImportAssetOptions.Default)which assumes the asset is already tracked. For a file the AssetDatabase has never seen, it does nothing. Correct pattern for new.csfiles:Write→refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True)→read_console(types=["error"]).refresh_unitycallsAssetDatabase.Refresh()which scans disk and imports untracked files. For edits to existing.csfiles,manage_asset(action="reimport")works correctly — only the new-file case is broken. File an MCP-gap issue againstThe1Studio/unity-mcpto either auto-detect untracked files inmanage_asset(action="reimport")or surface a clear error. -
Multi-teammate MCP race silently kills the Unity bridge — designate ONE MCP-owner when running 6+ parallel teammates. Complementary to the parallel-teammate
Library/Artifacts/gotcha above (the modal-blocking failure mode). This entry covers the silent-degradation mode that happens BEFORE the modal pops. When ≥6 parallel teammates each issue MCP calls within the same minute, the bridge degrades: (1)set_active_instancecalls race — instance hash flips between the project Editor and ephemeral package-environment Editors every few seconds; (2)refresh_unityreturns{ "status": "recovered" }butLibrary/ScriptAssemblies/*.dllmtime never advances — i.e. compile was never actually requested; (3)read_consolereturns empty or stale results for long windows even thoughAssetImportWorkeris idle; (4) last known-good heartbeat appears in only ONE teammate’s session log; others see only timeouts. Root cause: MCP bridge is single-threaded per-Editor-instance. Concurrentset_active_instancecalls overwrite each other’s session-level routing. Parallelrefresh_unitycalls queue; the Editor processes the first, the rest return cached “recovered” responses without re-executing. Mitigation —MCP-ownerdesignation: when the team-lead spawns ≥6 parallel teammates that will need MCP, designate ONE as the MCP-owner. All MCP calls (refresh_unity,read_console,run_tests,manage_*) MUST go through that teammate viaSendMessage. Other teammates work with filesystem + Bash only (Write,Edit,git status,find,grep). At the end of each phase, MCP-owner runs batchedrefresh_unity+read_consoleand reports compile-clean. For Gate 3 (compile-clean) and Gate 4 (test-pass), defer to a final dots-tester teammate that runs sequentially after all parallel work lands. -
Linux/Wayland focus-steal during MCP tool calls is Unity Editor behavior, NOT MCP —
AssetDatabase.Refresh(),CompilationPipeline.RequestScriptCompilation(), and Test Runner activation all cause Unity to grab focus from whatever window the user is in, on every script edit /refresh_unity/run_testscall. The MCP package itself never callsEditorWindow.Focus()(verified via grep: only one read-onlyfocusedWindowreference). Fix is window-manager-level, not MCP-level. For KDE Plasma 6 + KWin Wayland: write a window rule to~/.config/kwinrulesrcmatchingwmclass=Unity(the Editor’s WM_CLASS is literallyUnity, notUnityEditor— verify withkdotool getwindowclassname <id>before writing the rule),wmclassmatch=1(Exact),fsplevel=4(Extreme prevention),fsplevelrule=2(Force). Thenqdbus6 org.kde.KWin /KWin reconfigure. Verify with the test job result’seditor_is_focused: falsefield after arun_testscall. For i3/Sway:for_window [class="Unity"] focus_on_window_activation none. For GNOME:gsettings set org.gnome.desktop.wm.preferences focus-mode 'click'(partial). -
manage_asset(action="create")does NOT support arbitrary ScriptableObject types — only Folder, Material, and PhysicsMaterial.execute_codeis the next approach but on Windows hits a Mono “filename too long” error (~260-char path limit) on both roslyn and codedom backends. Reliable fallback on all platforms: write the.assetYAML directly to disk using the script GUID from its.cs.metafile, then callrefresh_unity(mode="force", scope="assets"). Full YAML template, field-name rules, and step-by-step guide:references/workflow-scriptableobject-creation.md.
Field reports for the gotchas in this section: see references/incidents.md.
MCP Gaps — FIX-FIRST in our fork, do NOT just file an issue
Section titled “MCP Gaps — FIX-FIRST in our fork, do NOT just file an issue”The1Studio/unity-mcp is OUR editable fork (beta branch). When an MCP tool is actually BROKEN (a server-side code defect — crash, exception, wrong result, missing await), FIX IT DIRECTLY in the fork — do NOT settle for filing an [t1k:mcp-gap] issue. Filing-only leaves the bug live in every session; we are the only ones who ship this fork, so “file and wait” never resolves. Fixing is almost always faster than the round-trip of issue → triage → someone-else-fixes.
Established 2026-05-30:
rendering_statswas 100% broken ('coroutine' object has no attribute 'strip'). The systemic root cause — a missingawaiton the asyncget_unity_instance_from_context— affected 22 tool files, all patched in one PR (#12) once we fixed-first instead of only filing issue #7. User directive: “don’t file an MCP issue, fix it right away — we can only fix it in the project.”
Fix-first procedure (server-side Python defect in the fork)
Section titled “Fix-first procedure (server-side Python defect in the fork)”- Locate the source. The Python server is the
Server/subdir ofThe1Studio/unity-mcp. The in-project submodulePackages/com.coplaydev.unity-mcp/may contain it, but it can be on a feature branch with uncommitted work — safest is a fresh clone:git clone --branch beta [email protected]:The1Studio/unity-mcp.git /tmp/unity-mcp-fix. - Diagnose the real root cause (read the handler + the helper it calls; confirm types/async-ness). Then sweep the whole tree for the same pattern — these bugs are usually systemic (one missing-
awaitpattern repeated across many tool files), not a single site. - Patch +
python -m py_compileevery changed file; re-grep to confirm zero remaining occurrences. - Branch off
beta, commit (conventional, reference the issue if one exists), push, open a PR tobeta. Do NOT merge — leave for review. Remove the temp clone after pushing. - Repo is editable, NOT a
theonekit-*kit repo → the kit-PR-boundary rule does NOT apply; you may push the branch + open the PR from a consumer session.
When to emit [t1k:mcp-gap] instead (the narrow file-only path)
Section titled “When to emit [t1k:mcp-gap] instead (the narrow file-only path)”Reserve the marker/issue path for cases you genuinely cannot fix-first:
- A feature request (the tool lacks an action/capability) requiring design/upstream coordination — not a defect.
- The fix needs C# Unity-bridge changes you can’t validate without an Editor round-trip, or it’s genuinely non-trivial.
- You’re mid-task and can’t context-switch — emit the marker AND tell the user a fix-first PR is the real follow-up.
Marker syntax (all four attributes required):
[t1k:mcp-gap kit="unity" tool="<tool-name>" gap="<one-line summary>" evidence="<verbatim error or repro>"]Examples:
[t1k:mcp-gap kit="unity" tool="manage_dots" gap="action='set_chunk_capacity' returns Unknown action" evidence="manage_dots(action='set_chunk_capacity', archetype='Player') → {error: 'Unknown action: set_chunk_capacity'}"]
[t1k:mcp-gap kit="unity" tool="manage_terrain" gap="paint_layer rejects normalized splat weights >1.0 silently" evidence="manage_terrain(action='paint_layer', weight=1.5) returns ok but no pixels written"]When NOT to emit:
- Transient errors (compilation pending, domain reload) — retry instead.
- Misuse on your part (wrong parameter type, missing required arg) — fix the call.
- Already-known gaps documented in
references/error-recovery-guide.md— don’t re-file. - Anything that’s a fork-feature request requiring upstream coordination — escalate to the user instead.
The marker is sanitized (paths/secrets stripped), fingerprinted, and 7-day deduped. Rate-limited to 5 markers per session shared with [t1k:lesson] and [t1k:skill-bug].