73 KiB
Unity-MCP Workflow Patterns
Common workflows and patterns for effective Unity-MCP usage.
Table of Contents
- Setup & Verification
- Scene Creation Workflows
- Script Development Workflows
- Asset Management Workflows
- Testing Workflows
- Debugging Workflows
- UI Creation Workflows
- Camera & Cinemachine Workflows
- ProBuilder Workflows
- Graphics & Rendering Workflows
- Package Management Workflows
- Package Deployment Workflows
- API Verification Workflows
- Batch Operations
Setup & Verification
Initial Connection Verification
# 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
# 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.
# 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:
# 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:
# 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:
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
# 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
# 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
# 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
# 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
# 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
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
# 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
# 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
# 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
# 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.
# 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_prefabsis for headless prefab editing (inspect, modify contents, create from GameObject). To instantiate a prefab into the scene, always usemanage_gameobject(action="create", prefab_path="...").
Testing Workflows
Run Specific Tests
# 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
# 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
# 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
# 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
# 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
# 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:
- Always read
mcpforunity://project/infofirst to detect installed packages and input system.- Validate component/property names against the current project.
- Prefer targeting by instance ID or full path over generic names.
- 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.
# 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 field | Value | What to use |
|---|---|---|
packages.uiToolkit |
true |
Preferred: Use manage_ui for UI Toolkit (UXML/USS) |
packages.ugui |
true |
Canvas-based UI (Image, Button, etc.) via batch_execute |
packages.textmeshpro |
true |
TextMeshProUGUI for text (uGUI) |
packages.textmeshpro |
false |
UnityEngine.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 theui: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
# 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
# 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
# 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:
# 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.
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.
# 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.
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.
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.
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.
# 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)
# 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)
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.
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.
# 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 Element | Required Components | Notes |
|---|---|---|
| Canvas | Canvas + CanvasScaler + GraphicRaycaster | Root for all UI. One per screen. |
| EventSystem | EventSystem + input module (see below) | One per scene. Required for interaction. |
| Panel | Image + RectTransform sizing | Container. Set color for background. |
| Text | TextMeshProUGUI (or Text if no TMP) + RectTransform | Check packages.textmeshpro. |
| Button | Image + Button + child(TextMeshProUGUI) + RectTransform | Image = visual, Button = click handler. |
| Slider | Slider + Image + children + wire fillRect/handleRect | Won't function without wiring. |
| Toggle | Toggle + children + wire graphic | Wire checkmark Image to graphic. |
| Input Field | Image + TMP_InputField + children + wire textViewport/textComponent/placeholder | Won't function without wiring. |
| Layout Group | VerticalLayoutGroup / HorizontalLayoutGroup / GridLayoutGroup | Auto-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
# 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().
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):
// 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.
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):
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.
# 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
StandaloneInputModulewhenactiveInputHandleris"New"will cause a runtime error. Always check first.
Camera & Cinemachine Workflows
Setting Up a Third-Person Camera
# 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
# 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
# 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
# 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.
# 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
| Need | Use Primitives | Use ProBuilder |
|---|---|---|
| Simple placeholder cube | manage_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 building | Either | ProBuilder + batch_execute |
Basic ProBuilder Scene Build
# 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:
# 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. Usecenter_pivotor Transform positioning.convert_to_probuilder: Broken -- MeshImporter throws. Create shapes natively withcreate_shape/create_poly_shape.subdivide: UsesConnectElements.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.
# 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
pingfirst to confirm URP/HDRP is active. Volumes do nothing on Built-in RP. - Use
volume_list_effectsto discover available effect types for the active pipeline (URP and HDRP have different sets). - Use
volume_get_infoto inspect current effect parameters before modifying. - Create a reusable VolumeProfile asset with
volume_create_profileand reference it viaprofile_pathon multiple volumes.
Adding a Full-Screen Effect via Renderer Features (URP)
Add a custom full-screen shader pass using URP Renderer Features.
# 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-featuresto inspect features without modifying. - Feature execution order affects the final image. Use
feature_reorderto control pass ordering.
Configuring Light Baking
Set up lightmaps, light probes, and reflection probes for baked GI.
# 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_startwill fail. - Use
bake_cancelto abort a long bake. bake_clearremoves all baked data (lightmaps, probes). Use before re-baking from scratch.- For large scenes, use
async_bake=True(default) and pollbake_statusperiodically.
Package Management Workflows
Install a Package and Verify
# 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
# 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
# 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)
# 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.
# 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
# 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
docsgroup. 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.
# 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:
# 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:
# 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
# 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
# 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:
# 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
# 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
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
# 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
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
# 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
# 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