using System;
using System.Reflection;
using McpUnity.Unity;
using McpUnity.Utils;
using UnityEngine;
using UnityEditor;
using Newtonsoft.Json.Linq;
namespace McpUnity.Tools
{
/// <summary>
/// Tool for updating component data in the Unity Editor
/// </summary>
public class UpdateComponentTool : McpToolBase
{
public UpdateComponentTool()
{
Name = "update_component";
Description = "Updates component fields on a GameObject or adds it to the GameObject if it does not contain the component";
}
/// <summary>
/// Execute the UpdateComponent tool with the provided parameters synchronously
/// </summary>
/// <param name="parameters">Tool parameters as a JObject</param>
public override JObject Execute(JObject parameters)
{
// Extract parameters
int? instanceId = parameters["instanceId"]?.ToObject<int?>();
string objectPath = parameters["objectPath"]?.ToObject<string>();
string componentName = parameters["componentName"]?.ToObject<string>();
JObject componentData = parameters["componentData"] as JObject;
// Validate parameters - require either instanceId or objectPath
if (!instanceId.HasValue && string.IsNullOrEmpty(objectPath))
{
return McpUnitySocketHandler.CreateErrorResponse(
"Either 'instanceId' or 'objectPath' must be provided",
"validation_error"
);
}
if (string.IsNullOrEmpty(componentName))
{
return McpUnitySocketHandler.CreateErrorResponse(
"Required parameter 'componentName' not provided",
"validation_error"
);
}
// Find the GameObject by instance ID or path
GameObject gameObject = null;
string identifier = "unknown";
if (instanceId.HasValue)
{
gameObject = EditorUtility.InstanceIDToObject(instanceId.Value) as GameObject;
identifier = $"ID {instanceId.Value}";
}
else
{
// Find by path
gameObject = GameObject.Find(objectPath);
identifier = $"path '{objectPath}'";
if (gameObject == null)
{
// Try to find using the Unity Scene hierarchy path
gameObject = FindGameObjectByPath(objectPath);
}
}
if (gameObject == null)
{
return McpUnitySocketHandler.CreateErrorResponse(
$"GameObject with path '{objectPath}' or instance ID {instanceId} not found",
"not_found_error"
);
}
McpLogger.LogInfo($"[MCP Unity] Updating component '{componentName}' on GameObject '{gameObject.name}' (found by {identifier})");
// Try to find the component by name
Component component = gameObject.GetComponent(componentName);
// If component not found, try to add it
if (component == null)
{
Type componentType = FindComponentType(componentName);
if (componentType == null)
{
return McpUnitySocketHandler.CreateErrorResponse(
$"Component type '{componentName}' not found in Unity",
"component_error"
);
}
component = Undo.AddComponent(gameObject, componentType);
// Ensure changes are saved
EditorUtility.SetDirty(gameObject);
if (PrefabUtility.IsPartOfAnyPrefab(gameObject))
{
PrefabUtility.RecordPrefabInstancePropertyModifications(component);
}
McpLogger.LogInfo($"[MCP Unity] Added component '{componentName}' to GameObject '{gameObject.name}'");
}
// Update component fields
if (componentData != null && componentData.Count > 0)
{
bool success = UpdateComponentData(component, componentData, out string errorMessage);
// If update failed, return error
if (!success)
{
return McpUnitySocketHandler.CreateErrorResponse(errorMessage, "update_error");
}
// Ensure field changes are saved
EditorUtility.SetDirty(gameObject);
if (PrefabUtility.IsPartOfAnyPrefab(gameObject))
{
PrefabUtility.RecordPrefabInstancePropertyModifications(component);
}
}
// Create the response
return new JObject
{
["success"] = true,
["type"] = "text",
["message"] = $"Successfully updated component '{componentName}' on GameObject '{gameObject.name}'"
};
}
/// <summary>
/// Find a GameObject by its hierarchy path
/// </summary>
/// <param name="path">The path to the GameObject (e.g. "Canvas/Panel/Button")</param>
/// <returns>The GameObject if found, null otherwise</returns>
private GameObject FindGameObjectByPath(string path)
{
// Split the path by '/'
string[] pathParts = path.Split('/');
GameObject[] rootGameObjects = UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects();
// If the path is empty, return null
if (pathParts.Length == 0)
{
return null;
}
// Search through all root GameObjects in all scenes
foreach (GameObject rootObj in rootGameObjects)
{
if (rootObj.name == pathParts[0])
{
// Found the root object, now traverse down the path
GameObject current = rootObj;
// Start from index 1 since we've already matched the root
for (int i = 1; i < pathParts.Length; i++)
{
Transform child = current.transform.Find(pathParts[i]);
if (child == null)
{
// Path segment not found
return null;
}
// Move to the next level
current = child.gameObject;
}
// If we got here, we found the full path
return current;
}
}
// Not found
return null;
}
/// <summary>
/// Find a component type by name
/// </summary>
/// <param name="componentName">The name of the component type</param>
/// <returns>The component type, or null if not found</returns>
private Type FindComponentType(string componentName)
{
// First try direct match
Type type = Type.GetType(componentName);
if (type != null && typeof(Component).IsAssignableFrom(type))
{
return type;
}
// Try common Unity namespaces
string[] commonNamespaces = new string[]
{
"UnityEngine",
"UnityEngine.UI",
"UnityEngine.EventSystems",
"UnityEngine.Animations",
"UnityEngine.Rendering",
"TMPro"
};
foreach (string ns in commonNamespaces)
{
type = Type.GetType($"{ns}.{componentName}, UnityEngine");
if (type != null && typeof(Component).IsAssignableFrom(type))
{
return type;
}
}
// Try assemblies search
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
foreach (Type t in assembly.GetTypes())
{
if (t.Name == componentName && typeof(Component).IsAssignableFrom(t))
{
return t;
}
}
}
catch (Exception)
{
// Some assemblies might throw exceptions when getting types
continue;
}
}
return null;
}
/// <summary>
/// Update component data based on the provided JObject
/// </summary>
/// <param name="component">The component to update</param>
/// <param name="componentData">The data to apply to the component</param>
/// <returns>True if the component was updated successfully</returns>
private bool UpdateComponentData(Component component, JObject componentData, out string errorMessage)
{
errorMessage = "";
if (component == null || componentData == null)
{
errorMessage = "Component or component data is null";
return false;
}
Type componentType = component.GetType();
bool fullSuccess = true;
// Record object for undo
Undo.RecordObject(component, $"Update {componentType.Name} fields");
// Process each field or property in the component data
foreach (var property in componentData.Properties())
{
string fieldName = property.Name;
JToken fieldValue = property.Value;
// Skip null values
if (string.IsNullOrEmpty(fieldName) || fieldValue.Type == JTokenType.Null)
{
continue;
}
// Try to update field
FieldInfo fieldInfo = componentType.GetField(fieldName,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
object value = ConvertJTokenToValue(fieldValue, fieldInfo.FieldType);
fieldInfo.SetValue(component, value);
continue;
}
// Try to update property if not found as a field
PropertyInfo propertyInfo = componentType.GetProperty(fieldName,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (propertyInfo != null)
{
object value = ConvertJTokenToValue(fieldValue, propertyInfo.PropertyType);
propertyInfo.SetValue(component, value);
continue;
}
fullSuccess = false;
errorMessage = $"Field or Property with name '{fieldName}' not found on component '{componentType.Name}'";
}
return fullSuccess;
}
/// <summary>
/// Convert a JToken to a value of the specified type
/// </summary>
/// <param name="token">The JToken to convert</param>
/// <param name="targetType">The target type to convert to</param>
/// <returns>The converted value</returns>
private object ConvertJTokenToValue(JToken token, Type targetType)
{
if (token == null)
{
return null;
}
// Handle Unity Vector types
if (targetType == typeof(Vector2) && token.Type == JTokenType.Object)
{
JObject vector = (JObject)token;
return new Vector2(
vector["x"]?.ToObject<float>() ?? 0f,
vector["y"]?.ToObject<float>() ?? 0f
);
}
if (targetType == typeof(Vector3) && token.Type == JTokenType.Object)
{
JObject vector = (JObject)token;
return new Vector3(
vector["x"]?.ToObject<float>() ?? 0f,
vector["y"]?.ToObject<float>() ?? 0f,
vector["z"]?.ToObject<float>() ?? 0f
);
}
if (targetType == typeof(Vector4) && token.Type == JTokenType.Object)
{
JObject vector = (JObject)token;
return new Vector4(
vector["x"]?.ToObject<float>() ?? 0f,
vector["y"]?.ToObject<float>() ?? 0f,
vector["z"]?.ToObject<float>() ?? 0f,
vector["w"]?.ToObject<float>() ?? 0f
);
}
if (targetType == typeof(Quaternion) && token.Type == JTokenType.Object)
{
JObject quaternion = (JObject)token;
return new Quaternion(
quaternion["x"]?.ToObject<float>() ?? 0f,
quaternion["y"]?.ToObject<float>() ?? 0f,
quaternion["z"]?.ToObject<float>() ?? 0f,
quaternion["w"]?.ToObject<float>() ?? 1f
);
}
if (targetType == typeof(Color) && token.Type == JTokenType.Object)
{
JObject color = (JObject)token;
return new Color(
color["r"]?.ToObject<float>() ?? 0f,
color["g"]?.ToObject<float>() ?? 0f,
color["b"]?.ToObject<float>() ?? 0f,
color["a"]?.ToObject<float>() ?? 1f
);
}
if (targetType == typeof(Bounds) && token.Type == JTokenType.Object)
{
JObject bounds = (JObject)token;
Vector3 center = bounds["center"]?.ToObject<Vector3>() ?? Vector3.zero;
Vector3 size = bounds["size"]?.ToObject<Vector3>() ?? Vector3.one;
return new Bounds(center, size);
}
if (targetType == typeof(Rect) && token.Type == JTokenType.Object)
{
JObject rect = (JObject)token;
return new Rect(
rect["x"]?.ToObject<float>() ?? 0f,
rect["y"]?.ToObject<float>() ?? 0f,
rect["width"]?.ToObject<float>() ?? 0f,
rect["height"]?.ToObject<float>() ?? 0f
);
}
// Handle UnityEngine.Object types;
if (targetType == typeof(UnityEngine.Object))
{
return token.ToObject<UnityEngine.Object>();
}
// Handle enum types
if (targetType.IsEnum)
{
// If JToken is a string, try to parse as enum name
if (token.Type == JTokenType.String)
{
string enumName = token.ToObject<string>();
if (Enum.TryParse(targetType, enumName, true, out object result))
{
return result;
}
// If parsing fails, try to convert numeric value
if (int.TryParse(enumName, out int enumValue))
{
return Enum.ToObject(targetType, enumValue);
}
}
// If JToken is a number, convert directly to enum
else if (token.Type == JTokenType.Integer)
{
return Enum.ToObject(targetType, token.ToObject<int>());
}
}
// For other types, use JToken's ToObject method
try
{
return token.ToObject(targetType);
}
catch (Exception ex)
{
McpLogger.LogError($"[MCP Unity] Error converting value to type {targetType.Name}: {ex.Message}");
return null;
}
}
}
}