Skip to main content
Glama
ManageEditor.cs16 kB
using System; using MCPForUnity.Editor.Helpers; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEditorInternal; // Required for tag management namespace MCPForUnity.Editor.Tools { /// <summary> /// Handles editor control actions including play mode control, tool selection, /// and tag/layer management. For reading editor state, use MCP resources instead. /// </summary> [McpForUnityTool("manage_editor", AutoRegister = false)] public static class ManageEditor { // Constant for starting user layer index private const int FirstUserLayerIndex = 8; // Constant for total layer count private const int TotalLayerCount = 32; /// <summary> /// Main handler for editor management actions. /// </summary> public static object HandleCommand(JObject @params) { string action = @params["action"]?.ToString().ToLower(); // Parameters for specific actions string tagName = @params["tagName"]?.ToString(); string layerName = @params["layerName"]?.ToString(); bool waitForCompletion = @params["waitForCompletion"]?.ToObject<bool>() ?? false; // Example - not used everywhere if (string.IsNullOrEmpty(action)) { return new ErrorResponse("Action parameter is required."); } // Route action switch (action) { // Play Mode Control case "play": try { if (!EditorApplication.isPlaying) { EditorApplication.isPlaying = true; return new SuccessResponse("Entered play mode."); } return new SuccessResponse("Already in play mode."); } catch (Exception e) { return new ErrorResponse($"Error entering play mode: {e.Message}"); } case "pause": try { if (EditorApplication.isPlaying) { EditorApplication.isPaused = !EditorApplication.isPaused; return new SuccessResponse( EditorApplication.isPaused ? "Game paused." : "Game resumed." ); } return new ErrorResponse("Cannot pause/resume: Not in play mode."); } catch (Exception e) { return new ErrorResponse($"Error pausing/resuming game: {e.Message}"); } case "stop": try { if (EditorApplication.isPlaying) { EditorApplication.isPlaying = false; return new SuccessResponse("Exited play mode."); } return new SuccessResponse("Already stopped (not in play mode)."); } catch (Exception e) { return new ErrorResponse($"Error stopping play mode: {e.Message}"); } // Tool Control case "set_active_tool": string toolName = @params["toolName"]?.ToString(); if (string.IsNullOrEmpty(toolName)) return new ErrorResponse("'toolName' parameter required for set_active_tool."); return SetActiveTool(toolName); // Tag Management case "add_tag": if (string.IsNullOrEmpty(tagName)) return new ErrorResponse("'tagName' parameter required for add_tag."); return AddTag(tagName); case "remove_tag": if (string.IsNullOrEmpty(tagName)) return new ErrorResponse("'tagName' parameter required for remove_tag."); return RemoveTag(tagName); // Layer Management case "add_layer": if (string.IsNullOrEmpty(layerName)) return new ErrorResponse("'layerName' parameter required for add_layer."); return AddLayer(layerName); case "remove_layer": if (string.IsNullOrEmpty(layerName)) return new ErrorResponse("'layerName' parameter required for remove_layer."); return RemoveLayer(layerName); // --- Settings (Example) --- // case "set_resolution": // int? width = @params["width"]?.ToObject<int?>(); // int? height = @params["height"]?.ToObject<int?>(); // if (!width.HasValue || !height.HasValue) return new ErrorResponse("'width' and 'height' parameters required."); // return SetGameViewResolution(width.Value, height.Value); // case "set_quality": // // Handle string name or int index // return SetQualityLevel(@params["qualityLevel"]); default: return new ErrorResponse( $"Unknown action: '{action}'. Supported actions: play, pause, stop, set_active_tool, add_tag, remove_tag, add_layer, remove_layer. Use MCP resources for reading editor state, project info, tags, layers, selection, windows, prefab stage, and active tool." ); } } // --- Tool Control Methods --- private static object SetActiveTool(string toolName) { try { Tool targetTool; if (Enum.TryParse<Tool>(toolName, true, out targetTool)) // Case-insensitive parse { // Check if it's a valid built-in tool if (targetTool != Tool.None && targetTool <= Tool.Custom) // Tool.Custom is the last standard tool { UnityEditor.Tools.current = targetTool; return new SuccessResponse($"Set active tool to '{targetTool}'."); } else { return new ErrorResponse( $"Cannot directly set tool to '{toolName}'. It might be None, Custom, or invalid." ); } } else { // Potentially try activating a custom tool by name here if needed // This often requires specific editor scripting knowledge for that tool. return new ErrorResponse( $"Could not parse '{toolName}' as a standard Unity Tool (View, Move, Rotate, Scale, Rect, Transform, Custom)." ); } } catch (Exception e) { return new ErrorResponse($"Error setting active tool: {e.Message}"); } } // --- Tag Management Methods --- private static object AddTag(string tagName) { if (string.IsNullOrWhiteSpace(tagName)) return new ErrorResponse("Tag name cannot be empty or whitespace."); // Check if tag already exists if (System.Linq.Enumerable.Contains(InternalEditorUtility.tags, tagName)) { return new ErrorResponse($"Tag '{tagName}' already exists."); } try { // Add the tag using the internal utility InternalEditorUtility.AddTag(tagName); // Force save assets to ensure the change persists in the TagManager asset AssetDatabase.SaveAssets(); return new SuccessResponse($"Tag '{tagName}' added successfully."); } catch (Exception e) { return new ErrorResponse($"Failed to add tag '{tagName}': {e.Message}"); } } private static object RemoveTag(string tagName) { if (string.IsNullOrWhiteSpace(tagName)) return new ErrorResponse("Tag name cannot be empty or whitespace."); if (tagName.Equals("Untagged", StringComparison.OrdinalIgnoreCase)) return new ErrorResponse("Cannot remove the built-in 'Untagged' tag."); // Check if tag exists before attempting removal if (!System.Linq.Enumerable.Contains(InternalEditorUtility.tags, tagName)) { return new ErrorResponse($"Tag '{tagName}' does not exist."); } try { // Remove the tag using the internal utility InternalEditorUtility.RemoveTag(tagName); // Force save assets AssetDatabase.SaveAssets(); return new SuccessResponse($"Tag '{tagName}' removed successfully."); } catch (Exception e) { // Catch potential issues if the tag is somehow in use or removal fails return new ErrorResponse($"Failed to remove tag '{tagName}': {e.Message}"); } } // --- Layer Management Methods --- private static object AddLayer(string layerName) { if (string.IsNullOrWhiteSpace(layerName)) return new ErrorResponse("Layer name cannot be empty or whitespace."); // Access the TagManager asset SerializedObject tagManager = GetTagManager(); if (tagManager == null) return new ErrorResponse("Could not access TagManager asset."); SerializedProperty layersProp = tagManager.FindProperty("layers"); if (layersProp == null || !layersProp.isArray) return new ErrorResponse("Could not find 'layers' property in TagManager."); // Check if layer name already exists (case-insensitive check recommended) for (int i = 0; i < TotalLayerCount; i++) { SerializedProperty layerSP = layersProp.GetArrayElementAtIndex(i); if ( layerSP != null && layerName.Equals(layerSP.stringValue, StringComparison.OrdinalIgnoreCase) ) { return new ErrorResponse($"Layer '{layerName}' already exists at index {i}."); } } // Find the first empty user layer slot (indices 8 to 31) int firstEmptyUserLayer = -1; for (int i = FirstUserLayerIndex; i < TotalLayerCount; i++) { SerializedProperty layerSP = layersProp.GetArrayElementAtIndex(i); if (layerSP != null && string.IsNullOrEmpty(layerSP.stringValue)) { firstEmptyUserLayer = i; break; } } if (firstEmptyUserLayer == -1) { return new ErrorResponse("No empty User Layer slots available (8-31 are full)."); } // Assign the name to the found slot try { SerializedProperty targetLayerSP = layersProp.GetArrayElementAtIndex( firstEmptyUserLayer ); targetLayerSP.stringValue = layerName; // Apply the changes to the TagManager asset tagManager.ApplyModifiedProperties(); // Save assets to make sure it's written to disk AssetDatabase.SaveAssets(); return new SuccessResponse( $"Layer '{layerName}' added successfully to slot {firstEmptyUserLayer}." ); } catch (Exception e) { return new ErrorResponse($"Failed to add layer '{layerName}': {e.Message}"); } } private static object RemoveLayer(string layerName) { if (string.IsNullOrWhiteSpace(layerName)) return new ErrorResponse("Layer name cannot be empty or whitespace."); // Access the TagManager asset SerializedObject tagManager = GetTagManager(); if (tagManager == null) return new ErrorResponse("Could not access TagManager asset."); SerializedProperty layersProp = tagManager.FindProperty("layers"); if (layersProp == null || !layersProp.isArray) return new ErrorResponse("Could not find 'layers' property in TagManager."); // Find the layer by name (must be user layer) int layerIndexToRemove = -1; for (int i = FirstUserLayerIndex; i < TotalLayerCount; i++) // Start from user layers { SerializedProperty layerSP = layersProp.GetArrayElementAtIndex(i); // Case-insensitive comparison is safer if ( layerSP != null && layerName.Equals(layerSP.stringValue, StringComparison.OrdinalIgnoreCase) ) { layerIndexToRemove = i; break; } } if (layerIndexToRemove == -1) { return new ErrorResponse($"User layer '{layerName}' not found."); } // Clear the name for that index try { SerializedProperty targetLayerSP = layersProp.GetArrayElementAtIndex( layerIndexToRemove ); targetLayerSP.stringValue = string.Empty; // Set to empty string to remove // Apply the changes tagManager.ApplyModifiedProperties(); // Save assets AssetDatabase.SaveAssets(); return new SuccessResponse( $"Layer '{layerName}' (slot {layerIndexToRemove}) removed successfully." ); } catch (Exception e) { return new ErrorResponse($"Failed to remove layer '{layerName}': {e.Message}"); } } // --- Helper Methods --- /// <summary> /// Gets the SerializedObject for the TagManager asset. /// </summary> private static SerializedObject GetTagManager() { try { // Load the TagManager asset from the ProjectSettings folder UnityEngine.Object[] tagManagerAssets = AssetDatabase.LoadAllAssetsAtPath( "ProjectSettings/TagManager.asset" ); if (tagManagerAssets == null || tagManagerAssets.Length == 0) { McpLog.Error("[ManageEditor] TagManager.asset not found in ProjectSettings."); return null; } // The first object in the asset file should be the TagManager return new SerializedObject(tagManagerAssets[0]); } catch (Exception e) { McpLog.Error($"[ManageEditor] Error accessing TagManager.asset: {e.Message}"); return null; } } // --- Example Implementations for Settings --- /* private static object SetGameViewResolution(int width, int height) { ... } private static object SetQualityLevel(JToken qualityLevelToken) { ... } */ } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/CoplayDev/unity-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server