Back to Unity Mcp

Unity-MCP Workflow Patterns

unity-mcp-skill/references/workflows.md

9.6.672.9 KB
Original Source

Unity-MCP Workflow Patterns

Common workflows and patterns for effective Unity-MCP usage.

Table of Contents


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 Generator Build Workflow

Fresh Scene Before Building

Always start a generated scene build with manage_scene(action="create") to get a clean empty scene. This avoids conflicts with existing default objects (Camera, Light) that would cause "already exists" errors when the execution plan tries to create its own.

python
# Step 0: Create fresh empty scene (replaces current scene entirely)
manage_scene(action="create", name="MyGeneratedScene", path="Assets/Scenes/")

# Now proceed with the phased execution plan...
# Phase 1: Environment (camera, lights) — no conflicts
# Phase 2: Objects (GameObjects)
# Phase 3: Materials
# etc.

Wiring Object References Between Components

After creating scripts and attaching components, use set_property to wire cross-references between GameObjects. Use the {"name": "ObjectName"} format to reference scene objects by name:

python
# Wire a list of target GameObjects into a script's serialized field
manage_components(
    action="set_property",
    target="BeeManager",
    component_type="BeeManagerScript",
    property="targetObjects",
    value=[{"name": "Flower_1"}, {"name": "Flower_2"}, {"name": "Flower_3"}]
)

Physics Requirements for Trigger-Based Interactions

When scripts use OnTriggerEnter / OnTriggerStay / OnTriggerExit, at least one of the two colliding objects must have a Rigidbody component. Common pattern:

python
# Moving objects (bees, players) need Rigidbody for triggers to fire
batch_execute(commands=[
    {"tool": "manage_components", "params": {
        "action": "add", "target": "Bee_1", "component_type": "Rigidbody"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "Bee_1",
        "component_type": "Rigidbody",
        "properties": {"useGravity": false, "isKinematic": true}
    }}
])

Script Overwrites with manage_script(action="update")

When a generated script needs to be rewritten (e.g., to add auto-wiring logic), use update instead of deleting and recreating:

python
manage_script(
    action="update",
    path="Assets/Scripts/MyScript.cs",
    contents="using UnityEngine;\n\npublic class MyScript : MonoBehaviour { ... }"
)
# manage_script update auto-triggers import + compile — just wait and check console
# Read mcpforunity://editor/state → wait until is_compiling == false
read_console(types=["error"], count=10)

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_camera(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 (automatically triggers import + compilation)
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. Wait for compilation to finish
# Read mcpforunity://editor/state → wait until is_compiling == false

# 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. Wait for compilation (script_apply_edits auto-triggers import + compile)
# Read mcpforunity://editor/state → wait until is_compiling == false

# 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_camera(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']}")

Instantiate Prefab in Scene

Use manage_gameobject (not manage_prefabs) to place prefab instances in the scene.

python
# Full path
manage_gameobject(
    action="create",
    name="Enemy_1",
    prefab_path="Assets/Prefabs/Enemy.prefab",
    position=[5, 0, 3],
    parent="Enemies"
)

# Smart lookup — just the prefab name works too
manage_gameobject(action="create", name="Enemy_2", prefab_path="Enemy", position=[10, 0, 3])

# Batch-spawn multiple instances
batch_execute(commands=[
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": f"Enemy_{i}",
        "prefab_path": "Enemy", "position": [i * 3, 0, 0], "parent": "Enemies"
    }}
    for i in range(5)
])

Note: manage_prefabs is for headless prefab editing (inspect, modify contents, create from GameObject). To instantiate a prefab into the scene, always use manage_gameobject(action="create", prefab_path="...").


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. Wait for compilation (create_script auto-triggers import + compile)
# Read mcpforunity://editor/state → wait until is_compiling == false

# 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_camera(action="screenshot")

UI Creation Workflows

Unity has two UI systems: UI Toolkit (modern, recommended) and uGUI (Canvas-based, legacy). Use manage_ui for UI Toolkit workflows, and batch_execute with manage_gameobject + manage_components for uGUI.

Template warning: This section is a skill template library, not a guaranteed source of truth. Examples may be inaccurate for your Unity version, package setup, or project conventions. Use safely:

  1. Always read mcpforunity://project/info first to detect installed packages and input system.
  2. Validate component/property names against the current project.
  3. Prefer targeting by instance ID or full path over generic names.
  4. Treat numeric enum values as placeholders and verify before reuse.

Step 0: Detect Project UI Capabilities

Before creating any UI, read project info to determine which packages and input system are available.

python
# Read mcpforunity://project/info — returns:
# {
#   "renderPipeline": "BuiltIn" | "Universal" | "HighDefinition" | "Custom",
#   "activeInputHandler": "Old" | "New" | "Both",
#   "packages": {
#     "ugui": true/false,        — com.unity.ugui (Canvas, Image, Button, etc.)
#     "textmeshpro": true/false,  — com.unity.textmeshpro (TextMeshProUGUI)
#     "inputsystem": true/false,  — com.unity.inputsystem (new Input System)
#     "uiToolkit": true/false,    — UI Toolkit (always true for Unity 2021.3+)
#     "screenCapture": true/false  — ScreenCapture module enabled
#   }
# }

Decision matrix:

project_info fieldValueWhat to use
packages.uiToolkittruePreferred: Use manage_ui for UI Toolkit (UXML/USS)
packages.uguitrueCanvas-based UI (Image, Button, etc.) via batch_execute
packages.textmeshprotrueTextMeshProUGUI for text (uGUI)
packages.textmeshprofalseUnityEngine.UI.Text (legacy, lower quality)
activeInputHandler"Old"StandaloneInputModule for EventSystem (uGUI)
activeInputHandler"New"InputSystemUIInputModule for EventSystem (uGUI)
activeInputHandler"Both"Either works; prefer InputSystemUIInputModule for UI

UI Toolkit Workflows (manage_ui)

UI Toolkit uses a web-like approach: UXML (like HTML) for structure, USS (like CSS) for styling. This is the preferred UI system for new projects.

