diff --git a/.claude/skills/unity-mcp-skill/SKILL.md b/.claude/skills/unity-mcp-skill/SKILL.md
index 0c0a11f41..282578d55 100644
--- a/.claude/skills/unity-mcp-skill/SKILL.md
+++ b/.claude/skills/unity-mcp-skill/SKILL.md
@@ -178,11 +178,11 @@ uri="file:///full/path/to/file.cs"
| Category | Key Tools | Use For |
|----------|-----------|---------|
-| **Scene** | `manage_scene`, `find_gameobjects` | Scene operations, finding objects |
+| **Scene** | `manage_scene`, `find_gameobjects` | Scene operations, finding objects. Multi-scene editing (additive load, close, set active, move GO between scenes), scene templates (`3d_basic`, `2d_basic`, `empty`, `default`), scene validation with `auto_repair`. For build settings, use `manage_build(action="scenes")`. |
| **Objects** | `manage_gameobject`, `manage_components` | Creating/modifying GameObjects |
| **Scripts** | `create_script`, `script_apply_edits`, `validate_script` | C# code management (auto-refreshes on create/edit) |
| **Assets** | `manage_asset`, `manage_prefabs` | Asset operations. **Prefab instantiation** is done via `manage_gameobject(action="create", prefab_path="...")`, not `manage_prefabs`. |
-| **Editor** | `manage_editor`, `execute_menu_item`, `read_console` | Editor control, package deployment (`deploy_package`/`restore_package` actions) |
+| **Editor** | `manage_editor`, `execute_menu_item`, `read_console` | Editor control, package deployment (`deploy_package`/`restore_package`), undo/redo (`undo`/`redo` actions) |
| **Testing** | `run_tests`, `get_test_job` | Unity Test Framework |
| **Batch** | `batch_execute` | Parallel/bulk operations |
| **Camera** | `manage_camera` | Camera management (Unity Camera + Cinemachine). **Tier 1** (always available): create, target, lens, priority, list, screenshot. **Tier 2** (requires `com.unity.cinemachine`): brain, body/aim/noise pipeline, extensions, blending, force/release. 7 presets: follow, third_person, freelook, dolly, static, top_down, side_scroller. Resource: `mcpforunity://scene/cameras`. Use `ping` to check Cinemachine availability. See [tools-reference.md](references/tools-reference.md#camera-tools). |
diff --git a/.claude/skills/unity-mcp-skill/references/tools-reference.md b/.claude/skills/unity-mcp-skill/references/tools-reference.md
index 21349c7e1..bc100e25a 100644
--- a/.claude/skills/unity-mcp-skill/references/tools-reference.md
+++ b/.claude/skills/unity-mcp-skill/references/tools-reference.md
@@ -175,6 +175,26 @@ manage_scene(action="get_build_settings") # Build settings
manage_scene(action="create", name="NewScene", path="Assets/Scenes/")
manage_scene(action="load", path="Assets/Scenes/Main.unity")
manage_scene(action="save")
+
+# Scene templates — create with preset objects
+manage_scene(action="create", name="Level1", template="3d_basic") # Camera + Light + Ground
+manage_scene(action="create", name="Level2", template="2d_basic") # Camera (ortho) + Light
+manage_scene(action="create", name="Empty", template="empty") # No default objects
+manage_scene(action="create", name="Default", template="default") # Camera + Light (Unity default)
+
+# Multi-scene editing
+manage_scene(action="load", path="Assets/Scenes/Level2.unity", additive=True) # Keep current scene
+manage_scene(action="get_loaded_scenes") # List all loaded scenes
+manage_scene(action="set_active_scene", scene_name="Level2") # Set active scene
+manage_scene(action="close_scene", scene_name="Level2") # Unload scene
+manage_scene(action="close_scene", scene_name="Level2", remove_scene=True) # Fully remove
+manage_scene(action="move_to_scene", target="Player", scene_name="Level2") # Move root GO
+
+# Build settings — use manage_build(action="scenes") instead
+
+# Scene validation
+manage_scene(action="validate") # Detect missing scripts, broken prefabs
+manage_scene(action="validate", auto_repair=True) # Also auto-fix missing scripts (undoable)
```
### find_gameobjects
@@ -332,6 +352,11 @@ manage_components(
# - "Assets/Prefabs/My.prefab" → String shorthand for asset paths
# - "ObjectName" → String shorthand for scene name lookup
# - 12345 → Integer shorthand for instanceID
+#
+# Sprite sub-asset references (for SpriteRenderer.sprite, Image.sprite, etc.):
+# - {"guid": "...", "spriteName": "SubSprite"} → Sprite sub-asset from atlas
+# - {"guid": "...", "fileID": 12345} → Sub-asset by fileID
+# Single-sprite textures auto-resolve from guid/path alone.
```
---
@@ -523,6 +548,24 @@ manage_prefabs(
position=[0, 1, 0],
components_to_add=["AudioSource"]
)
+
+# Add child GameObjects to a prefab (single or batch)
+manage_prefabs(
+ action="modify_contents",
+ prefab_path="Assets/Prefabs/Player.prefab",
+ create_child=[
+ {"name": "Child1", "primitive_type": "Sphere", "position": [1, 0, 0]},
+ {"name": "Child2", "primitive_type": "Cube", "parent": "Child1"}
+ ]
+)
+
+# Add a nested prefab instance inside a prefab
+manage_prefabs(
+ action="modify_contents",
+ prefab_path="Assets/Prefabs/Player.prefab",
+ create_child={"name": "Bullet", "source_prefab_path": "Assets/Prefabs/Bullet.prefab", "position": [0, 2, 0]}
+)
+# source_prefab_path and primitive_type are mutually exclusive
```
---
@@ -691,7 +734,7 @@ manage_ui(
### manage_editor
-Control Unity Editor state.
+Control Unity Editor state, undo/redo.
```python
manage_editor(action="play") # Enter play mode
@@ -708,6 +751,10 @@ manage_editor(action="remove_layer", layer_name="OldLayer")
manage_editor(action="close_prefab_stage") # Exit prefab editing mode back to main scene
+# Undo/Redo — returns the affected undo group name
+manage_editor(action="undo") # Undo last action
+manage_editor(action="redo") # Redo last undone action
+
# Package deployment (no confirmation dialog — designed for LLM-driven iteration)
manage_editor(action="deploy_package") # Copy configured MCPForUnity source into installed package
manage_editor(action="restore_package") # Revert to pre-deployment backup
diff --git a/MCPForUnity/Editor/Tools/ManageEditor.cs b/MCPForUnity/Editor/Tools/ManageEditor.cs
index 5bfd1972d..0fbc89217 100644
--- a/MCPForUnity/Editor/Tools/ManageEditor.cs
+++ b/MCPForUnity/Editor/Tools/ManageEditor.cs
@@ -145,9 +145,38 @@ public static object HandleCommand(JObject @params)
case "restore_package":
return RestorePackage();
+ // Undo/Redo
+ case "undo":
+ {
+ string groupName = Undo.GetCurrentGroupName();
+ Undo.PerformUndo();
+ string message = string.IsNullOrEmpty(groupName)
+ ? "Undo performed (stack may be empty)."
+ : $"Undid: {groupName}";
+ if (EditorApplication.isPlaying)
+ message += " Warning: undo during play mode may have unexpected effects.";
+ return new SuccessResponse(message, new
+ {
+ undone_group = string.IsNullOrEmpty(groupName) ? (string)null : groupName,
+ next_group = Undo.GetCurrentGroupName()
+ });
+ }
+ case "redo":
+ {
+ Undo.PerformRedo();
+ string nextGroup = Undo.GetCurrentGroupName();
+ string message = "Redo performed.";
+ if (EditorApplication.isPlaying)
+ message += " Warning: redo during play mode may have unexpected effects.";
+ return new SuccessResponse(message, new
+ {
+ current_group = string.IsNullOrEmpty(nextGroup) ? (string)null : nextGroup
+ });
+ }
+
default:
return new ErrorResponse(
- $"Unknown action: '{action}'. Supported actions: play, pause, stop, set_active_tool, add_tag, remove_tag, add_layer, remove_layer, close_prefab_stage, deploy_package, restore_package. Use MCP resources for reading editor state, project info, tags, layers, selection, windows, prefab stage, and active tool."
+ $"Unknown action: '{action}'. Supported actions: play, pause, stop, set_active_tool, add_tag, remove_tag, add_layer, remove_layer, close_prefab_stage, deploy_package, restore_package, undo, redo. Use MCP resources for reading editor state, project info, tags, layers, selection, windows, prefab stage, and active tool."
);
}
}
diff --git a/MCPForUnity/Editor/Tools/ManageScene.cs b/MCPForUnity/Editor/Tools/ManageScene.cs
index cdae1f22d..de44c8139 100644
--- a/MCPForUnity/Editor/Tools/ManageScene.cs
+++ b/MCPForUnity/Editor/Tools/ManageScene.cs
@@ -54,6 +54,15 @@ private sealed class SceneCommand
public int? maxDepth { get; set; }
public int? maxChildrenPerNode { get; set; }
public bool? includeTransform { get; set; }
+
+ // Multi-scene editing
+ public string sceneName { get; set; }
+ public string scenePath { get; set; }
+ public string target { get; set; } // GO reference for move_to_scene
+ public bool? removeScene { get; set; } // for close_scene
+ public bool? additive { get; set; } // for load additive mode
+ public string template { get; set; } // for create with template
+ public bool? autoRepair { get; set; } // for validate with auto-repair
}
private static float[] ParseFloatArray(JToken token)
@@ -122,9 +131,31 @@ private static SceneCommand ToSceneCommand(JObject p)
maxDepth = ParamCoercion.CoerceIntNullable(p["maxDepth"] ?? p["max_depth"]),
maxChildrenPerNode = ParamCoercion.CoerceIntNullable(p["maxChildrenPerNode"] ?? p["max_children_per_node"]),
includeTransform = ParamCoercion.CoerceBoolNullable(p["includeTransform"] ?? p["include_transform"]),
+
+ // Multi-scene editing
+ sceneName = (p["sceneName"] ?? p["scene_name"])?.ToString(),
+ scenePath = (p["scenePath"] ?? p["scene_path"])?.ToString(),
+ target = (p["target"])?.ToString(),
+ removeScene = ParamCoercion.CoerceBoolNullable(p["removeScene"] ?? p["remove_scene"]),
+ additive = ParamCoercion.CoerceBoolNullable(p["additive"]),
+ template = (p["template"])?.ToString()?.ToLowerInvariant(),
+ autoRepair = ParamCoercion.CoerceBoolNullable(p["autoRepair"] ?? p["auto_repair"]),
};
}
+ private static Scene? FindLoadedScene(string sceneName, string scenePath)
+ {
+ for (int i = 0; i < SceneManager.sceneCount; i++)
+ {
+ var scene = SceneManager.GetSceneAt(i);
+ if (!string.IsNullOrEmpty(scenePath) && scene.path == scenePath)
+ return scene;
+ if (!string.IsNullOrEmpty(sceneName) && scene.name == sceneName)
+ return scene;
+ }
+ return null;
+ }
+
///
/// Main handler for scene management actions.
///
@@ -191,15 +222,27 @@ public static object HandleCommand(JObject @params)
switch (action)
{
case "create":
- if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(relativePath))
+ if (string.IsNullOrEmpty(name))
return new ErrorResponse(
- "'name' and 'path' parameters are required for 'create' action."
+ "'name' parameter is required for 'create' action. 'path' is optional (defaults to 'Assets/Scenes/')."
);
+ if (!string.IsNullOrEmpty(cmd.template))
+ return CreateSceneFromTemplate(fullPath, relativePath, cmd.template);
return CreateScene(fullPath, relativePath);
case "load":
// Loading can be done by path/name or build index
- if (!string.IsNullOrEmpty(relativePath))
- return LoadScene(relativePath);
+ // When path ends with .unity and no name is given, use path directly as the scene path
+ string loadPath = relativePath;
+ if (string.IsNullOrEmpty(loadPath) && !string.IsNullOrEmpty(path))
+ loadPath = AssetPathUtility.NormalizeSeparators(
+ path.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase)
+ ? path : "Assets/" + path);
+ if (!string.IsNullOrEmpty(loadPath))
+ {
+ if (cmd.additive == true)
+ return LoadSceneAdditive(loadPath);
+ return LoadScene(loadPath);
+ }
else if (buildIndex.HasValue)
return LoadScene(buildIndex.Value);
else
@@ -225,9 +268,28 @@ public static object HandleCommand(JObject @params)
return CaptureScreenshot(cmd);
case "scene_view_frame":
return FrameSceneView(cmd);
+
+ // Multi-scene editing
+ case "close_scene":
+ return CloseScene(cmd);
+ case "set_active_scene":
+ return SetActiveScene(cmd);
+ case "get_loaded_scenes":
+ return GetLoadedScenes();
+ case "move_to_scene":
+ return MoveToScene(cmd);
+ case "modify_build_settings":
+ return new ErrorResponse(
+ "Build settings management has moved to manage_build (action='scenes'). "
+ + "Use manage_build to add, remove, or configure scenes in build settings.");
+
+ // Scene validation
+ case "validate":
+ return ValidateScene(cmd.autoRepair == true);
+
default:
return new ErrorResponse(
- $"Unknown action: '{action}'. Valid actions: create, load, save, get_hierarchy, get_active, get_build_settings, screenshot, scene_view_frame."
+ $"Unknown action: '{action}'. Valid actions: create, load, save, get_hierarchy, get_active, get_build_settings, screenshot, scene_view_frame, close_scene, set_active_scene, get_loaded_scenes, move_to_scene, validate. For build settings, use manage_build."
);
}
}
@@ -1446,6 +1508,252 @@ private static void ScheduleAssetImportWhenFileExists(string assetsRelativePath,
EditorApplication.update += tick;
}
+ // ── Multi-scene editing ────────────────────────────────────────────
+
+ private static object LoadSceneAdditive(string scenePath)
+ {
+ string projectRoot = Application.dataPath.Substring(0, Application.dataPath.Length - "Assets".Length);
+ if (!File.Exists(Path.Combine(projectRoot, scenePath)))
+ return new ErrorResponse($"Scene not found: '{scenePath}'");
+
+ var existing = SceneManager.GetSceneByPath(scenePath);
+ if (existing.IsValid() && existing.isLoaded)
+ return new ErrorResponse($"Scene '{existing.name}' is already loaded.");
+
+ var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
+ return new SuccessResponse($"Opened '{scene.name}' additively.", new
+ {
+ sceneName = scene.name,
+ scenePath = scene.path,
+ loadedSceneCount = SceneManager.sceneCount
+ });
+ }
+
+ private static object CloseScene(SceneCommand cmd)
+ {
+ var scene = FindLoadedScene(cmd.sceneName ?? cmd.name, cmd.scenePath);
+ if (!scene.HasValue)
+ return new ErrorResponse("Scene not found among loaded scenes. Provide 'sceneName' or 'scenePath'.");
+
+ if (SceneManager.sceneCount <= 1)
+ return new ErrorResponse("Cannot close the last loaded scene.");
+
+ if (scene.Value.isDirty)
+ return new ErrorResponse($"Scene '{scene.Value.name}' has unsaved changes. Save first or data will be lost.");
+
+ string capturedName = scene.Value.name;
+ bool remove = cmd.removeScene ?? false;
+ bool closed = EditorSceneManager.CloseScene(scene.Value, remove);
+ string verb = remove ? "Removed" : "Unloaded";
+ if (!closed)
+ return new ErrorResponse($"Failed to {verb.ToLowerInvariant()} scene '{capturedName}'.");
+ return new SuccessResponse($"{verb} scene '{capturedName}'.", new
+ {
+ sceneName = capturedName,
+ removed = remove,
+ loadedSceneCount = SceneManager.sceneCount
+ });
+ }
+
+ private static object SetActiveScene(SceneCommand cmd)
+ {
+ var scene = FindLoadedScene(cmd.sceneName ?? cmd.name, cmd.scenePath);
+ if (!scene.HasValue)
+ return new ErrorResponse("Scene not found among loaded scenes. Provide 'sceneName' or 'scenePath'.");
+ if (!scene.Value.isLoaded)
+ return new ErrorResponse($"Scene '{scene.Value.name}' is not loaded. Open it first.");
+
+ string capturedName = scene.Value.name;
+ bool success = SceneManager.SetActiveScene(scene.Value);
+ if (!success)
+ return new ErrorResponse($"Failed to set '{capturedName}' as the active scene.");
+ return new SuccessResponse($"Set '{capturedName}' as the active scene.");
+ }
+
+ private static object GetLoadedScenes()
+ {
+ var activeScene = SceneManager.GetActiveScene();
+ var scenes = new List