using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using MCPForUnity.Editor.Helpers; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEngine; namespace MCPForUnity.Editor.Tools { /// /// Tool for managing components on GameObjects. /// Actions: add, remove, set_property /// /// This is a focused tool for component lifecycle operations. /// For reading component data, use the unity://scene/gameobject/{id}/components resource. /// [McpForUnityTool("manage_components")] public static class ManageComponents { /// /// Handles the manage_components command. /// /// Command parameters /// Result of the component operation public static object HandleCommand(JObject @params) { if (@params == null) { return new ErrorResponse("Parameters cannot be null."); } string action = ParamCoercion.CoerceString(@params["action"], null)?.ToLowerInvariant(); if (string.IsNullOrEmpty(action)) { return new ErrorResponse("'action' parameter is required (add, remove, set_property)."); } // Target resolution JToken targetToken = @params["target"]; string searchMethod = ParamCoercion.CoerceString(@params["searchMethod"] ?? @params["search_method"], null); if (targetToken == null) { return new ErrorResponse("'target' parameter is required."); } try { return action switch { "add" => AddComponent(@params, targetToken, searchMethod), "remove" => RemoveComponent(@params, targetToken, searchMethod), "set_property" => SetProperty(@params, targetToken, searchMethod), _ => new ErrorResponse($"Unknown action: '{action}'. Supported actions: add, remove, set_property") }; } catch (Exception e) { Debug.LogError($"[ManageComponents] Action '{action}' failed: {e}"); return new ErrorResponse($"Internal error processing action '{action}': {e.Message}"); } } #region Action Implementations private static object AddComponent(JObject @params, JToken targetToken, string searchMethod) { GameObject targetGo = FindTarget(targetToken, searchMethod); if (targetGo == null) { return new ErrorResponse($"Target GameObject ('{targetToken}') not found using method '{searchMethod ?? "default"}'."); } string componentTypeName = ParamCoercion.CoerceString(@params["componentType"] ?? @params["component_type"], null); if (string.IsNullOrEmpty(componentTypeName)) { return new ErrorResponse("'componentType' parameter is required for 'add' action."); } // Resolve component type using unified type resolver Type type = UnityTypeResolver.ResolveComponent(componentTypeName); if (type == null) { return new ErrorResponse($"Component type '{componentTypeName}' not found. Use a fully-qualified name if needed."); } // Use ComponentOps for the actual operation Component newComponent = ComponentOps.AddComponent(targetGo, type, out string error); if (newComponent == null) { return new ErrorResponse(error ?? $"Failed to add component '{componentTypeName}'."); } // Set properties if provided JObject properties = @params["properties"] as JObject ?? @params["componentProperties"] as JObject; if (properties != null && properties.HasValues) { // Record for undo before modifying properties Undo.RecordObject(newComponent, "Modify Component Properties"); SetPropertiesOnComponent(newComponent, properties); } EditorUtility.SetDirty(targetGo); return new { success = true, message = $"Component '{componentTypeName}' added to '{targetGo.name}'.", data = new { instanceID = targetGo.GetInstanceID(), componentType = type.FullName, componentInstanceID = newComponent.GetInstanceID() } }; } private static object RemoveComponent(JObject @params, JToken targetToken, string searchMethod) { GameObject targetGo = FindTarget(targetToken, searchMethod); if (targetGo == null) { return new ErrorResponse($"Target GameObject ('{targetToken}') not found using method '{searchMethod ?? "default"}'."); } string componentTypeName = ParamCoercion.CoerceString(@params["componentType"] ?? @params["component_type"], null); if (string.IsNullOrEmpty(componentTypeName)) { return new ErrorResponse("'componentType' parameter is required for 'remove' action."); } // Resolve component type using unified type resolver Type type = UnityTypeResolver.ResolveComponent(componentTypeName); if (type == null) { return new ErrorResponse($"Component type '{componentTypeName}' not found."); } // Use ComponentOps for the actual operation bool removed = ComponentOps.RemoveComponent(targetGo, type, out string error); if (!removed) { return new ErrorResponse(error ?? $"Failed to remove component '{componentTypeName}'."); } EditorUtility.SetDirty(targetGo); return new { success = true, message = $"Component '{componentTypeName}' removed from '{targetGo.name}'.", data = new { instanceID = targetGo.GetInstanceID() } }; } private static object SetProperty(JObject @params, JToken targetToken, string searchMethod) { GameObject targetGo = FindTarget(targetToken, searchMethod); if (targetGo == null) { return new ErrorResponse($"Target GameObject ('{targetToken}') not found using method '{searchMethod ?? "default"}'."); } string componentType = ParamCoercion.CoerceString(@params["componentType"] ?? @params["component_type"], null); if (string.IsNullOrEmpty(componentType)) { return new ErrorResponse("'componentType' parameter is required for 'set_property' action."); } // Resolve component type using unified type resolver Type type = UnityTypeResolver.ResolveComponent(componentType); if (type == null) { return new ErrorResponse($"Component type '{componentType}' not found."); } Component component = targetGo.GetComponent(type); if (component == null) { return new ErrorResponse($"Component '{componentType}' not found on '{targetGo.name}'."); } // Get property and value string propertyName = ParamCoercion.CoerceString(@params["property"], null); JToken valueToken = @params["value"]; // Support both single property or properties object JObject properties = @params["properties"] as JObject; if (string.IsNullOrEmpty(propertyName) && (properties == null || !properties.HasValues)) { return new ErrorResponse("Either 'property'+'value' or 'properties' object is required for 'set_property' action."); } var errors = new List(); try { Undo.RecordObject(component, $"Set property on {componentType}"); if (!string.IsNullOrEmpty(propertyName) && valueToken != null) { // Single property mode var error = TrySetProperty(component, propertyName, valueToken); if (error != null) { errors.Add(error); } } if (properties != null && properties.HasValues) { // Multiple properties mode foreach (var prop in properties.Properties()) { var error = TrySetProperty(component, prop.Name, prop.Value); if (error != null) { errors.Add(error); } } } EditorUtility.SetDirty(component); if (errors.Count > 0) { return new { success = false, message = $"Some properties failed to set on '{componentType}'.", data = new { instanceID = targetGo.GetInstanceID(), errors = errors } }; } return new { success = true, message = $"Properties set on component '{componentType}' on '{targetGo.name}'.", data = new { instanceID = targetGo.GetInstanceID() } }; } catch (Exception e) { return new ErrorResponse($"Error setting properties on component '{componentType}': {e.Message}"); } } #endregion #region Helpers private static GameObject FindTarget(JToken targetToken, string searchMethod) { if (targetToken == null) return null; // Try instance ID first if (targetToken.Type == JTokenType.Integer) { int instanceId = targetToken.Value(); return GameObjectLookup.FindById(instanceId); } string targetStr = targetToken.ToString(); // Try parsing as instance ID if (int.TryParse(targetStr, out int parsedId)) { var byId = GameObjectLookup.FindById(parsedId); if (byId != null) return byId; } // Use GameObjectLookup for search return GameObjectLookup.FindByTarget(targetToken, searchMethod ?? "by_name", true); } private static void SetPropertiesOnComponent(Component component, JObject properties) { if (component == null || properties == null) return; var errors = new List(); foreach (var prop in properties.Properties()) { var error = TrySetProperty(component, prop.Name, prop.Value); if (error != null) errors.Add(error); } if (errors.Count > 0) { Debug.LogWarning($"[ManageComponents] Some properties failed to set on {component.GetType().Name}: {string.Join(", ", errors)}"); } } /// /// Attempts to set a property or field on a component. /// Delegates to ComponentOps.SetProperty for unified implementation. /// private static string TrySetProperty(Component component, string propertyName, JToken value) { if (component == null || string.IsNullOrEmpty(propertyName)) return "Invalid component or property name"; if (ComponentOps.SetProperty(component, propertyName, value, out string error)) { return null; // Success } Debug.LogWarning($"[ManageComponents] {error}"); return error; } #endregion } }