Important: Always use <ui:Style> (with the ui: namespace prefix) in UXML, not bare <Style>. UI Builder will fail to open files that use <Style> without the prefix.

Create a Complete UI Screen

python
# 1. Create UXML document (structure)
manage_ui(
    action="create",
    path="Assets/UI/MainMenu.uxml",
    contents='''<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements">
    <ui:Style src="Assets/UI/MainMenu.uss" />
    <ui:VisualElement name="root" class="root-container">
        <ui:Label text="My Game" class="title" />
        <ui:Button text="Play" name="play-btn" class="menu-button" />
        <ui:Button text="Settings" name="settings-btn" class="menu-button" />
        <ui:Button text="Quit" name="quit-btn" class="menu-button" />
    </ui:VisualElement>
</ui:UXML>'''
)

# 2. Create USS stylesheet (styling)
manage_ui(
    action="create",
    path="Assets/UI/MainMenu.uss",
    contents='''.root-container {
    flex-grow: 1;
    justify-content: center;
    align-items: center;
    background-color: rgba(0, 0, 0, 0.8);
}
.title {
    font-size: 48px;
    color: white;
    -unity-font-style: bold;
    margin-bottom: 40px;
}
.menu-button {
    width: 300px;
    height: 60px;
    font-size: 24px;
    margin: 8px;
    background-color: rgb(50, 120, 200);
    color: white;
    border-radius: 8px;
}
.menu-button:hover {
    background-color: rgb(70, 140, 220);
}'''
)

# 3. Create a GameObject and attach UIDocument
manage_gameobject(action="create", name="UIRoot")
manage_ui(
    action="attach_ui_document",
    target="UIRoot",
    source_asset="Assets/UI/MainMenu.uxml"
    # panel_settings auto-created if omitted
)

# 4. Verify the visual tree
manage_ui(action="get_visual_tree", target="UIRoot", max_depth=5)

Update Existing UI

python
# Read current content
result = manage_ui(action="read", path="Assets/UI/MainMenu.uss")
# Modify and update
manage_ui(
    action="update",
    path="Assets/UI/MainMenu.uss",
    contents=".title { font-size: 64px; color: yellow; }"
)

Custom PanelSettings

python
# Create PanelSettings with ScaleWithScreenSize
manage_ui(
    action="create_panel_settings",
    path="Assets/UI/GamePanelSettings.asset",
    scale_mode="ScaleWithScreenSize",
    reference_resolution={"width": 1920, "height": 1080}
)

# Attach UIDocument with custom PanelSettings
manage_ui(
    action="attach_ui_document",
    target="UIRoot",
    source_asset="Assets/UI/MainMenu.uxml",
    panel_settings="Assets/UI/GamePanelSettings.asset"
)

uGUI (Canvas-Based) Workflows

The sections below cover legacy Canvas-based UI using batch_execute. Use these when working with existing uGUI projects or when UI Toolkit is not suitable.

RectTransform Sizing (Critical for All UI Children)

Every GameObject under a Canvas gets a RectTransform instead of Transform. Without setting anchor/size, UI elements default to zero size and won't be visible. Use set_property on RectTransform:

python
# Stretch to fill parent (common for panels/backgrounds)
{"tool": "manage_components", "params": {
    "action": "set_property", "target": "MyPanel",
    "component_type": "RectTransform",
    "properties": {
        "anchorMin": [0, 0],        # bottom-left corner
        "anchorMax": [1, 1],        # top-right corner
        "sizeDelta": [0, 0],        # no extra size beyond anchors
        "anchoredPosition": [0, 0]  # centered on anchors
    }
}}

# Fixed-size centered element (e.g. 300x50 button)
{"tool": "manage_components", "params": {
    "action": "set_property", "target": "MyButton",
    "component_type": "RectTransform",
    "properties": {
        "anchorMin": [0.5, 0.5],
        "anchorMax": [0.5, 0.5],
        "sizeDelta": [300, 50],
        "anchoredPosition": [0, 0]
    }
}}

# Top-anchored bar (e.g. health bar at top of screen)
{"tool": "manage_components", "params": {
    "action": "set_property", "target": "TopBar",
    "component_type": "RectTransform",
    "properties": {
        "anchorMin": [0, 1],        # left-top
        "anchorMax": [1, 1],        # right-top (stretch horizontally)
        "sizeDelta": [0, 60],       # 60px tall, full width
        "anchoredPosition": [0, -30] # offset down by half height
    }
}}

Note: Vector2 properties accept both [x, y] array format and {"x": ..., "y": ...} object format.

Create Canvas (Foundation for All UI)

Every UI element must be under a Canvas. A Canvas requires three components: Canvas, CanvasScaler, and GraphicRaycaster.

python
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "MainCanvas"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "MainCanvas", "component_type": "Canvas"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "MainCanvas", "component_type": "CanvasScaler"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "MainCanvas", "component_type": "GraphicRaycaster"
    }},
    # renderMode: 0=ScreenSpaceOverlay, 1=ScreenSpaceCamera, 2=WorldSpace
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "MainCanvas",
        "component_type": "Canvas", "property": "renderMode", "value": 0
    }},
    # CanvasScaler: uiScaleMode 1=ScaleWithScreenSize
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "MainCanvas",
        "component_type": "CanvasScaler", "property": "uiScaleMode", "value": 1
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "MainCanvas",
        "component_type": "CanvasScaler", "property": "referenceResolution",
        "value": [1920, 1080]
    }}
])

Create EventSystem (Required Once Per Scene for UI Interaction)

If no EventSystem exists in the scene, buttons and other interactive UI elements won't respond to input. Create one alongside your first Canvas. Check project_info.activeInputHandler to pick the correct input module.

python
# For activeInputHandler == "New" or "Both" (project has Input System package):
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "EventSystem"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "EventSystem",
        "component_type": "UnityEngine.EventSystems.EventSystem"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "EventSystem",
        "component_type": "UnityEngine.InputSystem.UI.InputSystemUIInputModule"
    }}
])

# For activeInputHandler == "Old" (legacy Input Manager only):
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "EventSystem"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "EventSystem",
        "component_type": "UnityEngine.EventSystems.EventSystem"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "EventSystem",
        "component_type": "UnityEngine.EventSystems.StandaloneInputModule"
    }}
])

