diff --git a/unity-mcp-skill/SKILL.md b/unity-mcp-skill/SKILL.md new file mode 100644 index 0000000..40a8541 --- /dev/null +++ b/unity-mcp-skill/SKILL.md @@ -0,0 +1,214 @@ +--- +name: unity-mcp-orchestrator +description: Orchestrate Unity Editor via MCP (Model Context Protocol) tools and resources. Use when working with Unity projects through MCP for Unity - creating/modifying GameObjects, editing scripts, managing scenes, running tests, or any Unity Editor automation. Provides best practices, tool schemas, and workflow patterns for effective Unity-MCP integration. +--- + +# Unity-MCP Operator Guide + +This skill helps you effectively use the Unity Editor with MCP tools and resources. + +## Quick Start: Resource-First Workflow + +**Always read relevant resources before using tools.** This prevents errors and provides the necessary context. + +``` +1. Check editor state → mcpforunity://editor/state +2. Understand the scene → mcpforunity://scene/gameobject-api +3. Find what you need → find_gameobjects or resources +4. Take action → tools (manage_gameobject, create_script, script_apply_edits, apply_text_edits, validate_script, delete_script, get_sha, etc.) +5. Verify results → read_console, capture_screenshot (in manage_scene), resources +``` + +## Critical Best Practices + +### 1. After Writing/Editing Scripts: Always Refresh and Check Console + +```python +# After create_script or script_apply_edits: +refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) +read_console(types=["error"], count=10, include_stacktrace=True) +``` + +**Why:** Unity must compile scripts before they're usable. Compilation errors block all tool execution. + +### 2. Use `batch_execute` for Multiple Operations + +```python +# 10-100x faster than sequential calls +batch_execute( + commands=[ + {"tool": "manage_gameobject", "params": {"action": "create", "name": "Cube1", "primitive_type": "Cube"}}, + {"tool": "manage_gameobject", "params": {"action": "create", "name": "Cube2", "primitive_type": "Cube"}}, + {"tool": "manage_gameobject", "params": {"action": "create", "name": "Cube3", "primitive_type": "Cube"}} + ], + parallel=True # Read-only operations can run in parallel +) +``` + +**Max 25 commands per batch.** Use `fail_fast=True` for dependent operations. + +### 3. Use `screenshot` in manage_scene to Verify Visual Results + +```python +# Via manage_scene +manage_scene(action="screenshot") # Returns base64 image + +# After creating/modifying objects, verify visually: +# 1. Create objects +# 2. capture screenshot +# 3. Analyze if result matches intent +``` + +### 4. Check Console After Major Changes + +```python +read_console( + action="get", + types=["error", "warning"], # Focus on problems + count=10, + format="detailed" +) +``` + +### 5. Always Check `editor_state` Before Complex Operations + +```python +# Read mcpforunity://editor/state to check: +# - is_compiling: Wait if true +# - is_domain_reload_pending: Wait if true +# - ready_for_tools: Only proceed if true +# - blocking_reasons: Why tools might fail +``` + +## Parameter Type Conventions + +### Vectors (position, rotation, scale, color) +```python +# Both forms accepted: +position=[1.0, 2.0, 3.0] # List +position="[1.0, 2.0, 3.0]" # JSON string +``` + +### Booleans +```python +# Both forms accepted: +include_inactive=True # Boolean +include_inactive="true" # String +``` + +### Colors +```python +# Auto-detected format: +color=[255, 0, 0, 255] # 0-255 range +color=[1.0, 0.0, 0.0, 1.0] # 0.0-1.0 normalized (auto-converted) +``` + +### Paths +```python +# Assets-relative (default): +path="Assets/Scripts/MyScript.cs" + +# URI forms: +uri="mcpforunity://path/Assets/Scripts/MyScript.cs" +uri="file:///full/path/to/file.cs" +``` + +## Core Tool Categories + +| Category | Key Tools | Use For | +|----------|-----------|---------| +| **Scene** | `manage_scene`, `find_gameobjects` | Scene operations, finding objects | +| **Objects** | `manage_gameobject`, `manage_components` | Creating/modifying GameObjects | +| **Scripts** | `create_script`, `script_apply_edits`, `refresh_unity` | C# code management | +| **Assets** | `manage_asset`, `manage_prefabs` | Asset operations | +| **Editor** | `manage_editor`, `execute_menu_item`, `read_console` | Editor control | +| **Testing** | `run_tests`, `get_test_job` | Unity Test Framework | +| **Batch** | `batch_execute` | Parallel/bulk operations | + +## Common Workflows + +### Creating a New Script and Using It + +```python +# 1. Create the script +create_script( + path="Assets/Scripts/PlayerController.cs", + contents="using UnityEngine;\n\npublic class PlayerController : MonoBehaviour\n{\n void Update() { }\n}" +) + +# 2. CRITICAL: Refresh and wait for compilation +refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) + +# 3. Check for compilation errors +read_console(types=["error"], count=10) + +# 4. Only then attach to GameObject +manage_gameobject(action="modify", target="Player", components_to_add=["PlayerController"]) +``` + +### Finding and Modifying GameObjects + +```python +# 1. Find by name/tag/component (returns IDs only) +result = find_gameobjects(search_term="Enemy", search_method="by_tag", page_size=50) + +# 2. Get full data via resource +# mcpforunity://scene/gameobject/{instance_id} + +# 3. Modify using the ID +manage_gameobject(action="modify", target=instance_id, position=[10, 0, 0]) +``` + +### Running and Monitoring Tests + +```python +# 1. Start test run (async) +result = run_tests(mode="EditMode", test_names=["MyTests.TestSomething"]) +job_id = result["job_id"] + +# 2. Poll for completion +result = get_test_job(job_id=job_id, wait_timeout=60, include_failed_tests=True) +``` + +## Pagination Pattern + +Large queries return paginated results. Always follow `next_cursor`: + +```python +cursor = 0 +all_items = [] +while True: + result = manage_scene(action="get_hierarchy", page_size=50, cursor=cursor) + all_items.extend(result["data"]["items"]) + if not result["data"].get("next_cursor"): + break + cursor = result["data"]["next_cursor"] +``` + +## Multi-Instance Workflow + +When multiple Unity Editors are running: + +```python +# 1. List instances via resource: mcpforunity://instances +# 2. Set active instance +set_active_instance(instance="MyProject@abc123") +# 3. All subsequent calls route to that instance +``` + +## Error Recovery + +| Symptom | Cause | Solution | +|---------|-------|----------| +| Tools return "busy" | Compilation in progress | Wait, check `editor_state` | +| "stale_file" error | File changed since SHA | Re-fetch SHA with `get_sha`, retry | +| Connection lost | Domain reload | Wait ~5s, reconnect | +| Commands fail silently | Wrong instance | Check `set_active_instance` | + +## Reference Files + +For detailed schemas and examples: + +- **[tools-reference.md](references/tools-reference.md)**: Complete tool documentation with all parameters +- **[resources-reference.md](references/resources-reference.md)**: All available resources and their data +- **[workflows.md](references/workflows.md)**: Extended workflow examples and patterns diff --git a/unity-mcp-skill/references/resources-reference.md b/unity-mcp-skill/references/resources-reference.md new file mode 100644 index 0000000..0bc476f --- /dev/null +++ b/unity-mcp-skill/references/resources-reference.md @@ -0,0 +1,493 @@ +# Unity-MCP Resources Reference + +Resources provide read-only access to Unity state. Use resources to inspect before using tools to modify. + +## Table of Contents + +- [Editor State Resources](#editor-state-resources) +- [Scene & GameObject Resources](#scene--gameobject-resources) +- [Prefab Resources](#prefab-resources) +- [Project Resources](#project-resources) +- [Instance Resources](#instance-resources) +- [Test Resources](#test-resources) + +--- + +## URI Scheme + +All resources use `mcpforunity://` scheme: + +``` +mcpforunity://{category}/{resource_path}[?query_params] +``` + +**Categories:** `editor`, `scene`, `prefab`, `project`, `menu-items`, `custom-tools`, `tests`, `instances` + +--- + +## Editor State Resources + +### mcpforunity://editor/state + +**Purpose:** Editor readiness snapshot - check before tool operations. + +**Returns:** +```json +{ + "unity_version": "2022.3.10f1", + "is_compiling": false, + "is_domain_reload_pending": false, + "play_mode": { + "is_playing": false, + "is_paused": false + }, + "active_scene": { + "path": "Assets/Scenes/Main.unity", + "name": "Main" + }, + "ready_for_tools": true, + "blocking_reasons": [], + "recommended_retry_after_ms": null, + "staleness": { + "age_ms": 150, + "is_stale": false + } +} +``` + +**Key Fields:** +- `ready_for_tools`: Only proceed if `true` +- `is_compiling`: Wait if `true` +- `blocking_reasons`: Array explaining why tools might fail +- `recommended_retry_after_ms`: Suggested wait time + +### mcpforunity://editor/selection + +**Purpose:** Currently selected objects. + +**Returns:** +```json +{ + "activeObject": "Player", + "activeGameObject": "Player", + "activeInstanceID": 12345, + "count": 3, + "gameObjects": ["Player", "Enemy", "Wall"], + "assetGUIDs": [] +} +``` + +### mcpforunity://editor/active-tool + +**Purpose:** Current editor tool state. + +**Returns:** +```json +{ + "activeTool": "Move", + "isCustom": false, + "pivotMode": "Center", + "pivotRotation": "Global" +} +``` + +### mcpforunity://editor/windows + +**Purpose:** All open editor windows. + +**Returns:** +```json +{ + "windows": [ + { + "title": "Scene", + "typeName": "UnityEditor.SceneView", + "isFocused": true, + "position": {"x": 0, "y": 0, "width": 800, "height": 600} + } + ] +} +``` + +### mcpforunity://editor/prefab-stage + +**Purpose:** Current prefab editing context. + +**Returns:** +```json +{ + "isOpen": true, + "assetPath": "Assets/Prefabs/Player.prefab", + "prefabRootName": "Player", + "isDirty": false +} +``` + +--- + +## Scene & GameObject Resources + +### mcpforunity://scene/gameobject-api + +**Purpose:** Documentation for GameObject resources (read this first). + +### mcpforunity://scene/gameobject/{instance_id} + +**Purpose:** Basic GameObject data (metadata, no component properties). + +**Parameters:** +- `instance_id` (int): GameObject instance ID from `find_gameobjects` + +**Returns:** +```json +{ + "instanceID": 12345, + "name": "Player", + "tag": "Player", + "layer": 8, + "layerName": "Player", + "active": true, + "activeInHierarchy": true, + "isStatic": false, + "transform": { + "position": [0, 1, 0], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + "parent": {"instanceID": 0}, + "children": [{"instanceID": 67890}], + "componentTypes": ["Transform", "Rigidbody", "PlayerController"], + "path": "/Player" +} +``` + +### mcpforunity://scene/gameobject/{instance_id}/components + +**Purpose:** All components with full property serialization (paginated). + +**Parameters:** +- `instance_id` (int): GameObject instance ID +- `page_size` (int): Default 25, max 100 +- `cursor` (int): Pagination cursor +- `include_properties` (bool): Default true, set false for just types + +**Returns:** +```json +{ + "gameObjectID": 12345, + "gameObjectName": "Player", + "components": [ + { + "type": "Transform", + "properties": { + "position": {"x": 0, "y": 1, "z": 0}, + "rotation": {"x": 0, "y": 0, "z": 0, "w": 1} + } + }, + { + "type": "Rigidbody", + "properties": { + "mass": 1.0, + "useGravity": true + } + } + ], + "cursor": 0, + "pageSize": 25, + "nextCursor": null, + "hasMore": false +} +``` + +### mcpforunity://scene/gameobject/{instance_id}/component/{component_name} + +**Purpose:** Single component with full properties. + +**Parameters:** +- `instance_id` (int): GameObject instance ID +- `component_name` (string): e.g., "Rigidbody", "Camera", "Transform" + +**Returns:** +```json +{ + "gameObjectID": 12345, + "gameObjectName": "Player", + "component": { + "type": "Rigidbody", + "properties": { + "mass": 1.0, + "drag": 0, + "angularDrag": 0.05, + "useGravity": true, + "isKinematic": false + } + } +} +``` + +--- + +## Prefab Resources + +### mcpforunity://prefab-api + +**Purpose:** Documentation for prefab resources. + +### mcpforunity://prefab/{encoded_path} + +**Purpose:** Prefab asset information. + +**Parameters:** +- `encoded_path` (string): URL-encoded path, e.g., `Assets%2FPrefabs%2FPlayer.prefab` + +**Path Encoding:** +``` +Assets/Prefabs/Player.prefab → Assets%2FPrefabs%2FPlayer.prefab +``` + +**Returns:** +```json +{ + "assetPath": "Assets/Prefabs/Player.prefab", + "guid": "abc123...", + "prefabType": "Regular", + "rootObjectName": "Player", + "rootComponentTypes": ["Transform", "PlayerController"], + "childCount": 5, + "isVariant": false, + "parentPrefab": null +} +``` + +### mcpforunity://prefab/{encoded_path}/hierarchy + +**Purpose:** Full prefab hierarchy with nested prefab info. + +**Returns:** +```json +{ + "prefabPath": "Assets/Prefabs/Player.prefab", + "total": 6, + "items": [ + { + "name": "Player", + "instanceId": 12345, + "path": "/Player", + "activeSelf": true, + "childCount": 2, + "componentTypes": ["Transform", "PlayerController"] + }, + { + "name": "Model", + "path": "/Player/Model", + "isNestedPrefab": true, + "nestedPrefabPath": "Assets/Prefabs/PlayerModel.prefab" + } + ] +} +``` + +--- + +## Project Resources + +### mcpforunity://project/info + +**Purpose:** Static project configuration. + +**Returns:** +```json +{ + "projectRoot": "/Users/dev/MyProject", + "projectName": "MyProject", + "unityVersion": "2022.3.10f1", + "platform": "StandaloneWindows64", + "assetsPath": "/Users/dev/MyProject/Assets" +} +``` + +### mcpforunity://project/tags + +**Purpose:** All tags defined in TagManager. + +**Returns:** +```json +["Untagged", "Respawn", "Finish", "EditorOnly", "MainCamera", "Player", "GameController", "Enemy"] +``` + +### mcpforunity://project/layers + +**Purpose:** All layers with indices (0-31). + +**Returns:** +```json +{ + "0": "Default", + "1": "TransparentFX", + "2": "Ignore Raycast", + "4": "Water", + "5": "UI", + "8": "Player", + "9": "Enemy" +} +``` + +### mcpforunity://menu-items + +**Purpose:** All available Unity menu items. + +**Returns:** +```json +[ + "File/New Scene", + "File/Open Scene", + "File/Save", + "Edit/Undo", + "Edit/Redo", + "GameObject/Create Empty", + "GameObject/3D Object/Cube", + "Window/General/Console" +] +``` + +### mcpforunity://custom-tools + +**Purpose:** Custom tools available in the active Unity project. + +**Returns:** +```json +{ + "project_id": "MyProject", + "tool_count": 3, + "tools": [ + { + "name": "capture_screenshot", + "description": "Capture screenshots in Unity", + "parameters": [ + {"name": "filename", "type": "string", "required": true}, + {"name": "width", "type": "int", "required": false}, + {"name": "height", "type": "int", "required": false} + ] + } + ] +} +``` + +--- + +## Instance Resources + +### mcpforunity://instances + +**Purpose:** All running Unity Editor instances (for multi-instance workflows). + +**Returns:** +```json +{ + "transport": "http", + "instance_count": 2, + "instances": [ + { + "id": "MyProject@abc123", + "name": "MyProject", + "hash": "abc123", + "unity_version": "2022.3.10f1", + "connected_at": "2024-01-15T10:30:00Z" + }, + { + "id": "TestProject@def456", + "name": "TestProject", + "hash": "def456", + "unity_version": "2022.3.10f1", + "connected_at": "2024-01-15T11:00:00Z" + } + ], + "warnings": [] +} +``` + +**Use with:** `set_active_instance(instance="MyProject@abc123")` + +--- + +## Test Resources + +### mcpforunity://tests + +**Purpose:** All tests in the project. + +**Returns:** +```json +[ + { + "name": "TestSomething", + "full_name": "MyTests.TestSomething", + "mode": "EditMode" + }, + { + "name": "TestOther", + "full_name": "MyTests.TestOther", + "mode": "PlayMode" + } +] +``` + +### mcpforunity://tests/{mode} + +**Purpose:** Tests filtered by mode. + +**Parameters:** +- `mode` (string): "EditMode" or "PlayMode" + +**Example:** `mcpforunity://tests/EditMode` + +--- + +## Best Practices + +### 1. Check Editor State First + +```python +# Before any complex operation: +# Read mcpforunity://editor/state +# Check ready_for_tools == true +``` + +### 2. Use Find Then Read Pattern + +```python +# 1. find_gameobjects to get IDs +result = find_gameobjects(search_term="Player") + +# 2. Read resource for full data +# mcpforunity://scene/gameobject/{id} +``` + +### 3. Paginate Large Queries + +```python +# Start with include_properties=false for component lists +# mcpforunity://scene/gameobject/{id}/components?include_properties=false&page_size=25 + +# Then read specific components as needed +# mcpforunity://scene/gameobject/{id}/component/Rigidbody +``` + +### 4. URL-Encode Prefab Paths + +```python +# Wrong: +# mcpforunity://prefab/Assets/Prefabs/Player.prefab + +# Correct: +# mcpforunity://prefab/Assets%2FPrefabs%2FPlayer.prefab +``` + +### 5. Multi-Instance Awareness + +```python +# Always check mcpforunity://instances when: +# - First connecting +# - Commands fail unexpectedly +# - Working with multiple projects +``` diff --git a/unity-mcp-skill/references/tools-reference.md b/unity-mcp-skill/references/tools-reference.md new file mode 100644 index 0000000..f4d7555 --- /dev/null +++ b/unity-mcp-skill/references/tools-reference.md @@ -0,0 +1,606 @@ +# Unity-MCP Tools Reference + +Complete reference for all MCP tools. Each tool includes parameters, types, and usage examples. + +## Table of Contents + +- [Infrastructure Tools](#infrastructure-tools) +- [Scene Tools](#scene-tools) +- [GameObject Tools](#gameobject-tools) +- [Script Tools](#script-tools) +- [Asset Tools](#asset-tools) +- [Material & Shader Tools](#material--shader-tools) +- [Editor Control Tools](#editor-control-tools) +- [Testing Tools](#testing-tools) + +--- + +## Infrastructure Tools + +### batch_execute + +Execute multiple MCP commands in a single batch (10-100x faster). + +```python +batch_execute( + commands=[ # list[dict], required, max 25 + {"tool": "tool_name", "params": {...}}, + ... + ], + parallel=False, # bool, optional - run read-only ops in parallel + fail_fast=False, # bool, optional - stop on first failure + max_parallelism=None # int, optional - max parallel workers +) +``` + +### set_active_instance + +Route commands to a specific Unity instance (multi-instance workflows). + +```python +set_active_instance( + instance="ProjectName@abc123" # str, required - Name@hash or hash prefix +) +``` + +### refresh_unity + +Refresh asset database and trigger script compilation. + +```python +refresh_unity( + mode="if_dirty", # "if_dirty" | "force" + scope="all", # "assets" | "scripts" | "all" + compile="none", # "none" | "request" + wait_for_ready=True # bool - wait until editor ready +) +``` + +--- + +## Scene Tools + +### manage_scene + +Scene CRUD operations and hierarchy queries. + +```python +# Get hierarchy (paginated) +manage_scene( + action="get_hierarchy", + page_size=50, # int, default 50, max 500 + cursor=0, # int, pagination cursor + parent=None, # str|int, optional - filter by parent + include_transform=False # bool - include local transforms +) + +# Screenshot +manage_scene(action="screenshot") # Returns base64 PNG + +# Other actions +manage_scene(action="get_active") # Current scene info +manage_scene(action="get_build_settings") # Build settings +manage_scene(action="create", name="NewScene", path="Assets/Scenes/") +manage_scene(action="load", path="Assets/Scenes/Main.unity") +manage_scene(action="save") +``` + +### find_gameobjects + +Search for GameObjects (returns instance IDs only). + +```python +find_gameobjects( + search_term="Player", # str, required + search_method="by_name", # "by_name"|"by_tag"|"by_layer"|"by_component"|"by_path"|"by_id" + include_inactive=False, # bool|str + page_size=50, # int, default 50, max 500 + cursor=0 # int, pagination cursor +) +# Returns: {"ids": [12345, 67890], "next_cursor": 50, ...} +``` + +--- + +## GameObject Tools + +### manage_gameobject + +Create, modify, delete, duplicate GameObjects. + +```python +# Create +manage_gameobject( + action="create", + name="MyCube", # str, required + primitive_type="Cube", # "Cube"|"Sphere"|"Capsule"|"Cylinder"|"Plane"|"Quad" + position=[0, 1, 0], # list[float] or JSON string "[0,1,0]" + rotation=[0, 45, 0], # euler angles + scale=[1, 1, 1], + components_to_add=["Rigidbody", "BoxCollider"], + save_as_prefab=False, + prefab_path="Assets/Prefabs/MyCube.prefab" +) + +# Modify +manage_gameobject( + action="modify", + target="Player", # name, path, or instance ID + search_method="by_name", # how to find target + position=[10, 0, 0], + rotation=[0, 90, 0], + scale=[2, 2, 2], + set_active=True, + layer="Player", + components_to_add=["AudioSource"], + components_to_remove=["OldComponent"], + component_properties={ # nested dict for property setting + "Rigidbody": { + "mass": 10.0, + "useGravity": True + } + } +) + +# Delete +manage_gameobject(action="delete", target="OldObject") + +# Duplicate +manage_gameobject( + action="duplicate", + target="Player", + new_name="Player2", + offset=[5, 0, 0] # position offset from original +) + +# Move relative +manage_gameobject( + action="move_relative", + target="Player", + reference_object="Enemy", # optional reference + direction="left", # "left"|"right"|"up"|"down"|"forward"|"back" + distance=5.0, + world_space=True +) +``` + +### manage_components + +Add, remove, or set properties on components. + +```python +# Add component +manage_components( + action="add", + target=12345, # instance ID (preferred) or name + component_type="Rigidbody", + search_method="by_id" +) + +# Remove component +manage_components( + action="remove", + target="Player", + component_type="OldScript" +) + +# Set single property +manage_components( + action="set_property", + target=12345, + component_type="Rigidbody", + property="mass", + value=5.0 +) + +# Set multiple properties +manage_components( + action="set_property", + target=12345, + component_type="Transform", + properties={ + "position": [1, 2, 3], + "localScale": [2, 2, 2] + } +) +``` + +--- + +## Script Tools + +### create_script + +Create a new C# script. + +```python +create_script( + path="Assets/Scripts/MyScript.cs", # str, required + contents='''using UnityEngine; + +public class MyScript : MonoBehaviour +{ + void Start() { } + void Update() { } +}''', + script_type="MonoBehaviour", # optional hint + namespace="MyGame" # optional namespace +) +``` + +### script_apply_edits + +Apply structured edits to C# scripts (safer than raw text edits). + +```python +script_apply_edits( + name="MyScript", # script name (no .cs) + path="Assets/Scripts", # folder path + edits=[ + # Replace entire method + { + "op": "replace_method", + "methodName": "Update", + "replacement": "void Update() { transform.Rotate(Vector3.up); }" + }, + # Insert new method + { + "op": "insert_method", + "afterMethod": "Start", + "code": "void OnEnable() { Debug.Log(\"Enabled\"); }" + }, + # Delete method + { + "op": "delete_method", + "methodName": "OldMethod" + }, + # Anchor-based insert + { + "op": "anchor_insert", + "anchor": "void Start()", + "position": "before", # "before" | "after" + "text": "// Called before Start\n" + }, + # Regex replace + { + "op": "regex_replace", + "pattern": "Debug\\.Log\\(", + "text": "Debug.LogWarning(" + }, + # Prepend/append to file + {"op": "prepend", "text": "// File header\n"}, + {"op": "append", "text": "\n// File footer"} + ] +) +``` + +### apply_text_edits + +Apply precise character-position edits (1-indexed lines/columns). + +```python +apply_text_edits( + uri="mcpforunity://path/Assets/Scripts/MyScript.cs", + edits=[ + { + "startLine": 10, + "startCol": 5, + "endLine": 10, + "endCol": 20, + "newText": "replacement text" + } + ], + precondition_sha256="abc123...", # optional, prevents stale edits + strict=True # optional, stricter validation +) +``` + +### validate_script + +Check script for syntax/semantic errors. + +```python +validate_script( + uri="mcpforunity://path/Assets/Scripts/MyScript.cs", + level="standard", # "basic" | "standard" + include_diagnostics=True # include full error details +) +``` + +### get_sha + +Get file hash without content (for preconditions). + +```python +get_sha(uri="mcpforunity://path/Assets/Scripts/MyScript.cs") +# Returns: {"sha256": "...", "lengthBytes": 1234, "lastModifiedUtc": "..."} +``` + +### delete_script + +Delete a script file. + +```python +delete_script(uri="mcpforunity://path/Assets/Scripts/OldScript.cs") +``` + +--- + +## Asset Tools + +### manage_asset + +Asset operations: search, import, create, modify, delete. + +```python +# Search assets (paginated) +manage_asset( + action="search", + path="Assets", # search scope + search_pattern="*.prefab", # glob or "t:MonoScript" filter + filter_type="Prefab", # optional type filter + page_size=25, # keep small to avoid large payloads + page_number=1, # 1-based + generate_preview=False # avoid base64 bloat +) + +# Get asset info +manage_asset(action="get_info", path="Assets/Prefabs/Player.prefab") + +# Create asset +manage_asset( + action="create", + path="Assets/Materials/NewMaterial.mat", + asset_type="Material", + properties={"color": [1, 0, 0, 1]} +) + +# Duplicate/move/rename +manage_asset(action="duplicate", path="Assets/A.prefab", destination="Assets/B.prefab") +manage_asset(action="move", path="Assets/A.prefab", destination="Assets/Prefabs/A.prefab") +manage_asset(action="rename", path="Assets/A.prefab", destination="Assets/B.prefab") + +# Create folder +manage_asset(action="create_folder", path="Assets/NewFolder") + +# Delete +manage_asset(action="delete", path="Assets/OldAsset.asset") +``` + +### manage_prefabs + +Headless prefab operations. + +```python +# Get prefab info +manage_prefabs(action="get_info", prefab_path="Assets/Prefabs/Player.prefab") + +# Get prefab hierarchy +manage_prefabs(action="get_hierarchy", prefab_path="Assets/Prefabs/Player.prefab") + +# Create prefab from scene GameObject +manage_prefabs( + action="create_from_gameobject", + target="Player", # GameObject in scene + prefab_path="Assets/Prefabs/Player.prefab", + allow_overwrite=False +) + +# Modify prefab contents (headless) +manage_prefabs( + action="modify_contents", + prefab_path="Assets/Prefabs/Player.prefab", + target="ChildObject", # object within prefab + position=[0, 1, 0], + components_to_add=["AudioSource"] +) +``` + +--- + +## Material & Shader Tools + +### manage_material + +Create and modify materials. + +```python +# Create material +manage_material( + action="create", + material_path="Assets/Materials/Red.mat", + shader="Standard", + properties={"_Color": [1, 0, 0, 1]} +) + +# Get material info +manage_material(action="get_material_info", material_path="Assets/Materials/Red.mat") + +# Set shader property +manage_material( + action="set_material_shader_property", + material_path="Assets/Materials/Red.mat", + property="_Metallic", + value=0.8 +) + +# Set color +manage_material( + action="set_material_color", + material_path="Assets/Materials/Red.mat", + property="_BaseColor", + color=[0, 1, 0, 1] # RGBA +) + +# Assign to renderer +manage_material( + action="assign_material_to_renderer", + target="MyCube", + material_path="Assets/Materials/Red.mat", + slot=0 # material slot index +) + +# Set renderer color directly +manage_material( + action="set_renderer_color", + target="MyCube", + color=[1, 0, 0, 1], + mode="instance" # "shared"|"instance"|"property_block" +) +``` + +### manage_texture + +Create procedural textures. + +```python +manage_texture( + action="create", + path="Assets/Textures/Checker.png", + width=64, + height=64, + fill_color=[255, 255, 255, 255] # or [1.0, 1.0, 1.0, 1.0] +) + +# Apply pattern +manage_texture( + action="apply_pattern", + path="Assets/Textures/Checker.png", + pattern="checkerboard", # "checkerboard"|"stripes"|"dots"|"grid"|"brick" + palette=[[0,0,0,255], [255,255,255,255]], + pattern_size=8 +) + +# Apply gradient +manage_texture( + action="apply_gradient", + path="Assets/Textures/Gradient.png", + gradient_type="linear", # "linear"|"radial" + gradient_angle=45, + palette=[[255,0,0,255], [0,0,255,255]] +) +``` + +--- + +## Editor Control Tools + +### manage_editor + +Control Unity Editor state. + +```python +manage_editor(action="play") # Enter play mode +manage_editor(action="pause") # Pause play mode +manage_editor(action="stop") # Exit play mode + +manage_editor(action="set_active_tool", tool_name="Move") # Move/Rotate/Scale/etc. + +manage_editor(action="add_tag", tag_name="Enemy") +manage_editor(action="remove_tag", tag_name="OldTag") + +manage_editor(action="add_layer", layer_name="Projectiles") +manage_editor(action="remove_layer", layer_name="OldLayer") +``` + +### execute_menu_item + +Execute any Unity menu item. + +```python +execute_menu_item(menu_path="File/Save Project") +execute_menu_item(menu_path="GameObject/3D Object/Cube") +execute_menu_item(menu_path="Window/General/Console") +``` + +### read_console + +Read or clear Unity console messages. + +```python +# Get recent messages +read_console( + action="get", + types=["error", "warning", "log"], # or ["all"] + count=10, # max messages (ignored with paging) + filter_text="NullReference", # optional text filter + since_timestamp="2024-01-01T00:00:00Z", # optional time filter + page_size=50, + cursor=0, + format="detailed", # "plain"|"detailed"|"json" + include_stacktrace=True +) + +# Clear console +read_console(action="clear") +``` + +--- + +## Testing Tools + +### run_tests + +Start async test execution. + +```python +result = run_tests( + mode="EditMode", # "EditMode"|"PlayMode" + test_names=["MyTests.TestA", "MyTests.TestB"], # specific tests + group_names=["Integration*"], # regex patterns + category_names=["Unit"], # NUnit categories + assembly_names=["Tests"], # assembly filter + include_failed_tests=True, # include failure details + include_details=False # include all test details +) +# Returns: {"job_id": "abc123", ...} +``` + +### get_test_job + +Poll test job status. + +```python +result = get_test_job( + job_id="abc123", + wait_timeout=60, # wait up to N seconds + include_failed_tests=True, + include_details=False +) +# Returns: {"status": "complete"|"running"|"failed", "results": {...}} +``` + +--- + +## Search Tools + +### find_in_file + +Search file contents with regex. + +```python +find_in_file( + uri="mcpforunity://path/Assets/Scripts/MyScript.cs", + pattern="public void \\w+", # regex pattern + max_results=200, + ignore_case=True +) +# Returns: line numbers, content excerpts, match positions +``` + +--- + +## Custom Tools + +### execute_custom_tool + +Execute project-specific custom tools. + +```python +execute_custom_tool( + tool_name="my_custom_tool", + parameters={"param1": "value", "param2": 42} +) +``` + +Discover available custom tools via `mcpforunity://custom-tools` resource. diff --git a/unity-mcp-skill/references/workflows.md b/unity-mcp-skill/references/workflows.md new file mode 100644 index 0000000..f04245c --- /dev/null +++ b/unity-mcp-skill/references/workflows.md @@ -0,0 +1,609 @@ +# Unity-MCP Workflow Patterns + +Common workflows and patterns for effective Unity-MCP usage. + +## Table of Contents + +- [Setup & Verification](#setup--verification) +- [Scene Creation Workflows](#scene-creation-workflows) +- [Script Development Workflows](#script-development-workflows) +- [Asset Management Workflows](#asset-management-workflows) +- [Testing Workflows](#testing-workflows) +- [Debugging Workflows](#debugging-workflows) +- [Batch Operations](#batch-operations) + +--- + +## Setup & Verification + +### Initial Connection Verification + +```python +# 1. Check editor state +# Read mcpforunity://editor/state + +# 2. Verify ready_for_tools == true +# If false, wait for recommended_retry_after_ms + +# 3. Check active scene +# Read mcpforunity://editor/state → active_scene + +# 4. List available instances (multi-instance) +# Read mcpforunity://instances +``` + +### Before Any Operation + +```python +# Quick readiness check pattern: +editor_state = read_resource("mcpforunity://editor/state") + +if not editor_state["ready_for_tools"]: + # Check blocking_reasons + # Wait recommended_retry_after_ms + pass + +if editor_state["is_compiling"]: + # Wait for compilation to complete + pass +``` + +--- + +## Scene Creation Workflows + +### Create Complete Scene from Scratch + +```python +# 1. Create new scene +manage_scene(action="create", name="GameLevel", path="Assets/Scenes/") + +# 2. Batch create environment objects +batch_execute(commands=[ + {"tool": "manage_gameobject", "params": { + "action": "create", "name": "Ground", "primitive_type": "Plane", + "position": [0, 0, 0], "scale": [10, 1, 10] + }}, + {"tool": "manage_gameobject", "params": { + "action": "create", "name": "Light", "primitive_type": "Cube" + }}, + {"tool": "manage_gameobject", "params": { + "action": "create", "name": "Player", "primitive_type": "Capsule", + "position": [0, 1, 0] + }} +]) + +# 3. Add light component (delete cube mesh, add light) +manage_components(action="remove", target="Light", component_type="MeshRenderer") +manage_components(action="remove", target="Light", component_type="MeshFilter") +manage_components(action="remove", target="Light", component_type="BoxCollider") +manage_components(action="add", target="Light", component_type="Light") +manage_components(action="set_property", target="Light", component_type="Light", + property="type", value="Directional") + +# 4. Set up camera +manage_gameobject(action="modify", target="Main Camera", position=[0, 5, -10], + rotation=[30, 0, 0]) + +# 5. Verify with screenshot +manage_scene(action="screenshot") + +# 6. Save scene +manage_scene(action="save") +``` + +### Populate Scene with Grid of Objects + +```python +# Create 5x5 grid of cubes using batch +commands = [] +for x in range(5): + for z in range(5): + commands.append({ + "tool": "manage_gameobject", + "params": { + "action": "create", + "name": f"Cube_{x}_{z}", + "primitive_type": "Cube", + "position": [x * 2, 0, z * 2] + } + }) + +# Execute in batches of 25 +batch_execute(commands=commands[:25], parallel=True) +``` + +### Clone and Arrange Objects + +```python +# Find template object +result = find_gameobjects(search_term="Template", search_method="by_name") +template_id = result["ids"][0] + +# Duplicate in a line +for i in range(10): + manage_gameobject( + action="duplicate", + target=template_id, + new_name=f"Instance_{i}", + offset=[i * 2, 0, 0] + ) +``` + +--- + +## Script Development Workflows + +### Create New Script and Attach + +```python +# 1. Create script +create_script( + path="Assets/Scripts/EnemyAI.cs", + contents='''using UnityEngine; + +public class EnemyAI : MonoBehaviour +{ + public float speed = 5f; + public Transform target; + + void Update() + { + if (target != null) + { + Vector3 direction = (target.position - transform.position).normalized; + transform.position += direction * speed * Time.deltaTime; + } + } +}''' +) + +# 2. CRITICAL: Refresh and compile +refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) + +# 3. Check for errors +console = read_console(types=["error"], count=10) +if console["messages"]: + # Handle compilation errors + print("Compilation errors:", console["messages"]) +else: + # 4. Attach to GameObject + manage_gameobject(action="modify", target="Enemy", components_to_add=["EnemyAI"]) + + # 5. Set component properties + manage_components( + action="set_property", + target="Enemy", + component_type="EnemyAI", + properties={ + "speed": 10.0 + } + ) +``` + +### Edit Existing Script Safely + +```python +# 1. Get current SHA +sha_info = get_sha(uri="mcpforunity://path/Assets/Scripts/PlayerController.cs") + +# 2. Find the method to edit +matches = find_in_file( + uri="mcpforunity://path/Assets/Scripts/PlayerController.cs", + pattern="void Update\\(\\)" +) + +# 3. Apply structured edit +script_apply_edits( + name="PlayerController", + path="Assets/Scripts", + edits=[{ + "op": "replace_method", + "methodName": "Update", + "replacement": '''void Update() + { + float h = Input.GetAxis("Horizontal"); + float v = Input.GetAxis("Vertical"); + transform.Translate(new Vector3(h, 0, v) * speed * Time.deltaTime); + }''' + }] +) + +# 4. Validate +validate_script( + uri="mcpforunity://path/Assets/Scripts/PlayerController.cs", + level="standard" +) + +# 5. Refresh +refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) + +# 6. Check console +read_console(types=["error"], count=10) +``` + +### Add Method to Existing Class + +```python +script_apply_edits( + name="GameManager", + path="Assets/Scripts", + edits=[ + { + "op": "insert_method", + "afterMethod": "Start", + "code": ''' + public void ResetGame() + { + SceneManager.LoadScene(SceneManager.GetActiveScene().name); + }''' + }, + { + "op": "anchor_insert", + "anchor": "using UnityEngine;", + "position": "after", + "text": "\nusing UnityEngine.SceneManagement;" + } + ] +) +``` + +--- + +## Asset Management Workflows + +### Create and Apply Material + +```python +# 1. Create material +manage_material( + action="create", + material_path="Assets/Materials/PlayerMaterial.mat", + shader="Standard", + properties={ + "_Color": [0.2, 0.5, 1.0, 1.0], + "_Metallic": 0.5, + "_Glossiness": 0.8 + } +) + +# 2. Assign to renderer +manage_material( + action="assign_material_to_renderer", + target="Player", + material_path="Assets/Materials/PlayerMaterial.mat", + slot=0 +) + +# 3. Verify visually +manage_scene(action="screenshot") +``` + +### Create Procedural Texture + +```python +# 1. Create base texture +manage_texture( + action="create", + path="Assets/Textures/Checkerboard.png", + width=256, + height=256, + fill_color=[255, 255, 255, 255] +) + +# 2. Apply checkerboard pattern +manage_texture( + action="apply_pattern", + path="Assets/Textures/Checkerboard.png", + pattern="checkerboard", + palette=[[0, 0, 0, 255], [255, 255, 255, 255]], + pattern_size=32 +) + +# 3. Create material with texture +manage_material( + action="create", + material_path="Assets/Materials/CheckerMaterial.mat", + shader="Standard" +) + +# 4. Assign texture to material (via manage_material set_material_shader_property) +``` + +### Organize Assets into Folders + +```python +# 1. Create folder structure +batch_execute(commands=[ + {"tool": "manage_asset", "params": {"action": "create_folder", "path": "Assets/Prefabs"}}, + {"tool": "manage_asset", "params": {"action": "create_folder", "path": "Assets/Materials"}}, + {"tool": "manage_asset", "params": {"action": "create_folder", "path": "Assets/Scripts"}}, + {"tool": "manage_asset", "params": {"action": "create_folder", "path": "Assets/Textures"}} +]) + +# 2. Move existing assets +manage_asset(action="move", path="Assets/MyMaterial.mat", destination="Assets/Materials/MyMaterial.mat") +manage_asset(action="move", path="Assets/MyScript.cs", destination="Assets/Scripts/MyScript.cs") +``` + +### Search and Process Assets + +```python +# Find all prefabs +result = manage_asset( + action="search", + path="Assets", + search_pattern="*.prefab", + page_size=50, + generate_preview=False +) + +# Process each prefab +for asset in result["assets"]: + prefab_path = asset["path"] + # Get prefab info + info = manage_prefabs(action="get_info", prefab_path=prefab_path) + print(f"Prefab: {prefab_path}, Children: {info['childCount']}") +``` + +--- + +## Testing Workflows + +### Run Specific Tests + +```python +# 1. List available tests +# Read mcpforunity://tests/EditMode + +# 2. Run specific tests +result = run_tests( + mode="EditMode", + test_names=["MyTests.TestPlayerMovement", "MyTests.TestEnemySpawn"], + include_failed_tests=True +) +job_id = result["job_id"] + +# 3. Wait for results +final_result = get_test_job( + job_id=job_id, + wait_timeout=60, + include_failed_tests=True +) + +# 4. Check results +if final_result["status"] == "complete": + for test in final_result.get("failed_tests", []): + print(f"FAILED: {test['name']}: {test['message']}") +``` + +### Run Tests by Category + +```python +# Run all unit tests +result = run_tests( + mode="EditMode", + category_names=["Unit"], + include_failed_tests=True +) + +# Poll until complete +while True: + status = get_test_job(job_id=result["job_id"], wait_timeout=30) + if status["status"] in ["complete", "failed"]: + break +``` + +### Test-Driven Development Pattern + +```python +# 1. Write test first +create_script( + path="Assets/Tests/Editor/PlayerTests.cs", + contents='''using NUnit.Framework; +using UnityEngine; + +public class PlayerTests +{ + [Test] + public void TestPlayerStartsAtOrigin() + { + var player = new GameObject("TestPlayer"); + Assert.AreEqual(Vector3.zero, player.transform.position); + Object.DestroyImmediate(player); + } +}''' +) + +# 2. Refresh +refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) + +# 3. Run test (expect pass for this simple test) +result = run_tests(mode="EditMode", test_names=["PlayerTests.TestPlayerStartsAtOrigin"]) +get_test_job(job_id=result["job_id"], wait_timeout=30) +``` + +--- + +## Debugging Workflows + +### Diagnose Compilation Errors + +```python +# 1. Check console for errors +errors = read_console( + types=["error"], + count=20, + include_stacktrace=True, + format="detailed" +) + +# 2. For each error, find the file and line +for error in errors["messages"]: + # Parse error message for file:line info + # Use find_in_file to locate the problematic code + pass + +# 3. After fixing, refresh and check again +refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) +read_console(types=["error"], count=10) +``` + +### Investigate Missing References + +```python +# 1. Find the GameObject +result = find_gameobjects(search_term="Player", search_method="by_name") + +# 2. Get all components +# Read mcpforunity://scene/gameobject/{id}/components + +# 3. Check for null references in serialized fields +# Look for fields with null/missing values + +# 4. Find the referenced object +result = find_gameobjects(search_term="Target", search_method="by_name") + +# 5. Set the reference +manage_components( + action="set_property", + target="Player", + component_type="PlayerController", + property="target", + value={"instanceID": result["ids"][0]} # Reference by ID +) +``` + +### Check Scene State + +```python +# 1. Get hierarchy +hierarchy = manage_scene(action="get_hierarchy", page_size=100, include_transform=True) + +# 2. Find objects at unexpected positions +for item in hierarchy["data"]["items"]: + if item.get("transform", {}).get("position", [0,0,0])[1] < -100: + print(f"Object {item['name']} fell through floor!") + +# 3. Visual verification +manage_scene(action="screenshot") +``` + +--- + +## Batch Operations + +### Mass Property Update + +```python +# Find all enemies +enemies = find_gameobjects(search_term="Enemy", search_method="by_tag") + +# Update health on all enemies +commands = [] +for enemy_id in enemies["ids"]: + commands.append({ + "tool": "manage_components", + "params": { + "action": "set_property", + "target": enemy_id, + "component_type": "EnemyHealth", + "property": "maxHealth", + "value": 100 + } + }) + +# Execute in batches +for i in range(0, len(commands), 25): + batch_execute(commands=commands[i:i+25], parallel=True) +``` + +### Mass Object Creation with Variations + +```python +import random + +commands = [] +for i in range(20): + commands.append({ + "tool": "manage_gameobject", + "params": { + "action": "create", + "name": f"Tree_{i}", + "primitive_type": "Capsule", + "position": [random.uniform(-50, 50), 0, random.uniform(-50, 50)], + "scale": [1, random.uniform(2, 5), 1] + } + }) + +batch_execute(commands=commands, parallel=True) +``` + +### Cleanup Pattern + +```python +# Find all temporary objects +temps = find_gameobjects(search_term="Temp_", search_method="by_name") + +# Delete in batch +commands = [ + {"tool": "manage_gameobject", "params": {"action": "delete", "target": id}} + for id in temps["ids"] +] + +batch_execute(commands=commands, fail_fast=False) +``` + +--- + +## Error Recovery Patterns + +### Stale File Recovery + +```python +try: + apply_text_edits(uri=script_uri, edits=[...], precondition_sha256=old_sha) +except Exception as e: + if "stale_file" in str(e): + # Re-fetch SHA + new_sha = get_sha(uri=script_uri) + # Retry with new SHA + apply_text_edits(uri=script_uri, edits=[...], precondition_sha256=new_sha["sha256"]) +``` + +### Domain Reload Recovery + +```python +# After domain reload, connection may be lost +# Wait and retry pattern: +import time + +max_retries = 5 +for attempt in range(max_retries): + try: + editor_state = read_resource("mcpforunity://editor/state") + if editor_state["ready_for_tools"]: + break + except: + time.sleep(2 ** attempt) # Exponential backoff +``` + +### Compilation Block Recovery + +```python +# If tools fail due to compilation: +# 1. Check console for errors +errors = read_console(types=["error"], count=20) + +# 2. Fix the script errors +# ... edit scripts ... + +# 3. Force refresh +refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) + +# 4. Verify clean console +errors = read_console(types=["error"], count=5) +if not errors["messages"]: + # Safe to proceed with tools + pass +```