From 57fb86fbca64e0478bff7c5c95466a126e7a6911 Mon Sep 17 00:00:00 2001
From: Shutong Wu <51266340+Scriptwonder@users.noreply.github.com>
Date: Wed, 11 Feb 2026 17:02:45 -0500
Subject: [PATCH 1/2] Update for UI-based batch execution
---
.claude/skills/unity-mcp-skill/SKILL.md | 1 +
.../unity-mcp-skill/references/workflows.md | 407 ++++++++++++++++++
unity-mcp-skill/SKILL.md | 1 +
unity-mcp-skill/references/workflows.md | 407 ++++++++++++++++++
4 files changed, 816 insertions(+)
diff --git a/.claude/skills/unity-mcp-skill/SKILL.md b/.claude/skills/unity-mcp-skill/SKILL.md
index 40a8541d8..b9a76357e 100644
--- a/.claude/skills/unity-mcp-skill/SKILL.md
+++ b/.claude/skills/unity-mcp-skill/SKILL.md
@@ -124,6 +124,7 @@ uri="file:///full/path/to/file.cs"
| **Editor** | `manage_editor`, `execute_menu_item`, `read_console` | Editor control |
| **Testing** | `run_tests`, `get_test_job` | Unity Test Framework |
| **Batch** | `batch_execute` | Parallel/bulk operations |
+| **UI** | `batch_execute` with `manage_gameobject` + `manage_components` | Canvas, Panel, Button, Text, Slider, Toggle, Input Field (see [UI workflows](references/workflows.md#ui-creation-workflows)) |
## Common Workflows
diff --git a/.claude/skills/unity-mcp-skill/references/workflows.md b/.claude/skills/unity-mcp-skill/references/workflows.md
index f04245cf4..629becfa8 100644
--- a/.claude/skills/unity-mcp-skill/references/workflows.md
+++ b/.claude/skills/unity-mcp-skill/references/workflows.md
@@ -10,6 +10,7 @@ Common workflows and patterns for effective Unity-MCP usage.
- [Asset Management Workflows](#asset-management-workflows)
- [Testing Workflows](#testing-workflows)
- [Debugging Workflows](#debugging-workflows)
+- [UI Creation Workflows](#ui-creation-workflows)
- [Batch Operations](#batch-operations)
---
@@ -491,6 +492,412 @@ manage_scene(action="screenshot")
---
+## UI Creation Workflows
+
+Unity UI (Canvas-based UGUI) requires specific component hierarchies. Use `batch_execute` with `fail_fast=True` to create complete UI elements in a single call. These templates handle the boilerplate so you don't need to remember which components each UI element needs.
+
+### 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.
+
+```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"
+ }}
+])
+```
+
+> **Note:** For projects using legacy Input Manager instead of Input System, use `"component_type": "UnityEngine.EventSystems.StandaloneInputModule"` instead.
+
+### 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"
+ }},
+ # Set semi-transparent dark background
+ {"tool": "manage_components", "params": {
+ "action": "set_property", "target": "MenuPanel",
+ "component_type": "Image", "property": "color",
+ "value": [0.1, 0.1, 0.1, 0.8]
+ }}
+])
+```
+
+### Create Text (TextMeshPro)
+
+TextMeshProUGUI automatically adds a RectTransform when added to a child of a Canvas.
+
+```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]
+ }
+ }}
+])
+```
+
+> **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=[
+ # Button container with Image + Button components
+ {"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]
+ }},
+ # Child text label
+ {"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}
+ }}
+])
+```
+
+### Create Slider
+
+A Slider requires a specific hierarchy: the slider root, a background, a fill area with fill, and a handle area with handle.
+
+```python
+batch_execute(fail_fast=True, commands=[
+ # Slider root
+ {"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"
+ }},
+ # Background
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Background", "parent": "HealthSlider"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Background", "component_type": "Image"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "set_property", "target": "Background",
+ "component_type": "Image", "property": "color",
+ "value": [0.3, 0.3, 0.3, 1.0]
+ }},
+ # Fill Area + Fill
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Fill Area", "parent": "HealthSlider"
+ }},
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Fill", "parent": "Fill Area"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Fill", "component_type": "Image"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "set_property", "target": "Fill",
+ "component_type": "Image", "property": "color",
+ "value": [0.2, 0.8, 0.2, 1.0]
+ }},
+ # Handle Area + Handle
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Handle Slide Area", "parent": "HealthSlider"
+ }},
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Handle", "parent": "Handle Slide Area"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Handle", "component_type": "Image"
+ }}
+])
+```
+
+### Create Input Field (TextMeshPro)
+
+```python
+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"
+ }},
+ # Text area child
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Text Area", "parent": "NameInput"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Text Area",
+ "component_type": "RectMask2D"
+ }},
+ # Placeholder
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Placeholder", "parent": "Text Area"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Placeholder",
+ "component_type": "TextMeshProUGUI"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "set_property", "target": "Placeholder",
+ "component_type": "TextMeshProUGUI",
+ "properties": {"text": "Enter name...", "fontStyle": 2, "color": [0.5, 0.5, 0.5, 0.5]}
+ }},
+ # Actual text
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Text", "parent": "Text Area"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Text",
+ "component_type": "TextMeshProUGUI"
+ }}
+])
+```
+
+### Create Toggle (Checkbox)
+
+```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"
+ }},
+ # Background box
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Background", "parent": "SoundToggle"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Background", "component_type": "Image"
+ }},
+ # Checkmark
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Checkmark", "parent": "Background"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Checkmark", "component_type": "Image"
+ }},
+ # Label
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Label", "parent": "SoundToggle"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Label", "component_type": "TextMeshProUGUI"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "set_property", "target": "Label",
+ "component_type": "TextMeshProUGUI",
+ "properties": {"text": "Sound Effects", "fontSize": 18, "alignment": 513}
+ }}
+])
+```
+
+### Add Layout Group (Vertical/Horizontal/Grid)
+
+Layout groups auto-arrange child elements. Add to any container.
+
+```python
+# Vertical layout for a menu panel
+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": 1,
+ "childForceExpandWidth": True,
+ "childForceExpandHeight": False
+ }
+ }},
+ # Add ContentSizeFitter to auto-resize
+ {"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 (25 command limit per batch).
+
+```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
+ {"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
+ {"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": "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": "PlayButton_Label", "parent": "PlayButton"}},
+ {"tool": "manage_components", "params": {"action": "add", "target": "PlayButton_Label", "component_type": "TextMeshProUGUI"}},
+ {"tool": "manage_components", "params": {"action": "set_property", "target": "PlayButton_Label", "component_type": "TextMeshProUGUI", "properties": {"text": "Play", "fontSize": 32, "alignment": 514}}},
+ # 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": "SettingsButton_Label", "parent": "SettingsButton"}},
+ {"tool": "manage_components", "params": {"action": "add", "target": "SettingsButton_Label", "component_type": "TextMeshProUGUI"}},
+ {"tool": "manage_components", "params": {"action": "set_property", "target": "SettingsButton_Label", "component_type": "TextMeshProUGUI", "properties": {"text": "Settings", "fontSize": 32, "alignment": 514}}},
+ # 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": "QuitButton_Label", "parent": "QuitButton"}},
+ {"tool": "manage_components", "params": {"action": "add", "target": "QuitButton_Label", "component_type": "TextMeshProUGUI"}},
+ {"tool": "manage_components", "params": {"action": "set_property", "target": "QuitButton_Label", "component_type": "TextMeshProUGUI", "properties": {"text": "Quit", "fontSize": 32, "alignment": 514}}}
+])
+```
+
+### UI Component Quick Reference
+
+| UI Element | Required Components | Notes |
+| ---------- | ------------------- | ----- |
+| **Canvas** | Canvas + CanvasScaler + GraphicRaycaster | Root for all UI. One per screen. |
+| **EventSystem** | EventSystem + StandaloneInputModule (or InputSystemUIInputModule) | One per scene. Required for interaction. |
+| **Panel** | Image | Container. Set color for background. |
+| **Text** | TextMeshProUGUI | Auto-adds RectTransform under Canvas. |
+| **Button** | Image + Button + child(TextMeshProUGUI) | Image = visual, Button = click handler. |
+| **Image** | Image | Set sprite property for custom graphics. |
+| **Slider** | Slider + Image + children(Background, Fill Area/Fill, Handle Slide Area/Handle) | Complex hierarchy. |
+| **Toggle** | Toggle + children(Background/Checkmark, Label) | Checkbox/radio button. |
+| **Input Field** | Image + TMP_InputField + children(Text Area/Placeholder/Text) | Text input. |
+| **Scroll View** | ScrollRect + Image + children(Viewport/Content, Scrollbar) | Scrollable container. |
+| **Dropdown** | Image + TMP_Dropdown + children(Label, Arrow, Template) | Selection menu. |
+| **Layout Group** | VerticalLayoutGroup / HorizontalLayoutGroup / GridLayoutGroup | Add to any container to auto-arrange children. |
+
+---
+
## Batch Operations
### Mass Property Update
diff --git a/unity-mcp-skill/SKILL.md b/unity-mcp-skill/SKILL.md
index 40a8541d8..b9a76357e 100644
--- a/unity-mcp-skill/SKILL.md
+++ b/unity-mcp-skill/SKILL.md
@@ -124,6 +124,7 @@ uri="file:///full/path/to/file.cs"
| **Editor** | `manage_editor`, `execute_menu_item`, `read_console` | Editor control |
| **Testing** | `run_tests`, `get_test_job` | Unity Test Framework |
| **Batch** | `batch_execute` | Parallel/bulk operations |
+| **UI** | `batch_execute` with `manage_gameobject` + `manage_components` | Canvas, Panel, Button, Text, Slider, Toggle, Input Field (see [UI workflows](references/workflows.md#ui-creation-workflows)) |
## Common Workflows
diff --git a/unity-mcp-skill/references/workflows.md b/unity-mcp-skill/references/workflows.md
index f04245cf4..629becfa8 100644
--- a/unity-mcp-skill/references/workflows.md
+++ b/unity-mcp-skill/references/workflows.md
@@ -10,6 +10,7 @@ Common workflows and patterns for effective Unity-MCP usage.
- [Asset Management Workflows](#asset-management-workflows)
- [Testing Workflows](#testing-workflows)
- [Debugging Workflows](#debugging-workflows)
+- [UI Creation Workflows](#ui-creation-workflows)
- [Batch Operations](#batch-operations)
---
@@ -491,6 +492,412 @@ manage_scene(action="screenshot")
---
+## UI Creation Workflows
+
+Unity UI (Canvas-based UGUI) requires specific component hierarchies. Use `batch_execute` with `fail_fast=True` to create complete UI elements in a single call. These templates handle the boilerplate so you don't need to remember which components each UI element needs.
+
+### 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.
+
+```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"
+ }}
+])
+```
+
+> **Note:** For projects using legacy Input Manager instead of Input System, use `"component_type": "UnityEngine.EventSystems.StandaloneInputModule"` instead.
+
+### 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"
+ }},
+ # Set semi-transparent dark background
+ {"tool": "manage_components", "params": {
+ "action": "set_property", "target": "MenuPanel",
+ "component_type": "Image", "property": "color",
+ "value": [0.1, 0.1, 0.1, 0.8]
+ }}
+])
+```
+
+### Create Text (TextMeshPro)
+
+TextMeshProUGUI automatically adds a RectTransform when added to a child of a Canvas.
+
+```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]
+ }
+ }}
+])
+```
+
+> **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=[
+ # Button container with Image + Button components
+ {"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]
+ }},
+ # Child text label
+ {"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}
+ }}
+])
+```
+
+### Create Slider
+
+A Slider requires a specific hierarchy: the slider root, a background, a fill area with fill, and a handle area with handle.
+
+```python
+batch_execute(fail_fast=True, commands=[
+ # Slider root
+ {"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"
+ }},
+ # Background
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Background", "parent": "HealthSlider"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Background", "component_type": "Image"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "set_property", "target": "Background",
+ "component_type": "Image", "property": "color",
+ "value": [0.3, 0.3, 0.3, 1.0]
+ }},
+ # Fill Area + Fill
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Fill Area", "parent": "HealthSlider"
+ }},
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Fill", "parent": "Fill Area"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Fill", "component_type": "Image"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "set_property", "target": "Fill",
+ "component_type": "Image", "property": "color",
+ "value": [0.2, 0.8, 0.2, 1.0]
+ }},
+ # Handle Area + Handle
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Handle Slide Area", "parent": "HealthSlider"
+ }},
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Handle", "parent": "Handle Slide Area"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Handle", "component_type": "Image"
+ }}
+])
+```
+
+### Create Input Field (TextMeshPro)
+
+```python
+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"
+ }},
+ # Text area child
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Text Area", "parent": "NameInput"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Text Area",
+ "component_type": "RectMask2D"
+ }},
+ # Placeholder
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Placeholder", "parent": "Text Area"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Placeholder",
+ "component_type": "TextMeshProUGUI"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "set_property", "target": "Placeholder",
+ "component_type": "TextMeshProUGUI",
+ "properties": {"text": "Enter name...", "fontStyle": 2, "color": [0.5, 0.5, 0.5, 0.5]}
+ }},
+ # Actual text
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Text", "parent": "Text Area"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Text",
+ "component_type": "TextMeshProUGUI"
+ }}
+])
+```
+
+### Create Toggle (Checkbox)
+
+```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"
+ }},
+ # Background box
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Background", "parent": "SoundToggle"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Background", "component_type": "Image"
+ }},
+ # Checkmark
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Checkmark", "parent": "Background"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Checkmark", "component_type": "Image"
+ }},
+ # Label
+ {"tool": "manage_gameobject", "params": {
+ "action": "create", "name": "Label", "parent": "SoundToggle"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "add", "target": "Label", "component_type": "TextMeshProUGUI"
+ }},
+ {"tool": "manage_components", "params": {
+ "action": "set_property", "target": "Label",
+ "component_type": "TextMeshProUGUI",
+ "properties": {"text": "Sound Effects", "fontSize": 18, "alignment": 513}
+ }}
+])
+```
+
+### Add Layout Group (Vertical/Horizontal/Grid)
+
+Layout groups auto-arrange child elements. Add to any container.
+
+```python
+# Vertical layout for a menu panel
+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": 1,
+ "childForceExpandWidth": True,
+ "childForceExpandHeight": False
+ }
+ }},
+ # Add ContentSizeFitter to auto-resize
+ {"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 (25 command limit per batch).
+
+```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
+ {"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
+ {"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": "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": "PlayButton_Label", "parent": "PlayButton"}},
+ {"tool": "manage_components", "params": {"action": "add", "target": "PlayButton_Label", "component_type": "TextMeshProUGUI"}},
+ {"tool": "manage_components", "params": {"action": "set_property", "target": "PlayButton_Label", "component_type": "TextMeshProUGUI", "properties": {"text": "Play", "fontSize": 32, "alignment": 514}}},
+ # 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": "SettingsButton_Label", "parent": "SettingsButton"}},
+ {"tool": "manage_components", "params": {"action": "add", "target": "SettingsButton_Label", "component_type": "TextMeshProUGUI"}},
+ {"tool": "manage_components", "params": {"action": "set_property", "target": "SettingsButton_Label", "component_type": "TextMeshProUGUI", "properties": {"text": "Settings", "fontSize": 32, "alignment": 514}}},
+ # 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": "QuitButton_Label", "parent": "QuitButton"}},
+ {"tool": "manage_components", "params": {"action": "add", "target": "QuitButton_Label", "component_type": "TextMeshProUGUI"}},
+ {"tool": "manage_components", "params": {"action": "set_property", "target": "QuitButton_Label", "component_type": "TextMeshProUGUI", "properties": {"text": "Quit", "fontSize": 32, "alignment": 514}}}
+])
+```
+
+### UI Component Quick Reference
+
+| UI Element | Required Components | Notes |
+| ---------- | ------------------- | ----- |
+| **Canvas** | Canvas + CanvasScaler + GraphicRaycaster | Root for all UI. One per screen. |
+| **EventSystem** | EventSystem + StandaloneInputModule (or InputSystemUIInputModule) | One per scene. Required for interaction. |
+| **Panel** | Image | Container. Set color for background. |
+| **Text** | TextMeshProUGUI | Auto-adds RectTransform under Canvas. |
+| **Button** | Image + Button + child(TextMeshProUGUI) | Image = visual, Button = click handler. |
+| **Image** | Image | Set sprite property for custom graphics. |
+| **Slider** | Slider + Image + children(Background, Fill Area/Fill, Handle Slide Area/Handle) | Complex hierarchy. |
+| **Toggle** | Toggle + children(Background/Checkmark, Label) | Checkbox/radio button. |
+| **Input Field** | Image + TMP_InputField + children(Text Area/Placeholder/Text) | Text input. |
+| **Scroll View** | ScrollRect + Image + children(Viewport/Content, Scrollbar) | Scrollable container. |
+| **Dropdown** | Image + TMP_Dropdown + children(Label, Arrow, Template) | Selection menu. |
+| **Layout Group** | VerticalLayoutGroup / HorizontalLayoutGroup / GridLayoutGroup | Add to any container to auto-arrange children. |
+
+---
+
## Batch Operations
### Mass Property Update
From f067a10999f8163d06f4588846ea6b27aaacf8d8 Mon Sep 17 00:00:00 2001
From: Shutong Wu <51266340+Scriptwonder@users.noreply.github.com>
Date: Wed, 11 Feb 2026 18:46:39 -0500
Subject: [PATCH 2/2] [fix and mod] Bug fix and batch request customization
1. Fix the bug where for certain MacOS user (like me), the GitURLoverride is set to the unity-mcp folder rather than the unity-mcp/Server
2. Customize batch for it to take 0-100 requests. Tested on MacOS.
---
.claude/skills/unity-mcp-skill/SKILL.md | 2 +-
.../unity-mcp-skill/references/workflows.md | 2 +-
.../Editor/Constants/EditorPrefKeys.cs | 2 +
.../Editor/Helpers/AssetPathUtility.cs | 64 ++++++++++++++++++-
.../Editor/Services/EditorStateCache.cs | 13 ++++
MCPForUnity/Editor/Tools/BatchExecute.cs | 23 ++++++-
.../Components/Advanced/McpAdvancedSection.cs | 57 ++++++++++++++++-
.../Components/Tools/McpToolsSection.cs | 53 +++++++++++++++
Server/src/services/resources/editor_state.py | 5 ++
Server/src/services/tools/batch_execute.py | 55 ++++++++++++++--
unity-mcp-skill/SKILL.md | 2 +-
unity-mcp-skill/references/workflows.md | 2 +-
12 files changed, 267 insertions(+), 13 deletions(-)
diff --git a/.claude/skills/unity-mcp-skill/SKILL.md b/.claude/skills/unity-mcp-skill/SKILL.md
index b9a76357e..938ad19a6 100644
--- a/.claude/skills/unity-mcp-skill/SKILL.md
+++ b/.claude/skills/unity-mcp-skill/SKILL.md
@@ -45,7 +45,7 @@ batch_execute(
)
```
-**Max 25 commands per batch.** Use `fail_fast=True` for dependent operations.
+**Max 25 commands per batch by default (configurable in Unity MCP Tools window, hard max 100).** Use `fail_fast=True` for dependent operations.
### 3. Use `screenshot` in manage_scene to Verify Visual Results
diff --git a/.claude/skills/unity-mcp-skill/references/workflows.md b/.claude/skills/unity-mcp-skill/references/workflows.md
index 629becfa8..511f6fccb 100644
--- a/.claude/skills/unity-mcp-skill/references/workflows.md
+++ b/.claude/skills/unity-mcp-skill/references/workflows.md
@@ -822,7 +822,7 @@ batch_execute(fail_fast=True, commands=[
### Complete Example: Main Menu Screen
-Combines multiple templates into a full menu screen in two batch calls (25 command limit per batch).
+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).
```python
# Batch 1: Canvas + EventSystem + Panel + Title
diff --git a/MCPForUnity/Editor/Constants/EditorPrefKeys.cs b/MCPForUnity/Editor/Constants/EditorPrefKeys.cs
index 5f99d3e5a..f1360984b 100644
--- a/MCPForUnity/Editor/Constants/EditorPrefKeys.cs
+++ b/MCPForUnity/Editor/Constants/EditorPrefKeys.cs
@@ -63,5 +63,7 @@ internal static class EditorPrefKeys
internal const string CustomerUuid = "MCPForUnity.CustomerUUID";
internal const string ApiKey = "MCPForUnity.ApiKey";
+
+ internal const string BatchExecuteMaxCommands = "MCPForUnity.BatchExecute.MaxCommands";
}
}
diff --git a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs
index fc450bada..c45469413 100644
--- a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs
+++ b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs
@@ -201,6 +201,8 @@ public static JObject GetPackageJson()
/// Gets the package source for the MCP server (used with uvx --from).
/// Checks for EditorPrefs override first (supports git URLs, file:// paths, etc.),
/// then falls back to PyPI package reference.
+ /// When the override is a local path, auto-corrects to the "Server" subdirectory
+ /// if the path doesn't contain pyproject.toml but Server/pyproject.toml exists.
///
/// Package source string for uvx --from argument
public static string GetMcpServerPackageSource()
@@ -209,7 +211,14 @@ public static string GetMcpServerPackageSource()
string sourceOverride = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
if (!string.IsNullOrEmpty(sourceOverride))
{
- return sourceOverride;
+ string resolved = ResolveLocalServerPath(sourceOverride);
+ // Persist the corrected path so future reads are consistent
+ if (resolved != sourceOverride)
+ {
+ EditorPrefs.SetString(EditorPrefKeys.GitUrlOverride, resolved);
+ McpLog.Info($"Auto-corrected server source override from '{sourceOverride}' to '{resolved}'");
+ }
+ return resolved;
}
// Default to PyPI package (avoids Windows long path issues with git clone)
@@ -223,6 +232,59 @@ public static string GetMcpServerPackageSource()
return $"mcpforunityserver=={version}";
}
+ ///
+ /// Validates and auto-corrects a local server source path to ensure it points to the
+ /// directory containing pyproject.toml. If the path points to a parent directory
+ /// (e.g. the repo root "unity-mcp") instead of the Python package directory ("Server"),
+ /// this checks for a "Server" subdirectory with pyproject.toml and returns that path.
+ /// Non-local paths (URLs, PyPI references) are returned unchanged.
+ ///
+ internal static string ResolveLocalServerPath(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ return path;
+
+ // Skip non-local paths (git URLs, PyPI package names, etc.)
+ if (path.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
+ path.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
+ path.StartsWith("git+", StringComparison.OrdinalIgnoreCase) ||
+ path.StartsWith("ssh://", StringComparison.OrdinalIgnoreCase))
+ {
+ return path;
+ }
+
+ // If it looks like a PyPI package reference (no path separators), skip
+ if (!path.Contains('/') && !path.Contains('\\') && !path.StartsWith("file:", StringComparison.OrdinalIgnoreCase))
+ {
+ return path;
+ }
+
+ // Strip file:// prefix for filesystem checks, preserve for return value
+ string checkPath = path;
+ string prefix = string.Empty;
+ if (checkPath.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
+ {
+ prefix = checkPath.Substring(0, 7); // preserve original casing
+ checkPath = checkPath.Substring(7);
+ }
+
+ // Already correct — pyproject.toml exists at this path
+ if (System.IO.File.Exists(System.IO.Path.Combine(checkPath, "pyproject.toml")))
+ {
+ return path;
+ }
+
+ // Check if "Server" subdirectory contains pyproject.toml
+ string serverSubDir = System.IO.Path.Combine(checkPath, "Server");
+ if (System.IO.File.Exists(System.IO.Path.Combine(serverSubDir, "pyproject.toml")))
+ {
+ return prefix + serverSubDir;
+ }
+
+ // Return as-is; uvx will report the error if the path is truly invalid
+ return path;
+ }
+
///
/// Deprecated: Use GetMcpServerPackageSource() instead.
/// Kept for backwards compatibility.
diff --git a/MCPForUnity/Editor/Services/EditorStateCache.cs b/MCPForUnity/Editor/Services/EditorStateCache.cs
index 24fec0f14..9dd85d529 100644
--- a/MCPForUnity/Editor/Services/EditorStateCache.cs
+++ b/MCPForUnity/Editor/Services/EditorStateCache.cs
@@ -75,6 +75,9 @@ private sealed class EditorStateSnapshot
[JsonProperty("transport")]
public EditorStateTransport Transport { get; set; }
+
+ [JsonProperty("settings")]
+ public EditorStateSettings Settings { get; set; }
}
private sealed class EditorStateUnity
@@ -239,6 +242,12 @@ private sealed class EditorStateTransport
public long? LastMessageUnixMs { get; set; }
}
+ private sealed class EditorStateSettings
+ {
+ [JsonProperty("batch_execute_max_commands")]
+ public int BatchExecuteMaxCommands { get; set; }
+ }
+
static EditorStateCache()
{
try
@@ -482,6 +491,10 @@ private static JObject BuildSnapshot(string reason)
{
UnityBridgeConnected = null,
LastMessageUnixMs = null
+ },
+ Settings = new EditorStateSettings
+ {
+ BatchExecuteMaxCommands = Tools.BatchExecute.GetMaxCommandsPerBatch()
}
};
diff --git a/MCPForUnity/Editor/Tools/BatchExecute.cs b/MCPForUnity/Editor/Tools/BatchExecute.cs
index d9df336d6..66dc2b39b 100644
--- a/MCPForUnity/Editor/Tools/BatchExecute.cs
+++ b/MCPForUnity/Editor/Tools/BatchExecute.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using MCPForUnity.Editor.Constants;
using MCPForUnity.Editor.Helpers;
using Newtonsoft.Json.Linq;
+using UnityEditor;
namespace MCPForUnity.Editor.Tools
{
@@ -13,7 +15,20 @@ namespace MCPForUnity.Editor.Tools
[McpForUnityTool("batch_execute", AutoRegister = false)]
public static class BatchExecute
{
- private const int MaxCommandsPerBatch = 25;
+ /// Default limit when no EditorPrefs override is set.
+ internal const int DefaultMaxCommandsPerBatch = 25;
+
+ /// Hard ceiling to prevent extreme editor freezes regardless of user setting.
+ internal const int AbsoluteMaxCommandsPerBatch = 100;
+
+ ///
+ /// Returns the user-configured max commands per batch, clamped between 1 and .
+ ///
+ internal static int GetMaxCommandsPerBatch()
+ {
+ int configured = EditorPrefs.GetInt(EditorPrefKeys.BatchExecuteMaxCommands, DefaultMaxCommandsPerBatch);
+ return Math.Clamp(configured, 1, AbsoluteMaxCommandsPerBatch);
+ }
public static async Task