Create Panel (Background Container)

A Panel is an Image component used as a background/container for other UI elements.

python
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "MenuPanel", "parent": "MainCanvas"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "MenuPanel", "component_type": "Image"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "MenuPanel",
        "component_type": "Image", "property": "color",
        "value": [0.1, 0.1, 0.1, 0.8]
    }},
    # Size the panel (stretch to 60% of canvas, centered)
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "MenuPanel",
        "component_type": "RectTransform",
        "properties": {
            "anchorMin": [0.2, 0.1], "anchorMax": [0.8, 0.9],
            "sizeDelta": [0, 0], "anchoredPosition": [0, 0]
        }
    }}
])

Create Text (TextMeshPro)

TextMeshProUGUI automatically adds a RectTransform when added to a child of a Canvas. If packages.textmeshpro is false, use UnityEngine.UI.Text instead.

python
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "TitleText", "parent": "MenuPanel"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "TitleText",
        "component_type": "TextMeshProUGUI"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "TitleText",
        "component_type": "TextMeshProUGUI",
        "properties": {
            "text": "My Game Title",
            "fontSize": 48,
            "alignment": 514,
            "color": [1, 1, 1, 1]
        }
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "TitleText",
        "component_type": "RectTransform",
        "properties": {
            "anchorMin": [0, 0.8], "anchorMax": [1, 1],
            "sizeDelta": [0, 0], "anchoredPosition": [0, 0]
        }
    }}
])

TextMeshPro alignment values: 257=TopLeft, 258=TopCenter, 260=TopRight, 513=MiddleLeft, 514=MiddleCenter, 516=MiddleRight, 1025=BottomLeft, 1026=BottomCenter, 1028=BottomRight.

Create Button (With Label)

A Button needs an Image (visual) + Button (interaction) on the parent, and a child with TextMeshProUGUI for the label.

python
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "StartButton", "parent": "MenuPanel"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "StartButton", "component_type": "Image"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "StartButton", "component_type": "Button"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "StartButton",
        "component_type": "Image", "property": "color",
        "value": [0.2, 0.6, 1.0, 1.0]
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "StartButton",
        "component_type": "RectTransform",
        "properties": {
            "anchorMin": [0.5, 0.5], "anchorMax": [0.5, 0.5],
            "sizeDelta": [300, 60], "anchoredPosition": [0, 0]
        }
    }},
    # Child text label (stretches to fill button)
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "StartButton_Label", "parent": "StartButton"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "StartButton_Label",
        "component_type": "TextMeshProUGUI"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "StartButton_Label",
        "component_type": "TextMeshProUGUI",
        "properties": {"text": "Start Game", "fontSize": 24, "alignment": 514}
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "StartButton_Label",
        "component_type": "RectTransform",
        "properties": {
            "anchorMin": [0, 0], "anchorMax": [1, 1],
            "sizeDelta": [0, 0], "anchoredPosition": [0, 0]
        }
    }}
])

Create Slider (With Reference Wiring)

A Slider requires a specific hierarchy and must have its fillRect and handleRect references wired to function.

python
# Step 1: Create hierarchy
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "HealthSlider", "parent": "MainCanvas"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "HealthSlider", "component_type": "Slider"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "HealthSlider", "component_type": "Image"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "HealthSlider",
        "component_type": "RectTransform",
        "properties": {
            "anchorMin": [0.5, 0.5], "anchorMax": [0.5, 0.5],
            "sizeDelta": [400, 30], "anchoredPosition": [0, 0]
        }
    }},
    # Background
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "SliderBG", "parent": "HealthSlider"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "SliderBG", "component_type": "Image"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "SliderBG",
        "component_type": "Image", "property": "color", "value": [0.3, 0.3, 0.3, 1.0]
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "SliderBG",
        "component_type": "RectTransform",
        "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}
    }},
    # Fill Area + Fill
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "FillArea", "parent": "HealthSlider"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "FillArea",
        "component_type": "RectTransform",
        "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}
    }},
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "SliderFill", "parent": "FillArea"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "SliderFill", "component_type": "Image"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "SliderFill",
        "component_type": "Image", "property": "color", "value": [0.2, 0.8, 0.2, 1.0]
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "SliderFill",
        "component_type": "RectTransform",
        "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}
    }},
    # Handle Area + Handle
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "HandleArea", "parent": "HealthSlider"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "HandleArea",
        "component_type": "RectTransform",
        "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}
    }},
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "SliderHandle", "parent": "HandleArea"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "SliderHandle", "component_type": "Image"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "SliderHandle",
        "component_type": "RectTransform",
        "properties": {"anchorMin": [0.5, 0], "anchorMax": [0.5, 1], "sizeDelta": [20, 0]}
    }}
])

# Step 2: Wire Slider references (CRITICAL — slider won't work without this)
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "HealthSlider",
        "component_type": "Slider", "property": "fillRect",
        "value": {"name": "SliderFill"}
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "HealthSlider",
        "component_type": "Slider", "property": "handleRect",
        "value": {"name": "SliderHandle"}
    }}
])

Create Input Field (With Reference Wiring)

python
# Step 1: Create hierarchy
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "NameInput", "parent": "MenuPanel"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "NameInput", "component_type": "Image"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "NameInput",
        "component_type": "TMP_InputField"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "NameInput",
        "component_type": "RectTransform",
        "properties": {
            "anchorMin": [0.5, 0.5], "anchorMax": [0.5, 0.5],
            "sizeDelta": [400, 50], "anchoredPosition": [0, 0]
        }
    }},
    # Text Area child (clips text to input bounds)
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "InputTextArea", "parent": "NameInput"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "InputTextArea", "component_type": "RectMask2D"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "InputTextArea",
        "component_type": "RectTransform",
        "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [-16, -8]}
    }},
    # Placeholder
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "InputPlaceholder", "parent": "InputTextArea"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "InputPlaceholder", "component_type": "TextMeshProUGUI"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "InputPlaceholder",
        "component_type": "TextMeshProUGUI",
        "properties": {"text": "Enter name...", "fontStyle": 2, "color": [0.5, 0.5, 0.5, 0.5]}
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "InputPlaceholder",
        "component_type": "RectTransform",
        "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}
    }},
    # Actual text display
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "InputText", "parent": "InputTextArea"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "InputText", "component_type": "TextMeshProUGUI"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "InputText",
        "component_type": "RectTransform",
        "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}
    }}
])

