diff --git a/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs b/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs index e686145ef..8d8a36eac 100644 --- a/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs +++ b/MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs @@ -154,9 +154,16 @@ internal static List FindObjectsInternal( } else { +#if UNITY_2023_1_OR_NEWER + var inactive = searchInactive ? FindObjectsInactive.Include : FindObjectsInactive.Exclude; + searchPoolComp = UnityEngine.Object.FindObjectsByType(componentType, inactive, FindObjectsSortMode.None) + .Cast() + .Select(c => c.gameObject); +#else searchPoolComp = UnityEngine.Object.FindObjectsOfType(componentType, searchInactive) .Cast() .Select(c => c.gameObject); +#endif } results.AddRange(searchPoolComp.Where(go => go != null)); } diff --git a/MCPForUnity/Editor/Tools/ManageScene.cs b/MCPForUnity/Editor/Tools/ManageScene.cs index e58c09424..2f9e397b3 100644 --- a/MCPForUnity/Editor/Tools/ManageScene.cs +++ b/MCPForUnity/Editor/Tools/ManageScene.cs @@ -384,34 +384,27 @@ private static object CaptureScreenshot(string fileName, int? superSize) try { int resolvedSuperSize = (superSize.HasValue && superSize.Value > 0) ? superSize.Value : 1; - ScreenshotCaptureResult result; - if (Application.isPlaying) + // Best-effort: ensure Game View exists and repaints before capture. + if (!Application.isBatchMode) { - result = ScreenshotUtility.CaptureToAssetsFolder(fileName, resolvedSuperSize, ensureUniqueFileName: true); + EnsureGameView(); } - else - { - // Edit Mode path: render from the best-guess camera using RenderTexture. - Camera cam = Camera.main; - if (cam == null) - { - // Use FindObjectsOfType for Unity 2021 compatibility - var cams = UnityEngine.Object.FindObjectsOfType(); - cam = cams.FirstOrDefault(); - } - if (cam == null) - { - return new ErrorResponse("No camera found to capture screenshot in Edit Mode."); - } + ScreenshotCaptureResult result = ScreenshotUtility.CaptureToAssetsFolder(fileName, resolvedSuperSize, ensureUniqueFileName: true); - result = ScreenshotUtility.CaptureFromCameraToAssetsFolder(cam, fileName, resolvedSuperSize, ensureUniqueFileName: true); + // ScreenCapture.CaptureScreenshot is async. Import after the file actually hits disk. + if (result.IsAsync) + { + ScheduleAssetImportWhenFileExists(result.AssetsRelativePath, result.FullPath, timeoutSeconds: 30.0); + } + else + { + AssetDatabase.ImportAsset(result.AssetsRelativePath, ImportAssetOptions.ForceSynchronousImport); } - AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); - - string message = $"Screenshot captured to '{result.AssetsRelativePath}' (full: {result.FullPath})."; + string verb = result.IsAsync ? "Screenshot requested" : "Screenshot captured"; + string message = $"{verb} to '{result.AssetsRelativePath}' (full: {result.FullPath})."; return new SuccessResponse( message, @@ -420,6 +413,7 @@ private static object CaptureScreenshot(string fileName, int? superSize) path = result.AssetsRelativePath, fullPath = result.FullPath, superSize = result.SuperSize, + isAsync = result.IsAsync, } ); } @@ -429,6 +423,111 @@ private static object CaptureScreenshot(string fileName, int? superSize) } } + private static void EnsureGameView() + { + try + { + // Ensure a Game View exists and has a chance to repaint before capture. + try + { + if (!EditorApplication.ExecuteMenuItem("Window/General/Game")) + { + // Some Unity versions expose hotkey suffixes in menu paths. + EditorApplication.ExecuteMenuItem("Window/General/Game %2"); + } + } + catch (Exception e) + { + try { McpLog.Debug($"[ManageScene] screenshot: failed to open Game View via menu item: {e.Message}"); } catch { } + } + + try + { + var gameViewType = Type.GetType("UnityEditor.GameView,UnityEditor"); + if (gameViewType != null) + { + var window = EditorWindow.GetWindow(gameViewType); + window?.Repaint(); + } + } + catch (Exception e) + { + try { McpLog.Debug($"[ManageScene] screenshot: failed to repaint Game View: {e.Message}"); } catch { } + } + + try { SceneView.RepaintAll(); } + catch (Exception e) + { + try { McpLog.Debug($"[ManageScene] screenshot: failed to repaint Scene View: {e.Message}"); } catch { } + } + + try { EditorApplication.QueuePlayerLoopUpdate(); } + catch (Exception e) + { + try { McpLog.Debug($"[ManageScene] screenshot: failed to queue player loop update: {e.Message}"); } catch { } + } + } + catch (Exception e) + { + try { McpLog.Debug($"[ManageScene] screenshot: EnsureGameView failed: {e.Message}"); } catch { } + } + } + + private static void ScheduleAssetImportWhenFileExists(string assetsRelativePath, string fullPath, double timeoutSeconds) + { + if (string.IsNullOrWhiteSpace(assetsRelativePath) || string.IsNullOrWhiteSpace(fullPath)) + { + McpLog.Warn("[ManageScene] ScheduleAssetImportWhenFileExists: invalid paths provided, skipping import scheduling."); + return; + } + + double start = EditorApplication.timeSinceStartup; + int failureCount = 0; + bool hasSeenFile = false; + const int maxLoggedFailures = 3; + EditorApplication.CallbackFunction tick = null; + tick = () => + { + try + { + if (File.Exists(fullPath)) + { + hasSeenFile = true; + + AssetDatabase.ImportAsset(assetsRelativePath, ImportAssetOptions.ForceSynchronousImport); + McpLog.Debug($"[ManageScene] Imported asset at '{assetsRelativePath}'."); + EditorApplication.update -= tick; + return; + } + } + catch (Exception e) + { + failureCount++; + + if (failureCount <= maxLoggedFailures) + { + McpLog.Warn($"[ManageScene] Exception while importing asset '{assetsRelativePath}' from '{fullPath}' (attempt {failureCount}): {e}"); + } + } + + if (EditorApplication.timeSinceStartup - start > timeoutSeconds) + { + if (!hasSeenFile) + { + McpLog.Warn($"[ManageScene] Timed out waiting for file '{fullPath}' (asset: '{assetsRelativePath}') after {timeoutSeconds:F1} seconds. The asset was not imported."); + } + else + { + McpLog.Warn($"[ManageScene] Timed out importing asset '{assetsRelativePath}' from '{fullPath}' after {timeoutSeconds:F1} seconds. The file existed but the asset was not imported."); + } + + EditorApplication.update -= tick; + } + }; + + EditorApplication.update += tick; + } + private static object GetActiveSceneInfo() { try @@ -668,7 +767,10 @@ private static object BuildGameObjectSummary(GameObject go, bool includeTransfor } } } - catch { } + catch (Exception ex) + { + McpLog.Debug($"[ManageScene] Failed to enumerate components for '{go.name}': {ex.Message}"); + } var d = new Dictionary { @@ -684,7 +786,7 @@ private static object BuildGameObjectSummary(GameObject go, bool includeTransfor { "childrenTruncated", childrenTruncated }, { "childrenCursor", childCount > 0 ? "0" : null }, { "childrenPageSizeDefault", maxChildrenPerNode }, - { "componentTypes", componentTypes }, // NEW: Lightweight component type list + { "componentTypes", componentTypes }, }; if (includeTransform && go.transform != null) @@ -721,57 +823,5 @@ private static string GetGameObjectPath(GameObject go) } } - /// - /// Recursively builds a data representation of a GameObject and its children. - /// - private static object GetGameObjectDataRecursive(GameObject go) - { - if (go == null) - return null; - - var childrenData = new List(); - foreach (Transform child in go.transform) - { - childrenData.Add(GetGameObjectDataRecursive(child.gameObject)); - } - - var gameObjectData = new Dictionary - { - { "name", go.name }, - { "activeSelf", go.activeSelf }, - { "activeInHierarchy", go.activeInHierarchy }, - { "tag", go.tag }, - { "layer", go.layer }, - { "isStatic", go.isStatic }, - { "instanceID", go.GetInstanceID() }, // Useful unique identifier - { - "transform", - new - { - position = new - { - x = go.transform.localPosition.x, - y = go.transform.localPosition.y, - z = go.transform.localPosition.z, - }, - rotation = new - { - x = go.transform.localRotation.eulerAngles.x, - y = go.transform.localRotation.eulerAngles.y, - z = go.transform.localRotation.eulerAngles.z, - }, // Euler for simplicity - scale = new - { - x = go.transform.localScale.x, - y = go.transform.localScale.y, - z = go.transform.localScale.z, - }, - } - }, - { "children", childrenData }, - }; - - return gameObjectData; - } } } diff --git a/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs b/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs index eb71da614..4a001b058 100644 --- a/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs +++ b/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs @@ -9,58 +9,70 @@ namespace MCPForUnity.Runtime.Helpers public readonly struct ScreenshotCaptureResult { public ScreenshotCaptureResult(string fullPath, string assetsRelativePath, int superSize) + : this(fullPath, assetsRelativePath, superSize, isAsync: false) + { + } + + public ScreenshotCaptureResult(string fullPath, string assetsRelativePath, int superSize, bool isAsync) { FullPath = fullPath; AssetsRelativePath = assetsRelativePath; SuperSize = superSize; + IsAsync = isAsync; } public string FullPath { get; } public string AssetsRelativePath { get; } public int SuperSize { get; } + public bool IsAsync { get; } } public static class ScreenshotUtility { private const string ScreenshotsFolderName = "Screenshots"; + private static bool s_loggedLegacyScreenCaptureFallback; - public static ScreenshotCaptureResult CaptureToAssetsFolder(string fileName = null, int superSize = 1, bool ensureUniqueFileName = true) + private static Camera FindAvailableCamera() { - int size = Mathf.Max(1, superSize); - string resolvedName = BuildFileName(fileName); - string folder = Path.Combine(Application.dataPath, ScreenshotsFolderName); - Directory.CreateDirectory(folder); - - string fullPath = Path.Combine(folder, resolvedName); - if (ensureUniqueFileName) + var main = Camera.main; + if (main != null) { - fullPath = EnsureUnique(fullPath); + return main; } - string normalizedFullPath = fullPath.Replace('\\', '/'); - - // Use only the file name to let Unity decide the final location (per CaptureScreenshot docs). - string captureName = Path.GetFileName(normalizedFullPath); - - // Use Asset folder for ScreenCapture.CaptureScreenshot to ensure write to asset rather than project root - string projectRoot = GetProjectRootPath(); - string assetsRelativePath = normalizedFullPath; - if (assetsRelativePath.StartsWith(projectRoot, StringComparison.OrdinalIgnoreCase)) + try { - assetsRelativePath = assetsRelativePath.Substring(projectRoot.Length).TrimStart('/'); + // Use FindObjectsOfType for Unity 2021 compatibility. + var cams = UnityEngine.Object.FindObjectsOfType(); + return cams.FirstOrDefault(); } + catch + { + return null; + } + } + public static ScreenshotCaptureResult CaptureToAssetsFolder(string fileName = null, int superSize = 1, bool ensureUniqueFileName = true) + { #if UNITY_2022_1_OR_NEWER - ScreenCapture.CaptureScreenshot(assetsRelativePath, size); + ScreenshotCaptureResult result = PrepareCaptureResult(fileName, superSize, ensureUniqueFileName, isAsync: true); + ScreenCapture.CaptureScreenshot(result.AssetsRelativePath, result.SuperSize); + return result; #else - Debug.LogWarning("ScreenCapture is supported after Unity 2022.1. Using main camera capture as fallback."); - CaptureFromCameraToAssetsFolder(Camera.main, captureName, size, false); -#endif - - return new ScreenshotCaptureResult( - normalizedFullPath, - assetsRelativePath, - size); + if (!s_loggedLegacyScreenCaptureFallback) + { + Debug.Log("ScreenCapture is supported after Unity 2022.1. Using camera capture as fallback."); + s_loggedLegacyScreenCaptureFallback = true; + } + + var cam = FindAvailableCamera(); + if (cam == null) + { + throw new InvalidOperationException("No camera found to capture screenshot."); + } + + return CaptureFromCameraToAssetsFolder(cam, fileName, superSize, ensureUniqueFileName); +#endif } /// @@ -73,18 +85,8 @@ public static ScreenshotCaptureResult CaptureFromCameraToAssetsFolder(Camera cam throw new ArgumentNullException(nameof(camera)); } - int size = Mathf.Max(1, superSize); - string resolvedName = BuildFileName(fileName); - string folder = Path.Combine(Application.dataPath, ScreenshotsFolderName); - Directory.CreateDirectory(folder); - - string fullPath = Path.Combine(folder, resolvedName); - if (ensureUniqueFileName) - { - fullPath = EnsureUnique(fullPath); - } - - string normalizedFullPath = fullPath.Replace('\\', '/'); + ScreenshotCaptureResult result = PrepareCaptureResult(fileName, superSize, ensureUniqueFileName, isAsync: false); + int size = result.SuperSize; int width = Mathf.Max(1, camera.pixelWidth > 0 ? camera.pixelWidth : Screen.width); int height = Mathf.Max(1, camera.pixelHeight > 0 ? camera.pixelHeight : Screen.height); @@ -94,34 +96,69 @@ public static ScreenshotCaptureResult CaptureFromCameraToAssetsFolder(Camera cam RenderTexture prevRT = camera.targetTexture; RenderTexture prevActive = RenderTexture.active; var rt = RenderTexture.GetTemporary(width, height, 24, RenderTextureFormat.ARGB32); + Texture2D tex = null; try { camera.targetTexture = rt; camera.Render(); RenderTexture.active = rt; - var tex = new Texture2D(width, height, TextureFormat.RGBA32, false); + tex = new Texture2D(width, height, TextureFormat.RGBA32, false); tex.ReadPixels(new Rect(0, 0, width, height), 0, 0); tex.Apply(); byte[] png = tex.EncodeToPNG(); - File.WriteAllBytes(normalizedFullPath, png); + File.WriteAllBytes(result.FullPath, png); } finally { camera.targetTexture = prevRT; RenderTexture.active = prevActive; RenderTexture.ReleaseTemporary(rt); + if (tex != null) + { + if (Application.isPlaying) + { + UnityEngine.Object.Destroy(tex); + } + else + { + UnityEngine.Object.DestroyImmediate(tex); + } + } } + return result; + } + + private static ScreenshotCaptureResult PrepareCaptureResult(string fileName, int superSize, bool ensureUniqueFileName, bool isAsync) + { + int size = Mathf.Max(1, superSize); + string resolvedName = BuildFileName(fileName); + string folder = Path.Combine(Application.dataPath, ScreenshotsFolderName); + Directory.CreateDirectory(folder); + + string fullPath = Path.Combine(folder, resolvedName); + if (ensureUniqueFileName) + { + fullPath = EnsureUnique(fullPath); + } + + string normalizedFullPath = fullPath.Replace('\\', '/'); + string assetsRelativePath = ToAssetsRelativePath(normalizedFullPath); + + return new ScreenshotCaptureResult(normalizedFullPath, assetsRelativePath, size, isAsync); + } + + private static string ToAssetsRelativePath(string normalizedFullPath) + { string projectRoot = GetProjectRootPath(); string assetsRelativePath = normalizedFullPath; if (assetsRelativePath.StartsWith(projectRoot, StringComparison.OrdinalIgnoreCase)) { assetsRelativePath = assetsRelativePath.Substring(projectRoot.Length).TrimStart('/'); } - - return new ScreenshotCaptureResult(normalizedFullPath, assetsRelativePath, size); + return assetsRelativePath; } private static string BuildFileName(string fileName) diff --git a/MCPForUnity/package.json b/MCPForUnity/package.json index cee80952d..6bf9787a6 100644 --- a/MCPForUnity/package.json +++ b/MCPForUnity/package.json @@ -1,6 +1,6 @@ { "name": "com.coplaydev.unity-mcp", - "version": "9.1.0", + "version": "9.2.0", "displayName": "MCP for Unity", "description": "A bridge that connects AI assistants to Unity via the MCP (Model Context Protocol). Allows AI clients like Claude Code, Cursor, and VSCode to directly control your Unity Editor for enhanced development workflows.\n\nFeatures automated setup wizard, cross-platform support, and seamless integration with popular AI development tools.\n\nJoin Our Discord: https://discord.gg/y4p8KfzrN4", "unity": "2021.3", diff --git a/Server/README.md b/Server/README.md index 075aceb9f..68855400b 100644 --- a/Server/README.md +++ b/Server/README.md @@ -69,7 +69,7 @@ Use this to run the latest released version from the repository. Change the vers "command": "uvx", "args": [ "--from", - "git+https://github.com/CoplayDev/unity-mcp@v9.1.0#subdirectory=Server", + "git+https://github.com/CoplayDev/unity-mcp@v9.2.0#subdirectory=Server", "mcp-for-unity", "--transport", "stdio" diff --git a/Server/pyproject.toml b/Server/pyproject.toml index b3ffa6dde..fe3d56927 100644 --- a/Server/pyproject.toml +++ b/Server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mcpforunityserver" -version = "9.1.0" +version = "9.2.0" description = "MCP for Unity Server: A Unity package for Unity Editor integration via the Model Context Protocol (MCP)." readme = "README.md" license = "MIT" diff --git a/docs/development/README-DEV-zh.md b/docs/development/README-DEV-zh.md index c79ba5566..375c0bda6 100644 --- a/docs/development/README-DEV-zh.md +++ b/docs/development/README-DEV-zh.md @@ -208,6 +208,12 @@ X:\UnityProject\Library\PackageCache\com.coplaydev.unity-mcp@272123cfd97e - **`max_nodes`**:默认 **1000**,限制 **1..5000** - **`include_transform`**:默认 **false** +### `manage_scene(action="screenshot")` + +- 将 PNG 保存到 `Assets/Screenshots/`。 +- Unity **2022.1+**:通过 `ScreenCapture.CaptureScreenshot` 捕获 **Game View**,因此包含 `Screen Space - Overlay` UI。注意该写入是 **异步** 的,文件/导入可能会稍后出现。 +- Unity **2021.3**:回退为将可用的 `Camera` 渲染到 `RenderTexture`(仅相机输出;不包含 `Screen Space - Overlay` UI)。 + ### `manage_gameobject(action="get_components")` - **默认行为**:仅返回 **分页的组件元数据**(`typeName`, `instanceID`)。 diff --git a/docs/development/README-DEV.md b/docs/development/README-DEV.md index 077294780..b2e217070 100644 --- a/docs/development/README-DEV.md +++ b/docs/development/README-DEV.md @@ -199,6 +199,12 @@ Some Unity tool calls can return *very large* JSON payloads (deep hierarchies, c - **`max_nodes`**: defaults to **1000**, clamped to **1..5000** - **`include_transform`**: defaults to **false** +### `manage_scene(action="screenshot")` + +- Saves PNGs under `Assets/Screenshots/`. +- Unity **2022.1+**: captures the **Game View** via `ScreenCapture.CaptureScreenshot`, so `Screen Space - Overlay` UI is included. This write is **async**, so the file may appear/import a moment later. +- Unity **2021.3**: falls back to rendering the best available `Camera` into a `RenderTexture` (camera output only; `Screen Space - Overlay` UI is not included). + ### `manage_gameobject(action="get_components")` - **Default behavior**: returns **paged component metadata** only (`typeName`, `instanceID`). @@ -391,4 +397,4 @@ Tests that trigger script compilation mid-run (e.g., `DomainReloadResilienceTest - Run them first in the test suite (before backgrounding Unity) - Use the `[Explicit]` attribute to exclude them from default runs -**Note:** The MCP workflow itself is unaffected—socket messages provide external stimulus that keeps Unity responsive even when backgrounded. This limitation only affects Unity's internal test coroutine waits. \ No newline at end of file +**Note:** The MCP workflow itself is unaffected—socket messages provide external stimulus that keeps Unity responsive even when backgrounded. This limitation only affects Unity's internal test coroutine waits. diff --git a/manifest.json b/manifest.json index fac58611b..d712c0a86 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": "0.3", "name": "Unity MCP", - "version": "9.1.0", + "version": "9.2.0", "description": "AI-powered Unity Editor automation via MCP - manage GameObjects, scripts, materials, scenes, prefabs, VFX, and run tests", "author": { "name": "Coplay", @@ -20,39 +20,130 @@ "entry_point": "Server/src/main.py", "mcp_config": { "command": "uvx", - "args": ["--from", "mcpforunityserver", "mcp-for-unity"], + "args": [ + "--from", + "mcpforunityserver", + "mcp-for-unity" + ], "env": {} } }, "tools": [ - {"name": "batch_execute", "description": "Execute multiple Unity operations in a single batch"}, - {"name": "debug_request_context", "description": "Debug and inspect MCP request context"}, - {"name": "execute_custom_tool", "description": "Execute custom Unity Editor tools registered by the project"}, - {"name": "execute_menu_item", "description": "Execute Unity Editor menu items"}, - {"name": "find_gameobjects", "description": "Find GameObjects in the scene by various criteria"}, - {"name": "find_in_file", "description": "Search for content within Unity project files"}, - {"name": "manage_asset", "description": "Create, modify, search, and organize Unity assets"}, - {"name": "manage_components", "description": "Add, remove, and configure GameObject components"}, - {"name": "manage_editor", "description": "Control Unity Editor state, play mode, and preferences"}, - {"name": "manage_gameobject", "description": "Create, modify, transform, and delete GameObjects"}, - {"name": "manage_material", "description": "Create and modify Unity materials and shaders"}, - {"name": "manage_prefabs", "description": "Create, instantiate, unpack, and modify prefabs"}, - {"name": "manage_scene", "description": "Load, save, query hierarchy, and manage Unity scenes"}, - {"name": "manage_script", "description": "Create, read, and modify C# scripts"}, - {"name": "manage_scriptable_object", "description": "Create and modify ScriptableObjects"}, - {"name": "manage_shader", "description": "Work with Unity shaders"}, - {"name": "manage_vfx", "description": "Manage Visual Effects, particle systems, and trails"}, - {"name": "read_console", "description": "Read Unity Editor console output (logs, warnings, errors)"}, - {"name": "refresh_unity", "description": "Refresh Unity Editor asset database"}, - {"name": "run_tests", "description": "Run Unity Test Framework tests"}, - {"name": "get_test_job", "description": "Get status of async test job"}, - {"name": "script_apply_edits", "description": "Apply code edits to C# scripts with validation"}, - {"name": "set_active_instance", "description": "Set the active Unity Editor instance for multi-instance workflows"}, - {"name": "apply_text_edits", "description": "Apply text edits to script content"}, - {"name": "create_script", "description": "Create new C# scripts"}, - {"name": "delete_script", "description": "Delete C# scripts"}, - {"name": "validate_script", "description": "Validate C# script syntax and compilation"}, - {"name": "manage_script_capabilities", "description": "Query script management capabilities"}, - {"name": "get_sha", "description": "Get SHA hash of script content"} + { + "name": "batch_execute", + "description": "Execute multiple Unity operations in a single batch" + }, + { + "name": "debug_request_context", + "description": "Debug and inspect MCP request context" + }, + { + "name": "execute_custom_tool", + "description": "Execute custom Unity Editor tools registered by the project" + }, + { + "name": "execute_menu_item", + "description": "Execute Unity Editor menu items" + }, + { + "name": "find_gameobjects", + "description": "Find GameObjects in the scene by various criteria" + }, + { + "name": "find_in_file", + "description": "Search for content within Unity project files" + }, + { + "name": "manage_asset", + "description": "Create, modify, search, and organize Unity assets" + }, + { + "name": "manage_components", + "description": "Add, remove, and configure GameObject components" + }, + { + "name": "manage_editor", + "description": "Control Unity Editor state, play mode, and preferences" + }, + { + "name": "manage_gameobject", + "description": "Create, modify, transform, and delete GameObjects" + }, + { + "name": "manage_material", + "description": "Create and modify Unity materials and shaders" + }, + { + "name": "manage_prefabs", + "description": "Create, instantiate, unpack, and modify prefabs" + }, + { + "name": "manage_scene", + "description": "Load, save, query hierarchy, and manage Unity scenes" + }, + { + "name": "manage_script", + "description": "Create, read, and modify C# scripts" + }, + { + "name": "manage_scriptable_object", + "description": "Create and modify ScriptableObjects" + }, + { + "name": "manage_shader", + "description": "Work with Unity shaders" + }, + { + "name": "manage_vfx", + "description": "Manage Visual Effects, particle systems, and trails" + }, + { + "name": "read_console", + "description": "Read Unity Editor console output (logs, warnings, errors)" + }, + { + "name": "refresh_unity", + "description": "Refresh Unity Editor asset database" + }, + { + "name": "run_tests", + "description": "Run Unity Test Framework tests" + }, + { + "name": "get_test_job", + "description": "Get status of async test job" + }, + { + "name": "script_apply_edits", + "description": "Apply code edits to C# scripts with validation" + }, + { + "name": "set_active_instance", + "description": "Set the active Unity Editor instance for multi-instance workflows" + }, + { + "name": "apply_text_edits", + "description": "Apply text edits to script content" + }, + { + "name": "create_script", + "description": "Create new C# scripts" + }, + { + "name": "delete_script", + "description": "Delete C# scripts" + }, + { + "name": "validate_script", + "description": "Validate C# script syntax and compilation" + }, + { + "name": "manage_script_capabilities", + "description": "Query script management capabilities" + }, + { + "name": "get_sha", + "description": "Get SHA hash of script content" + } ] }