Skip to main content
Glama
SerializedObjectInspector.cs14.3 kB
using UnityEngine; #if UNITY_EDITOR using UnityEditor; using System.Collections.Generic; #endif namespace LocalMcp.UnityServer { #if UNITY_EDITOR /// <summary> /// Provides SerializedObject inspection capabilities for detecting missing references /// </summary> public static class SerializedObjectInspector { /// <summary> /// Inspects a component's SerializedObject and returns detailed property information /// including object references and missing reference detection /// </summary> public static string InspectSerializedObject(string paramsJson, object id) { try { string gameObjectPath = ExtractStringParam(paramsJson, "gameObjectPath"); string componentType = ExtractStringParam(paramsJson, "componentType"); if (string.IsNullOrEmpty(gameObjectPath) || string.IsNullOrEmpty(componentType)) { return JsonRpcResponseHelper.ErrorMessage("gameObjectPath and componentType are required", id); } // Find GameObject GameObject target = GameObject.Find(gameObjectPath); if (target == null) { return JsonRpcResponseHelper.ErrorMessage($"GameObject not found: {gameObjectPath}", id); } // Find component Component component = target.GetComponent(componentType); if (component == null) { return JsonRpcResponseHelper.ErrorMessage($"Component not found: {componentType} on {gameObjectPath}", id); } // Create SerializedObject SerializedObject serializedObject = new SerializedObject(component); SerializedProperty property = serializedObject.GetIterator(); var properties = new List<string>(); bool enterChildren = true; while (property.NextVisible(enterChildren)) { enterChildren = false; // Only enter children on first iteration var propertyInfo = new Dictionary<string, object> { { "path", property.propertyPath }, { "name", property.name }, { "displayName", property.displayName }, { "propertyType", property.propertyType.ToString() }, { "isArray", property.isArray }, { "arraySize", property.isArray ? property.arraySize : 0 }, { "depth", property.depth } }; // Add type-specific information switch (property.propertyType) { case SerializedPropertyType.ObjectReference: propertyInfo["objectReferenceValue"] = property.objectReferenceValue != null ? property.objectReferenceValue.name : "null"; propertyInfo["objectReferenceInstanceIDValue"] = property.objectReferenceInstanceIDValue; // Detect missing reference: null value but non-zero instance ID propertyInfo["isMissing"] = property.objectReferenceValue == null && property.objectReferenceInstanceIDValue != 0; propertyInfo["objectType"] = property.objectReferenceValue != null ? property.objectReferenceValue.GetType().Name : "null"; break; case SerializedPropertyType.Integer: propertyInfo["intValue"] = property.intValue; break; case SerializedPropertyType.Boolean: propertyInfo["boolValue"] = property.boolValue; break; case SerializedPropertyType.Float: propertyInfo["floatValue"] = property.floatValue; break; case SerializedPropertyType.String: propertyInfo["stringValue"] = property.stringValue; break; case SerializedPropertyType.Color: var color = property.colorValue; propertyInfo["colorValue"] = $"({color.r:F2}, {color.g:F2}, {color.b:F2}, {color.a:F2})"; break; case SerializedPropertyType.Vector2: var v2 = property.vector2Value; propertyInfo["vector2Value"] = $"({v2.x:F2}, {v2.y:F2})"; break; case SerializedPropertyType.Vector3: var v3 = property.vector3Value; propertyInfo["vector3Value"] = $"({v3.x:F2}, {v3.y:F2}, {v3.z:F2})"; break; case SerializedPropertyType.Vector4: var v4 = property.vector4Value; propertyInfo["vector4Value"] = $"({v4.x:F2}, {v4.y:F2}, {v4.z:F2}, {v4.w:F2})"; break; case SerializedPropertyType.Enum: propertyInfo["enumValueIndex"] = property.enumValueIndex; propertyInfo["enumDisplayNames"] = string.Join(", ", property.enumDisplayNames); break; } propertyInfo["tooltip"] = property.tooltip; propertyInfo["hasMultipleDifferentValues"] = property.hasMultipleDifferentValues; propertyInfo["editable"] = property.editable; properties.Add(JsonHelper.SerializeObject(propertyInfo)); } string result = $"{{\"componentType\": \"{componentType}\", \"gameObjectPath\": \"{gameObjectPath}\", \"properties\": [{string.Join(",", properties)}]}}"; return JsonRpcResponseHelper.SuccessMessage(result, id); } catch (System.Exception e) { return JsonRpcResponseHelper.ErrorMessage($"Failed to inspect SerializedObject: {e.Message}", id); } } /// <summary> /// Validates all components in a GameObject for missing references /// </summary> public static string ValidateMissingReferences(string paramsJson, object id) { try { string gameObjectPath = ExtractStringParam(paramsJson, "gameObjectPath"); if (string.IsNullOrEmpty(gameObjectPath)) { return JsonRpcResponseHelper.ErrorMessage("gameObjectPath is required", id); } // Find GameObject GameObject target = GameObject.Find(gameObjectPath); if (target == null) { return JsonRpcResponseHelper.ErrorMessage($"GameObject not found: {gameObjectPath}", id); } var missingReferences = new List<string>(); Component[] components = target.GetComponents<Component>(); foreach (Component component in components) { if (component == null) { missingReferences.Add(JsonHelper.SerializeObject(new Dictionary<string, object> { { "componentType", "Unknown" }, { "issue", "Component is null (missing script)" } })); continue; } SerializedObject serializedObject = new SerializedObject(component); SerializedProperty property = serializedObject.GetIterator(); while (property.NextVisible(true)) { if (property.propertyType == SerializedPropertyType.ObjectReference) { // Check for missing reference if (property.objectReferenceValue == null && property.objectReferenceInstanceIDValue != 0) { missingReferences.Add(JsonHelper.SerializeObject(new Dictionary<string, object> { { "componentType", component.GetType().Name }, { "propertyPath", property.propertyPath }, { "propertyName", property.name }, { "displayName", property.displayName }, { "instanceID", property.objectReferenceInstanceIDValue } })); } } } } string result = $"{{\"gameObjectPath\": \"{gameObjectPath}\", \"missingReferences\": [{string.Join(",", missingReferences)}], \"count\": {missingReferences.Count}}}"; return JsonRpcResponseHelper.SuccessMessage(result, id); } catch (System.Exception e) { return JsonRpcResponseHelper.ErrorMessage($"Failed to validate missing references: {e.Message}", id); } } /// <summary> /// Validates all GameObjects in the current scene for missing references /// </summary> public static string ValidateSceneMissingReferences(string paramsJson, object id) { try { var allMissingReferences = new List<string>(); GameObject[] allGameObjects = Object.FindObjectsOfType<GameObject>(); foreach (GameObject go in allGameObjects) { Component[] components = go.GetComponents<Component>(); foreach (Component component in components) { if (component == null) { allMissingReferences.Add(JsonHelper.SerializeObject(new Dictionary<string, object> { { "gameObjectPath", go.name }, { "componentType", "Unknown" }, { "issue", "Component is null (missing script)" } })); continue; } SerializedObject serializedObject = new SerializedObject(component); SerializedProperty property = serializedObject.GetIterator(); while (property.NextVisible(true)) { if (property.propertyType == SerializedPropertyType.ObjectReference) { if (property.objectReferenceValue == null && property.objectReferenceInstanceIDValue != 0) { allMissingReferences.Add(JsonHelper.SerializeObject(new Dictionary<string, object> { { "gameObjectPath", go.name }, { "componentType", component.GetType().Name }, { "propertyPath", property.propertyPath }, { "propertyName", property.name }, { "displayName", property.displayName }, { "instanceID", property.objectReferenceInstanceIDValue } })); } } } } } string result = $"{{\"totalGameObjects\": {allGameObjects.Length}, \"missingReferences\": [{string.Join(",", allMissingReferences)}], \"count\": {allMissingReferences.Count}}}"; return JsonRpcResponseHelper.SuccessMessage(result, id); } catch (System.Exception e) { return JsonRpcResponseHelper.ErrorMessage($"Failed to validate scene missing references: {e.Message}", id); } } private static string ExtractStringParam(string paramsJson, string paramName) { try { string pattern = $"\"{paramName}\"\\s*:\\s*\"([^\"]+)\""; var match = System.Text.RegularExpressions.Regex.Match(paramsJson, pattern); if (match.Success) { return match.Groups[1].Value; } } catch (System.Exception e) { Debug.LogError($"Error extracting parameter '{paramName}': {e.Message}"); } return null; } } /// <summary> /// Helper class for JSON serialization /// </summary> public static class JsonHelper { public static string SerializeObject(Dictionary<string, object> dict) { var pairs = new List<string>(); foreach (var kvp in dict) { string value; if (kvp.Value is string) { value = $"\"{EscapeJson(kvp.Value.ToString())}\""; } else if (kvp.Value is bool) { value = kvp.Value.ToString().ToLower(); } else { value = kvp.Value.ToString(); } pairs.Add($"\"{kvp.Key}\": {value}"); } return "{" + string.Join(", ", pairs) + "}"; } private static string EscapeJson(string str) { return str.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r"); } } #endif }

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/dsgarage/UniMCP4CC'

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