# Step 2: Wire TMP_InputField references (CRITICAL)
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "NameInput",
        "component_type": "TMP_InputField", "property": "textViewport",
        "value": {"name": "InputTextArea"}
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "NameInput",
        "component_type": "TMP_InputField", "property": "textComponent",
        "value": {"name": "InputText"}
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "NameInput",
        "component_type": "TMP_InputField", "property": "placeholder",
        "value": {"name": "InputPlaceholder"}
    }}
])

Create Toggle (With Reference Wiring)

python
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "SoundToggle", "parent": "MenuPanel"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "SoundToggle", "component_type": "Toggle"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "SoundToggle",
        "component_type": "RectTransform",
        "properties": {
            "anchorMin": [0.5, 0.5], "anchorMax": [0.5, 0.5],
            "sizeDelta": [200, 30], "anchoredPosition": [0, 0]
        }
    }},
    # Background box
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "ToggleBG", "parent": "SoundToggle"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "ToggleBG", "component_type": "Image"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "ToggleBG",
        "component_type": "RectTransform",
        "properties": {"anchorMin": [0, 0.5], "anchorMax": [0, 0.5], "sizeDelta": [26, 26], "anchoredPosition": [13, 0]}
    }},
    # Checkmark
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "ToggleCheckmark", "parent": "ToggleBG"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "ToggleCheckmark", "component_type": "Image"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "ToggleCheckmark",
        "component_type": "RectTransform",
        "properties": {"anchorMin": [0.1, 0.1], "anchorMax": [0.9, 0.9], "sizeDelta": [0, 0]}
    }},
    # Label
    {"tool": "manage_gameobject", "params": {
        "action": "create", "name": "ToggleLabel", "parent": "SoundToggle"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "ToggleLabel", "component_type": "TextMeshProUGUI"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "ToggleLabel",
        "component_type": "TextMeshProUGUI",
        "properties": {"text": "Sound Effects", "fontSize": 18, "alignment": 513}
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "ToggleLabel",
        "component_type": "RectTransform",
        "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [-35, 0], "anchoredPosition": [17.5, 0]}
    }}
])

# Wire Toggle references (CRITICAL)
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "SoundToggle",
        "component_type": "Toggle", "property": "graphic",
        "value": {"name": "ToggleCheckmark"}
    }}
])

Add Layout Group (Vertical/Horizontal/Grid)

Layout groups auto-arrange child elements, so you can skip manual RectTransform positioning for children.

python
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_components", "params": {
        "action": "add", "target": "MenuPanel",
        "component_type": "VerticalLayoutGroup"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "MenuPanel",
        "component_type": "VerticalLayoutGroup",
        "properties": {
            "spacing": 10,
            "childAlignment": 4,
            "childForceExpandWidth": True,
            "childForceExpandHeight": False,
            "padding": {"left": 20, "right": 20, "top": 20, "bottom": 20}
        }
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "MenuPanel",
        "component_type": "ContentSizeFitter"
    }},
    {"tool": "manage_components", "params": {
        "action": "set_property", "target": "MenuPanel",
        "component_type": "ContentSizeFitter",
        "properties": { "verticalFit": 2 }
    }}
])

childAlignment values: 0=UpperLeft, 1=UpperCenter, 2=UpperRight, 3=MiddleLeft, 4=MiddleCenter, 5=MiddleRight, 6=LowerLeft, 7=LowerCenter, 8=LowerRight. ContentSizeFitter fit modes: 0=Unconstrained, 1=MinSize, 2=PreferredSize.

Complete Example: Main Menu Screen

Combines multiple templates into a full menu screen in two batch calls (default 25 command limit per batch, configurable in Unity MCP Tools window up to 100). Assumes project_info has been read and activeInputHandler is known.

python
# Batch 1: Canvas + EventSystem + Panel + Title
batch_execute(fail_fast=True, commands=[
    # Canvas
    {"tool": "manage_gameobject", "params": {"action": "create", "name": "MenuCanvas"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "MenuCanvas", "component_type": "Canvas"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "MenuCanvas", "component_type": "CanvasScaler"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "MenuCanvas", "component_type": "GraphicRaycaster"}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuCanvas", "component_type": "Canvas", "property": "renderMode", "value": 0}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuCanvas", "component_type": "CanvasScaler", "properties": {"uiScaleMode": 1, "referenceResolution": [1920, 1080]}}},
    # EventSystem — use StandaloneInputModule OR InputSystemUIInputModule based on project_info
    {"tool": "manage_gameobject", "params": {"action": "create", "name": "EventSystem"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "EventSystem", "component_type": "UnityEngine.EventSystems.EventSystem"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "EventSystem", "component_type": "UnityEngine.EventSystems.StandaloneInputModule"}},
    # Panel (centered, 60% width)
    {"tool": "manage_gameobject", "params": {"action": "create", "name": "MenuPanel", "parent": "MenuCanvas"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "MenuPanel", "component_type": "Image"}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuPanel", "component_type": "Image", "property": "color", "value": [0.1, 0.1, 0.15, 0.9]}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuPanel", "component_type": "RectTransform", "properties": {"anchorMin": [0.2, 0.15], "anchorMax": [0.8, 0.85], "sizeDelta": [0, 0]}}},
    {"tool": "manage_components", "params": {"action": "add", "target": "MenuPanel", "component_type": "VerticalLayoutGroup"}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuPanel", "component_type": "VerticalLayoutGroup", "properties": {"spacing": 20, "childAlignment": 4, "childForceExpandWidth": True, "childForceExpandHeight": False}}},
    # Title
    {"tool": "manage_gameobject", "params": {"action": "create", "name": "Title", "parent": "MenuPanel"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "Title", "component_type": "TextMeshProUGUI"}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "Title", "component_type": "TextMeshProUGUI", "properties": {"text": "My Game", "fontSize": 64, "alignment": 514, "color": [1, 1, 1, 1]}}}
])

# Batch 2: Buttons
batch_execute(fail_fast=True, commands=[
    # Play Button
    {"tool": "manage_gameobject", "params": {"action": "create", "name": "PlayButton", "parent": "MenuPanel"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "PlayButton", "component_type": "Image"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "PlayButton", "component_type": "Button"}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "PlayButton", "component_type": "Image", "property": "color", "value": [0.2, 0.6, 1.0, 1.0]}},
    {"tool": "manage_gameobject", "params": {"action": "create", "name": "PlayLabel", "parent": "PlayButton"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "PlayLabel", "component_type": "TextMeshProUGUI"}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "PlayLabel", "component_type": "TextMeshProUGUI", "properties": {"text": "Play", "fontSize": 32, "alignment": 514}}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "PlayLabel", "component_type": "RectTransform", "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}}},
    # Settings Button
    {"tool": "manage_gameobject", "params": {"action": "create", "name": "SettingsButton", "parent": "MenuPanel"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "SettingsButton", "component_type": "Image"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "SettingsButton", "component_type": "Button"}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "SettingsButton", "component_type": "Image", "property": "color", "value": [0.3, 0.3, 0.35, 1.0]}},
    {"tool": "manage_gameobject", "params": {"action": "create", "name": "SettingsLabel", "parent": "SettingsButton"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "SettingsLabel", "component_type": "TextMeshProUGUI"}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "SettingsLabel", "component_type": "TextMeshProUGUI", "properties": {"text": "Settings", "fontSize": 32, "alignment": 514}}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "SettingsLabel", "component_type": "RectTransform", "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}}},
    # Quit Button
    {"tool": "manage_gameobject", "params": {"action": "create", "name": "QuitButton", "parent": "MenuPanel"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "QuitButton", "component_type": "Image"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "QuitButton", "component_type": "Button"}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "QuitButton", "component_type": "Image", "property": "color", "value": [0.8, 0.2, 0.2, 1.0]}},
    {"tool": "manage_gameobject", "params": {"action": "create", "name": "QuitLabel", "parent": "QuitButton"}},
    {"tool": "manage_components", "params": {"action": "add", "target": "QuitLabel", "component_type": "TextMeshProUGUI"}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "QuitLabel", "component_type": "TextMeshProUGUI", "properties": {"text": "Quit", "fontSize": 32, "alignment": 514}}},
    {"tool": "manage_components", "params": {"action": "set_property", "target": "QuitLabel", "component_type": "RectTransform", "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}}}
])

UI Component Quick Reference

UI ElementRequired ComponentsNotes
CanvasCanvas + CanvasScaler + GraphicRaycasterRoot for all UI. One per screen.
EventSystemEventSystem + input module (see below)One per scene. Required for interaction.
PanelImage + RectTransform sizingContainer. Set color for background.
TextTextMeshProUGUI (or Text if no TMP) + RectTransformCheck packages.textmeshpro.
ButtonImage + Button + child(TextMeshProUGUI) + RectTransformImage = visual, Button = click handler.
SliderSlider + Image + children + wire fillRect/handleRectWon't function without wiring.
ToggleToggle + children + wire graphicWire checkmark Image to graphic.
Input FieldImage + TMP_InputField + children + wire textViewport/textComponent/placeholderWon't function without wiring.
Layout GroupVerticalLayoutGroup / HorizontalLayoutGroup / GridLayoutGroupAuto-arranges children; skip manual RectTransform on children.

Input System: Old vs New

Unity has two input systems that affect UI interaction, script input handling, and EventSystem configuration. Always check project_info.activeInputHandler before creating EventSystems or writing input code.

Detection

python
# Read mcpforunity://project/info
# activeInputHandler: "Old" | "New" | "Both"
# packages.inputsystem: true/false (whether com.unity.inputsystem is installed)

EventSystem — Old Input Manager

Used when activeInputHandler is "Old". Uses StandaloneInputModule which reads from Input.GetAxis() / Input.GetButton().

python
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_gameobject", "params": {"action": "create", "name": "EventSystem"}},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "EventSystem",
        "component_type": "UnityEngine.EventSystems.EventSystem"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "EventSystem",
        "component_type": "UnityEngine.EventSystems.StandaloneInputModule"
    }}
])

Script pattern (old Input Manager):

csharp
// Input.GetAxis / Input.GetKey — works with old Input Manager
void Update()
{
    float h = Input.GetAxis("Horizontal");
    float v = Input.GetAxis("Vertical");
    transform.Translate(new Vector3(h, 0, v) * speed * Time.deltaTime);

    if (Input.GetKeyDown(KeyCode.Space))
        Jump();

    if (Input.GetMouseButtonDown(0))
        Fire();
}

EventSystem — New Input System

Used when activeInputHandler is "New" or "Both". Uses InputSystemUIInputModule from the com.unity.inputsystem package.

python
batch_execute(fail_fast=True, commands=[
    {"tool": "manage_gameobject", "params": {"action": "create", "name": "EventSystem"}},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "EventSystem",
        "component_type": "UnityEngine.EventSystems.EventSystem"
    }},
    {"tool": "manage_components", "params": {
        "action": "add", "target": "EventSystem",
        "component_type": "UnityEngine.InputSystem.UI.InputSystemUIInputModule"
    }}
])

Script pattern (new Input System with PlayerInput component):

csharp
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
    public float speed = 5f;
    private Vector2 moveInput;

    // Called by PlayerInput component via SendMessages or UnityEvents
    public void OnMove(InputValue value)
    {
        moveInput = value.Get<Vector2>();
    }

    public void OnJump(InputValue value)
    {
        if (value.isPressed)
            Jump();
    }

    void Update()
    {
        Vector3 move = new Vector3(moveInput.x, 0, moveInput.y);
        transform.Translate(move * speed * Time.deltaTime);
    }
}

When activeInputHandler is "Both"

Both systems are active simultaneously. For UI, prefer InputSystemUIInputModule. For gameplay scripts, either approach works — Input.GetAxis() still functions alongside the new Input System.

python
# UI: use new Input System module
{"tool": "manage_components", "params": {
    "action": "add", "target": "EventSystem",
    "component_type": "UnityEngine.InputSystem.UI.InputSystemUIInputModule"
}}

# Gameplay scripts: Input.GetAxis() still works in "Both" mode
# But prefer the new Input System for consistency

Gotcha: Adding StandaloneInputModule when activeInputHandler is "New" will cause a runtime error. Always check first.


Camera & Cinemachine Workflows

Setting Up a Third-Person Camera

python
# 1. Check Cinemachine availability
manage_camera(action="ping")

# 2. Ensure Brain on main camera
manage_camera(action="ensure_brain")

# 3. Create third-person camera with preset
manage_camera(action="create_camera", properties={
    "name": "FollowCam", "preset": "third_person",
    "follow": "Player", "lookAt": "Player", "priority": 20
})

# 4. Fine-tune body
manage_camera(action="set_body", target="FollowCam", properties={
    "cameraDistance": 5.0, "shoulderOffset": [0.5, 0.5, 0]
})

# 5. Add camera shake
manage_camera(action="set_noise", target="FollowCam", properties={
    "amplitudeGain": 0.3, "frequencyGain": 0.8
})

# 6. Verify with screenshot
manage_camera(action="screenshot", camera="FollowCam", include_image=True, max_resolution=512)

Multi-Camera Setup with Blending

python
# 1. Read current cameras
# Read mcpforunity://scene/cameras

# 2. Create gameplay camera (highest priority = active by default)
manage_camera(action="create_camera", properties={
    "name": "GameplayCam", "preset": "follow",
    "follow": "Player", "lookAt": "Player", "priority": 10
})

# 3. Create cinematic camera (lower priority, activated on demand)
manage_camera(action="create_camera", properties={
    "name": "CinematicCam", "preset": "dolly",
    "lookAt": "CutsceneTarget", "priority": 5
})

# 4. Set blend transition
manage_camera(action="set_blend", properties={"style": "EaseInOut", "duration": 2.0})

# 5. Force cinematic camera for a cutscene
manage_camera(action="force_camera", target="CinematicCam")

# 6. Release override to return to priority-based selection
manage_camera(action="release_override")

Camera Without Cinemachine

python
# Tier 1 actions work with plain Unity Camera
manage_camera(action="create_camera", properties={
    "name": "MainCam", "fieldOfView": 50
})

# Set lens
manage_camera(action="set_lens", target="MainCam", properties={
    "fieldOfView": 60, "nearClipPlane": 0.1, "farClipPlane": 1000
})

# Point camera at target (uses manage_gameobject look_at under the hood)
manage_camera(action="set_target", target="MainCam", properties={
    "lookAt": "Player"
})

# Screenshot from this camera
manage_camera(action="screenshot", camera="MainCam", include_image=True, max_resolution=512)

Camera Inspection Workflow

python
# 1. Read all cameras via resource
# Read mcpforunity://scene/cameras
# → Shows brain status, all Cinemachine cameras (priority, pipeline, targets),
#   all Unity cameras (FOV, depth, brain)

# 2. Get brain status for blending info
manage_camera(action="get_brain_status")

# 3. List cameras via tool (alternative to resource)
manage_camera(action="list_cameras")

# 4. Multi-view screenshot to see from different angles
manage_camera(action="screenshot_multiview", max_resolution=480)

Scene View Screenshot Workflow

Use capture_source="scene_view" to capture the editor's Scene View viewport — useful for seeing gizmos, wireframes, grid, debug overlays, and objects without cameras.

python
# 1. Capture the Scene View as-is
manage_camera(action="screenshot", capture_source="scene_view", include_image=True)

# 2. Frame on a specific object first, then capture
manage_camera(action="screenshot", capture_source="scene_view",
    view_target="Player", include_image=True, max_resolution=512)

# 3. Frame on UI Canvas (RectTransform bounds are supported)
manage_camera(action="screenshot", capture_source="scene_view",
    view_target="Canvas", include_image=True)

# Limitations: scene_view does not support batch, view_position, view_rotation, or camera selection.
# Use capture_source="game_view" (default) for those features.

ProBuilder Workflows

When com.unity.probuilder is installed, prefer ProBuilder shapes over primitive GameObjects for any geometry that needs editing, multi-material faces, or non-trivial shapes. Check availability first with manage_probuilder(action="ping").

See ProBuilder Workflow Guide for full reference with complex object examples.

ProBuilder vs Primitives Decision

NeedUse PrimitivesUse ProBuilder
Simple placeholder cubemanage_gameobject(action="create", primitive_type="Cube")-
Editable geometry-manage_probuilder(action="create_shape", ...)
Per-face materials-set_face_material
Custom shapes (L-rooms, arches)-create_poly_shape or create_shape
Mesh editing (extrude, bevel)-Face/edge/vertex operations
Batch environment buildingEitherProBuilder + batch_execute

Basic ProBuilder Scene Build

python
# 1. Check ProBuilder availability
manage_probuilder(action="ping")

# 2. Create shapes (use batch for multiple)
batch_execute(commands=[
    {"tool": "manage_probuilder", "params": {
        "action": "create_shape",
        "properties": {"shape_type": "Cube", "name": "Floor", "width": 20, "height": 0.2, "depth": 20}
    }},
    {"tool": "manage_probuilder", "params": {
        "action": "create_shape",
        "properties": {"shape_type": "Cube", "name": "Wall1", "width": 20, "height": 3, "depth": 0.3,
                       "position": [0, 1.5, 10]}
    }},
    {"tool": "manage_probuilder", "params": {
        "action": "create_shape",
        "properties": {"shape_type": "Cylinder", "name": "Pillar1", "radius": 0.4, "height": 3,
                       "position": [5, 1.5, 5]}
    }},
])

# 3. Edit geometry (always get_mesh_info first!)
info = manage_probuilder(action="get_mesh_info", target="Wall1",
    properties={"include": "faces"})
# Find direction="front" face, subdivide it, delete center for a window

# 4. Apply materials per face
manage_probuilder(action="set_face_material", target="Floor",
    properties={"faceIndices": [0], "materialPath": "Assets/Materials/Stone.mat"})

# 5. Smooth organic shapes
manage_probuilder(action="auto_smooth", target="Pillar1",
    properties={"angleThreshold": 45})

# 6. Screenshot to verify
manage_camera(action="screenshot", include_image=True, max_resolution=512)

Edit-Verify Loop Pattern

Face indices change after every edit. Always re-query:

python
# WRONG: Assume face indices are stable
manage_probuilder(action="subdivide", target="Obj", properties={"faceIndices": [2]})
manage_probuilder(action="delete_faces", target="Obj", properties={"faceIndices": [5]})  # Index may be wrong!

# RIGHT: Re-query after each edit
manage_probuilder(action="subdivide", target="Obj", properties={"faceIndices": [2]})
info = manage_probuilder(action="get_mesh_info", target="Obj", properties={"include": "faces"})
# Find the correct face by direction/center, then delete
manage_probuilder(action="delete_faces", target="Obj", properties={"faceIndices": [correct_index]})

Known Limitations

  • set_pivot: Broken -- vertex positions don't persist through mesh rebuild. Use center_pivot or Transform positioning.
  • convert_to_probuilder: Broken -- MeshImporter throws. Create shapes natively with create_shape/create_poly_shape.
  • subdivide: Uses ConnectElements.Connect (not traditional quad subdivision). Connects face midpoints.

Graphics & Rendering Workflows

Setting Up Post-Processing

Add post-processing effects to a URP/HDRP scene using Volumes.

python
# 1. Check pipeline status and available effects
manage_graphics(action="ping")

# 2. List available volume effects for the active pipeline
manage_graphics(action="volume_list_effects")

# 3. Create a global post-processing volume with common effects
manage_graphics(action="volume_create", name="GlobalPostProcess", is_global=True,
    effects=[
        {"type": "Bloom", "parameters": {"intensity": 1.0, "threshold": 0.9, "scatter": 0.7}},
        {"type": "Vignette", "parameters": {"intensity": 0.35}},
        {"type": "Tonemapping", "parameters": {"mode": 1}},
        {"type": "ColorAdjustments", "parameters": {"postExposure": 0.2, "contrast": 10}}
    ])

# 4. Verify the volume was created
# Read mcpforunity://scene/volumes

# 5. Fine-tune an effect parameter
manage_graphics(action="volume_set_effect", target="GlobalPostProcess",
    effect="Bloom", parameters={"intensity": 1.5})

# 6. Screenshot to verify visual result
manage_camera(action="screenshot", include_image=True, max_resolution=512)

Tips:

  • Always ping first to confirm URP/HDRP is active. Volumes do nothing on Built-in RP.
  • Use volume_list_effects to discover available effect types for the active pipeline (URP and HDRP have different sets).
  • Use volume_get_info to inspect current effect parameters before modifying.
  • Create a reusable VolumeProfile asset with volume_create_profile and reference it via profile_path on multiple volumes.

Adding a Full-Screen Effect via Renderer Features (URP)

Add a custom full-screen shader pass using URP Renderer Features.

python
# 1. Check pipeline and confirm URP
manage_graphics(action="ping")

# 2. Create a material for the full-screen effect
manage_material(action="create",
    material_path="Assets/Materials/GrayscaleEffect.mat",
    shader="Shader Graphs/GrayscaleFullScreen")

# 3. List current renderer features
manage_graphics(action="feature_list")

# 4. Add a FullScreenPassRendererFeature with the material
manage_graphics(action="feature_add",
    feature_type="FullScreenPassRendererFeature",
    name="GrayscalePass",
    material="Assets/Materials/GrayscaleEffect.mat")

# 5. Verify it was added
manage_graphics(action="feature_list")

# 6. Toggle it on/off to compare
manage_graphics(action="feature_toggle", index=0, active=False)  # disable
manage_camera(action="screenshot", include_image=True, max_resolution=512)

manage_graphics(action="feature_toggle", index=0, active=True)   # re-enable
manage_camera(action="screenshot", include_image=True, max_resolution=512)

# 7. Reorder features if needed (execution order matters)
manage_graphics(action="feature_reorder", order=[1, 0, 2])

Tips:

  • Renderer Features are URP-only. feature_* actions return an error on HDRP or Built-in RP.
  • Read mcpforunity://pipeline/renderer-features to inspect features without modifying.
  • Feature execution order affects the final image. Use feature_reorder to control pass ordering.

Configuring Light Baking

Set up lightmaps, light probes, and reflection probes for baked GI.

python
# 1. Set lights to Baked or Mixed mode
manage_components(action="set_property", target="Directional Light",
    component_type="Light", properties={"lightmapBakeType": 1})  # 1 = Mixed

# 2. Mark static objects for lightmapping
manage_gameobject(action="modify", target="Environment",
    component_properties={"StaticFlags": "ContributeGI"})

# 3. Configure lightmap settings
manage_graphics(action="bake_get_settings")
manage_graphics(action="bake_set_settings", settings={
    "lightmapper": 1,           # 1 = Progressive GPU
    "directSamples": 32,
    "indirectSamples": 128,
    "maxBounces": 4,
    "lightmapResolution": 40
})

# 4. Place light probes for dynamic objects
manage_graphics(action="bake_create_light_probe_group", name="MainProbeGrid",
    position=[0, 1.5, 0], grid_size=[5, 3, 5], spacing=3.0)

# 5. Place a reflection probe for an interior room
manage_graphics(action="bake_create_reflection_probe", name="RoomReflection",
    position=[0, 2, 0], size=[8, 4, 8], resolution=256,
    hdr=True, box_projection=True)

# 6. Start async bake
manage_graphics(action="bake_start", async_bake=True)

# 7. Poll bake status
manage_graphics(action="bake_status")
# Repeat until complete

# 8. Bake the reflection probe separately if needed
manage_graphics(action="bake_reflection_probe", target="RoomReflection")

# 9. Check rendering stats after bake
manage_graphics(action="stats_get")

Tips:

  • Baking only works in Edit mode. If the editor is in Play mode, bake_start will fail.
  • Use bake_cancel to abort a long bake.
  • bake_clear removes all baked data (lightmaps, probes). Use before re-baking from scratch.
  • For large scenes, use async_bake=True (default) and poll bake_status periodically.

Package Management Workflows

Install a Package and Verify

python
# 1. Check what's installed
manage_packages(action="ping")
manage_packages(action="list_packages")
# Poll status until complete
manage_packages(action="status", job_id="<job_id>")

# 2. Install the package
manage_packages(action="add_package", package="com.unity.inputsystem")
# Poll until domain reload completes
manage_packages(action="status", job_id="<job_id>")

# 3. Verify no compilation errors
read_console(types=["error"], count=10)

# 4. Confirm it's installed
manage_packages(action="get_package_info", package="com.unity.inputsystem")

Add OpenUPM Registry and Install Package

python
# 1. Add the OpenUPM scoped registry
manage_packages(
    action="add_registry",
    name="OpenUPM",
    url="https://package.openupm.com",
    scopes=["com.cysharp"]
)

# 2. Force resolution to pick up the new registry
manage_packages(action="resolve_packages")

# 3. Install a package from OpenUPM
manage_packages(action="add_package", package="com.cysharp.unitask")
manage_packages(action="status", job_id="<job_id>")

Safe Package Removal

python
# 1. Check dependencies before removing
manage_packages(action="remove_package", package="com.unity.modules.ui")
# If blocked: "Cannot remove: 3 package(s) depend on it"

# 2. Force removal if you're sure
manage_packages(action="remove_package", package="com.unity.modules.ui", force=True)
manage_packages(action="status", job_id="<job_id>")

Install from Git URL (e.g., NuGetForUnity)

python
# Git URLs trigger a security warning — ensure the source is trusted
manage_packages(
    action="add_package",
    package="https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity"
)
manage_packages(action="status", job_id="<job_id>")

Package Deployment Workflows

Iterative Development Loop (Edit → Deploy → Test)

Use deploy_package to copy your local MCPForUnity source into the project's installed package location. This bypasses the UI dialog and triggers recompilation automatically.

python
# Prerequisites: Set the MCPForUnity source path in Advanced Settings first.

# 1. Make code changes (e.g., edit C# tools)
# script_apply_edits or create_script as needed

# 2. Deploy the updated package (copies source → installed package, creates backup)
manage_editor(action="deploy_package")

# 3. Wait for recompilation to finish
refresh_unity(mode="force", compile="request", wait_for_ready=True)

# 4. Check for compilation errors
read_console(types=["error"], count=10, include_stacktrace=True)

# 5. Test the changes
run_tests(mode="EditMode")

Rollback After Failed Deploy

python
# Restore from the automatic pre-deployment backup
manage_editor(action="restore_package")

# Wait for recompilation
refresh_unity(mode="force", compile="request", wait_for_ready=True)

API Verification Workflows

These tools live in the opt-in docs group. Activate it first: manage_tools(action="activate", group="docs")

Full API Verification Before Writing Code

Use unity_reflect and unity_docs to verify Unity APIs before writing C# code. This prevents hallucinated or outdated API references.

Trust hierarchy: reflection (live runtime) > project assets > official docs.

python
# Step 1: Search for the type you need
unity_reflect(action="search", query="NavMesh")
# → Returns matching types: NavMeshAgent, NavMeshPath, NavMeshHit, etc.

# Step 2: Get member summary for the type
unity_reflect(action="get_type", class_name="UnityEngine.AI.NavMeshAgent")
# → Returns all methods, properties, fields (names only)

# Step 3: Get full signature for specific members you plan to use
unity_reflect(action="get_member", class_name="NavMeshAgent", member_name="SetDestination")
# → Returns parameter types, return type, all overloads

# Step 4: Get official docs for usage patterns and examples
unity_docs(action="get_doc", class_name="NavMeshAgent", member_name="SetDestination")
# → Returns description, signatures, parameters, code examples

Batch API Lookup

Use unity_docs lookup action to search multiple APIs in a single call:

python
# Search ScriptReference + Manual in parallel (+ package docs if package/pkg_version provided)
unity_docs(action="lookup", queries="Physics.Raycast,NavMeshAgent,Light2D")

# Include package docs in the search
unity_docs(action="lookup", query="VolumeProfile",
           package="com.unity.render-pipelines.universal", pkg_version="17.0")

Finding Shaders and Materials in Project

The lookup action automatically searches project assets for asset-related queries:

python
# This searches both docs AND project assets for shader-related content
unity_docs(action="lookup", query="Lit shader")
# → Returns doc hits + matching project assets (shaders, materials, etc.)

Manual and Package Documentation

python
# Fetch Unity Manual pages (execution order, scripting concepts, etc.)
unity_docs(action="get_manual", slug="execution-order")

# Fetch package-specific documentation
unity_docs(action="get_package_doc",
           package="com.unity.render-pipelines.universal",
           page="2d-index", pkg_version="17.0")

Verifying APIs Across Unity Versions

python
# Specify Unity version for version-specific docs
unity_docs(action="get_doc", class_name="Camera", member_name="main", version="6000.0.38f1")

# Use reflection to check what's actually available in the running editor
unity_reflect(action="search", query="InputAction", scope="packages")

Batch Operations

Batch Discovery (Multi-Search)

Use batch_execute to search for multiple things in a single call instead of calling find_gameobjects repeatedly:

python
# Instead of 4 separate find_gameobjects calls, batch them:
batch_execute(commands=[
    {"tool": "find_gameobjects", "params": {"search_term": "Camera", "search_method": "by_component"}},
    {"tool": "find_gameobjects", "params": {"search_term": "Rigidbody", "search_method": "by_component"}},
    {"tool": "find_gameobjects", "params": {"search_term": "Player", "search_method": "by_tag"}},
    {"tool": "find_gameobjects", "params": {"search_term": "GameManager", "search_method": "by_name"}}
])
# Returns array of results, one per command

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