Skip to content

Conversation

dsarno
Copy link
Collaborator

@dsarno dsarno commented Sep 3, 2025

Summary

Basically, now the MCP can intelligentaly add components to objects and not get confused by assembly friction. This PR enhances Unity's ComponentResolver with robust assembly loading and intelligent property name matching to eliminate "Assembly-CSharp-Editor" errors and improve user experience.

Key Features

🔧 Robust Component Resolution

  • Eliminates Assembly.LoadFrom errors by searching already-loaded assemblies
  • Prioritizes Player assemblies over Editor assemblies using
    CompilationPipeline.GetAssemblies
  • Supports assembly definitions (asmdefs) with proper type discovery
  • Handles duplicate type names with clear ambiguity resolution
  • Integrates with Unity's Undo system for proper editor workflow

🎯 Intelligent Property Matching

  • Fuzzy string matching using Levenshtein distance algorithm
  • Natural language support - converts "Max Reach Distance" → "maxReachDistance"
  • Unity naming convention awareness - handles camelCase, PascalCase, spaces, underscores
  • Smart suggestions when property names don't match exactly
  • Performance caching to avoid repeated calculations

✅ Comprehensive Testing

  • 45 unit tests with 100% pass rate
  • AIPropertyMatchingTests - 14 tests covering fuzzy matching, caching, edge cases
  • ComponentResolverTests - 11 tests for assembly resolution and type discovery
  • ManageGameObjectTests - 10 integration tests with real Unity components
  • Full coverage of error handling, performance, and real-world scenarios

Technical Implementation

  • Uses CompilationPipeline.GetAssemblies(AssembliesType.Player) instead of
    Assembly.LoadFrom
  • Implements rule-based fuzzy matching algorithm (not AI-dependent)
  • Comprehensive error messages with property suggestions
  • Robust null parameter handling and edge case coverage
  • Performance optimizations with intelligent caching

Example Usage

Before:
[ManageGameObject] Could not set property 'Max Reach Distance' on component
'LongUnityScriptClaudeTest'

After:
Property 'Max Reach Distance' not found on LongUnityScriptClaudeTest.
Did you mean: maxReachDistance?
Available properties: [maxReachDistance, maxHorizontalDistance, maxVerticalDistance, ...]

Files Changed

  • UnityMcpBridge/Editor/Tools/ManageGameObject.cs - Major ComponentResolver refactor
  • UnityMcpBridge/Editor/AssemblyInfo.cs - Test assembly access configuration
  • TestProjects/.../AIPropertyMatchingTests.cs - Comprehensive property matching tests
  • TestProjects/.../ManageGameObjectTests.cs - Integration tests with real components
  • TestProjects/.../ComponentResolverTests.cs.meta - Test metadata

Testing

All 45 unit tests pass, including:

  • ✅ Component resolution with built-in and custom components
  • ✅ Assembly definition support and prioritization
  • ✅ Fuzzy property name matching with various input formats
  • ✅ Error handling and edge cases
  • ✅ Performance caching and real-world integration

Breaking Changes

None - this is purely additive functionality that improves existing behavior.

Summary by CodeRabbit

  • New Features

    • Improved component type resolution with caching, support for nested property paths, and better conversion for vectors/colors/quaternions; prefab modify/set_property actions now route to asset management and include non-public serialized fields by default.
    • Added a sample custom component and assembly used by tests.
  • Bug Fixes

    • Handles null command parameters, avoids conflicting physics component operations, and returns clearer property-related error messages with suggestions.
  • Refactor

    • Centralized resolver and unified serialization pathways.
  • Tests

    • Extensive new edit-mode test suites and test assembly reference added.

dsarno and others added 3 commits September 2, 2025 18:45
- Replace Assembly.LoadFrom with already-loaded assembly search
- Prioritize Player assemblies over Editor assemblies using CompilationPipeline
- Support both short names and fully-qualified component names
- Add comprehensive caching and error handling with disambiguation
- Use Undo.AddComponent for proper editor integration
- Handle ReflectionTypeLoadException safely during type enumeration
- Add fallback to TypeCache for comprehensive type discovery in Editor

Fixes component resolution across custom assembly definitions and eliminates
"Could not load the file 'Assembly-CSharp-Editor'" errors.

Tested with:
- Built-in Unity components (Rigidbody, MeshRenderer, AudioSource)
- Custom user scripts in Assembly-CSharp (TicTacToe3D)
- Custom assembly definition components (TestNamespace.CustomComponent)
- Error handling for non-existent components

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add intelligent property name suggestions when property setting fails
- Implement GetAllComponentProperties to enumerate available properties
- Add rule-based AI algorithm for property name matching (camelCase, spaces, etc.)
- Include comprehensive error messages with suggestions and full property lists
- Add Levenshtein distance calculation for fuzzy string matching
- Cache suggestions to improve performance on repeated queries
- Add comprehensive unit tests (11 tests) covering all ComponentResolver scenarios
- Add InternalsVisibleTo attribute for test access to internal classes

Examples of improved error messages:
- "Max Reach Distance" → "Did you mean: maxReachDistance?"
- Shows all available properties when property not found
- Handles Unity Inspector display names vs actual field names

All tests passing (21/21) including new ComponentResolver test suite.
The system eliminates silent property setting failures and provides
actionable feedback to developers.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ent property matching

- Add AIPropertyMatchingTests.cs with 14 tests covering property enumeration, fuzzy matching, caching, and Unity naming conventions
- Add ManageGameObjectTests.cs with 10 integration tests for component resolution, property matching, and error handling
- Add ComponentResolverTests.cs.meta for existing comprehensive ComponentResolver tests
- Add AssemblyInfo.cs.meta for test assembly access
- Fix ManageGameObject.HandleCommand null parameter handling to prevent NullReferenceException

All 45 unit tests now pass, providing full coverage of:
- Robust component resolution avoiding Assembly.LoadFrom
- Intelligent property name suggestions using rule-based fuzzy matching
- Assembly definition (asmdef) support via CompilationPipeline
- Comprehensive error handling with helpful suggestions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

coderabbitai bot commented Sep 3, 2025

Walkthrough

Adds a TestAsmdef assembly and CustomComponent MonoBehaviour; introduces extensive EditMode tests for component resolution and AI-style property suggestions; exposes editor internals to tests; centralizes type lookup into a new ComponentResolver and integrates it across ManageGameObject and ManageAsset with nested property setting and richer error/suggestion flows.

Changes

Cohort / File(s) Summary
New sample assembly & component
TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/TestAsmdef.asmdef, .../TestAsmdef.asmdef.meta, TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/CustomComponent.cs, .../CustomComponent.cs.meta
Adds asmdef TestAsmdef (rootNamespace TestNamespace) and CustomComponent : MonoBehaviour with two serialized fields and a Start() Debug.Log; includes .meta files.
EditMode tests (new)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/AIPropertyMatchingTests.cs, .../ComponentResolverTests.cs, .../ManageGameObjectTests.cs, and their .meta files
Adds NUnit EditMode tests covering ComponentResolver behavior, AI-style property suggestions, ManageGameObject/ManageAsset command handling, integration with real components, caching/performance checks, and various edge cases.
Test asmdef reference update
TestProjects/UnityMCPTests/Assets/Tests/EditMode/MCPForUnityTests.Editor.asmdef
Adds "TestAsmdef" to the test assembly references.
Assembly visibility for tests
UnityMcpBridge/Editor/AssemblyInfo.cs, UnityMcpBridge/Editor/AssemblyInfo.cs.meta
Adds [assembly: InternalsVisibleTo("MCPForUnityTests.EditMode")] to allow test assembly access to internals; includes .meta.
ManageGameObject / ManageAsset refactor & resolver integration
UnityMcpBridge/Editor/Tools/ManageGameObject.cs, UnityMcpBridge/Editor/Tools/ManageAsset.cs
Replaces ad-hoc FindType with centralized ComponentResolver.TryResolve; introduces shared JSON serializer, prefab redirection, includeNonPublicSerialized support, ParseVector3 helper, nested property/index path setting, private [SerializeField] assignment, enhanced conversions, AI-style suggestion/error messages, and updated component add/remove safeguards. Removes previous FindType helper. Public HandleCommand signature unchanged.
ComponentResolver internal implementation (new helpers)
implemented inside UnityMcpBridge/Editor/Tools/ManageGameObject.cs (internal additions)
Adds internal ComponentResolver with TryResolve (short/full names, caching, player-assembly preference, ambiguity handling), GetAllComponentProperties, GetAIPropertySuggestions (rule-based + Levenshtein), supporting utilities, and updates to GetComponentsFromTarget to respect visibility and ordering.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant MG as ManageGameObject
  participant MA as ManageAsset
  participant CR as ComponentResolver
  participant Ser as Helpers.GameObjectSerializer

  Caller->>MG: HandleCommand(params)
  alt params == null
    MG-->>Caller: Error (null params)
  else target endsWith ".prefab" and action in {modify, set_component_property}
    note over MG,MA #e6f7ff: Prefab redirected to ManageAsset
    MG->>MA: Redirected params
    MA-->>Caller: Result / Error
  else
    MG->>CR: TryResolve(componentName)
    alt resolved
      MG->>MG: Get/Set component, SetNestedProperty
      MG->>Ser: Convert/serialize inputs
      Ser-->>MG: Converted values
      MG-->>Caller: Success
    else unresolved
      MG->>CR: GetAIPropertySuggestions(input, availableProps)
      MG-->>Caller: Error + suggestions
    end
  end
Loading
sequenceDiagram
  autonumber
  actor Tool
  participant MA as ManageAsset
  participant CR as ComponentResolver

  Tool->>MA: Create ScriptableObject(scriptClassName)
  MA->>CR: TryResolve(scriptClassName)
  alt resolved
    MA-->>Tool: Create asset with resolved Type
  else
    MA-->>Tool: Error: Script class 'X' invalid: <reason>
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • msanatan
  • Scriptwonder

Poem

A nibble of code, a hop through the scene,
I sniff out components—so tidy, so keen.
Prefabs redirect and suggestions unfurl,
I stitch nested paths with a twitch and a twirl.
Tests hop green — this rabbit gives a cheer. 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
UnityMcpBridge/Editor/Tools/ManageAsset.cs (3)

204-213: Surface resolver error context when ScriptableObject type resolution fails.

If resolution fails, include error from ComponentResolver in the user-facing message to aid debugging.

-                    Type scriptType = ComponentResolver.TryResolve(scriptClassName, out var resolvedType, out var error) ? resolvedType : null;
+                    Type scriptType = ComponentResolver.TryResolve(scriptClassName, out var resolvedType, out var error) ? resolvedType : null;
                     if (
                         scriptType == null
                         || !typeof(ScriptableObject).IsAssignableFrom(scriptType)
                     )
                     {
-                        return Response.Error(
-                            $"Script class '{scriptClassName}' not found or does not inherit from ScriptableObject."
-                        );
+                        var reason = scriptType == null
+                            ? (string.IsNullOrEmpty(error) ? "Type not found." : error)
+                            : "Type found but does not inherit from ScriptableObject.";
+                        return Response.Error($"Script class '{scriptClassName}' invalid: {reason}");
                     }

356-366: Use ComponentResolver for component lookup instead of GetComponent(string).

GetComponent(string) is fragile and may not behave as intended in C#. Resolve the type first (handles asmdefs/player assemblies/ambiguity), then call GetComponent(Type).

-                            // Using GetComponent(string) is convenient but might require exact type name or be ambiguous.
-                            // Consider using ComponentResolver if needed for more complex scenarios.
-                            Component targetComponent = gameObject.GetComponent(componentName);
+                            // Resolve component type via ComponentResolver, then fetch by Type
+                            Component targetComponent = null;
+                            if (ComponentResolver.TryResolve(componentName, out var compType, out var compError))
+                            {
+                                targetComponent = gameObject.GetComponent(compType);
+                            }
+                            else
+                            {
+                                Debug.LogWarning(
+                                    $"[ManageAsset.ModifyAsset] Failed to resolve component '{componentName}' on '{gameObject.name}': {compError}"
+                                );
+                            }

936-947: Fix operator precedence bug that can pass with null propName and cause exceptions.

Parentheses are required; otherwise the condition can be true when propName is null but value is integer.

-                if (
-                    !string.IsNullOrEmpty(propName) && floatProps["value"]?.Type == JTokenType.Float
-                    || floatProps["value"]?.Type == JTokenType.Integer
-                )
+                if (
+                    !string.IsNullOrEmpty(propName) &&
+                    (floatProps["value"]?.Type == JTokenType.Float || floatProps["value"]?.Type == JTokenType.Integer)
+                )
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (3)

287-297: Primitive creation leaks a GameObject on early error.

If name is missing, the code returns after creating the primitive without cleaning it up.

Apply:

-                        // Set name *after* creation for primitives
-                        if (!string.IsNullOrEmpty(name))
-                            newGo.name = name;
-                        else
-                            return Response.Error(
-                                "'name' parameter is required when creating a primitive."
-                            ); // Name is essential
+                        // Set name *after* creation for primitives
+                        if (!string.IsNullOrEmpty(name))
+                        {
+                            newGo.name = name;
+                        }
+                        else
+                        {
+                            UnityEngine.Object.DestroyImmediate(newGo); // cleanup leak
+                            return Response.Error(
+                                "'name' parameter is required when creating a primitive."
+                            );
+                        }

1496-1503: Resolve component type before GetComponent to avoid string-based lookup pitfalls.

Using GetComponent(string) ignores namespaces and risks ambiguity. Resolve to Type and call GetComponent(Type), with string fallback.

-            Component targetComponent = targetComponentInstance ?? targetGo.GetComponent(compName);
+            Component targetComponent = targetComponentInstance;
+            if (targetComponent == null)
+            {
+                var compType = FindType(compName);
+                targetComponent = compType != null
+                    ? targetGo.GetComponent(compType)
+                    : targetGo.GetComponent(compName); // fallback
+            }

1600-1631: Allow setting private [SerializeField] fields when present.

You surface serialized private fields in suggestions but SetProperty cannot set them. Include NonPublic field lookup gated by [SerializeField].

-                else
-                {
-                    FieldInfo fieldInfo = type.GetField(memberName, flags);
+                else
+                {
+                    FieldInfo fieldInfo = type.GetField(memberName, flags);
                     if (fieldInfo != null) // Check if !IsLiteral?
                     {
                          // Use the inputSerializer for conversion
                         object convertedValue = ConvertJTokenToType(value, fieldInfo.FieldType, inputSerializer);
                          if (convertedValue != null || value.Type == JTokenType.Null) // Allow setting null
                         {
                             fieldInfo.SetValue(target, convertedValue);
                             return true;
                         }
                          else {
                              Debug.LogWarning($"[SetProperty] Conversion failed for field '{memberName}' (Type: {fieldInfo.FieldType.Name}) from token: {value.ToString(Formatting.None)}");
                          }
                     }
+                    else
+                    {
+                        // Try NonPublic [SerializeField] fields
+                        var npField = type.GetField(memberName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase);
+                        if (npField != null && npField.GetCustomAttribute<SerializeField>() != null)
+                        {
+                            object convertedValue = ConvertJTokenToType(value, npField.FieldType, inputSerializer);
+                            if (convertedValue != null || value.Type == JTokenType.Null)
+                            {
+                                npField.SetValue(target, convertedValue);
+                                return true;
+                            }
+                        }
+                    }
                 }
🧹 Nitpick comments (22)
UnityMcpBridge/Editor/Tools/ManageAsset.cs (5)

361-369: Record Undo before mutating components to align with Editor best practices.

Adds proper Undo support when modifying prefab/component properties.

-                                modified |= ApplyObjectProperties(
+                                // Enable Undo of component changes
+                                Undo.RecordObject(targetComponent, $"Modify {componentName} on {gameObject.name}");
+                                modified |= ApplyObjectProperties(
                                     targetComponent,
                                     componentProperties
                                 );

396-405: Also record Undo for Material/ScriptableObject/Importer edits.

Improves UX parity across asset types.

-                    // Apply properties directly to the material. If this modifies, it sets modified=true.
+                    // Apply properties directly to the material. If this modifies, it sets modified=true.
+                    Undo.RecordObject(material, $"Modify Material {material.name}");
                     // Use |= in case the asset was already marked modified by previous logic (though unlikely here)
                     modified |= ApplyMaterialProperties(material, properties);

And similarly when asset is ScriptableObject so and before importer changes:

-                    modified |= ApplyObjectProperties(so, properties); // General helper
+                    Undo.RecordObject(so, $"Modify {so.name}");
+                    modified |= ApplyObjectProperties(so, properties); // General helper
-                        bool importerModified = ApplyObjectProperties(textureImporter, properties);
+                        Undo.RecordObject(textureImporter, $"Modify TextureImporter {Path.GetFileName(fullPath)}");
+                        bool importerModified = ApplyObjectProperties(textureImporter, properties);

1072-1081: Offer AI-backed property name suggestions when a set fails.

Leverage the new suggestion utilities to guide users if a property isn’t found.

-                if (SetPropertyOrField(target, propName, propValue, type))
-                {
-                    modified = true;
-                }
+                if (SetPropertyOrField(target, propName, propValue, type))
+                {
+                    modified = true;
+                }
+                else if (target is Component comp)
+                {
+                    var available = ComponentResolver.GetAllComponentProperties(type);
+                    var suggestions = ComponentResolver.GetAIPropertySuggestions(propName, available);
+                    if (suggestions.Count > 0)
+                    {
+                        Debug.LogWarning($"[ApplyObjectProperties] '{propName}' not found on {type.Name}. Did you mean: {string.Join(", ", suggestions)}?");
+                    }
+                }

1024-1057: Simplify (and harden) combine parsing with Enum.TryParse and case-insensitivity.

Reduces duplication, handles mixed case, and only marks modified when value actually changes.

-            List<String> averageList = new List<String> { "ave", "Ave", "average", "Average" };
-            List<String> multiplyList = new List<String> { "mul", "Mul", "mult", "Mult", "multiply", "Multiply" };
-            List<String> minimumList = new List<String> { "min", "Min", "minimum", "Minimum" };
-            List<String> maximumList = new List<String> { "max", "Max", "maximum", "Maximum" };
+            // Accept common shorthands via a small alias map
+            var combineAliases = new Dictionary<string, PhysicsMaterialCombine>(StringComparer.OrdinalIgnoreCase)
+            {
+                ["ave"] = PhysicsMaterialCombine.Average,
+                ["average"] = PhysicsMaterialCombine.Average,
+                ["mul"] = PhysicsMaterialCombine.Multiply,
+                ["mult"] = PhysicsMaterialCombine.Multiply,
+                ["multiply"] = PhysicsMaterialCombine.Multiply,
+                ["min"] = PhysicsMaterialCombine.Minimum,
+                ["minimum"] = PhysicsMaterialCombine.Minimum,
+                ["max"] = PhysicsMaterialCombine.Maximum,
+                ["maximum"] = PhysicsMaterialCombine.Maximum
+            };
@@
-            if (properties["frictionCombine"]?.Type == JTokenType.String)
+            if (properties["frictionCombine"]?.Type == JTokenType.String)
             {
-                string frictionCombine = properties["frictionCombine"].ToString();
-                if (averageList.Contains(frictionCombine))
-                    pmat.frictionCombine = PhysicsMaterialCombine.Average;
-                else if (multiplyList.Contains(frictionCombine))
-                    pmat.frictionCombine = PhysicsMaterialCombine.Multiply;
-                else if (minimumList.Contains(frictionCombine))
-                    pmat.frictionCombine = PhysicsMaterialCombine.Minimum;
-                else if (maximumList.Contains(frictionCombine))
-                    pmat.frictionCombine = PhysicsMaterialCombine.Maximum;
-                modified = true;
+                string raw = properties["frictionCombine"].ToString();
+                PhysicsMaterialCombine parsed;
+                if (combineAliases.TryGetValue(raw, out parsed) ||
+                    Enum.TryParse(raw, true, out parsed))
+                {
+                    if (pmat.frictionCombine != parsed)
+                    {
+                        pmat.frictionCombine = parsed;
+                        modified = true;
+                    }
+                }
             }
@@
-            if (properties["bounceCombine"]?.Type == JTokenType.String)
+            if (properties["bounceCombine"]?.Type == JTokenType.String)
             {
-                string bounceCombine = properties["bounceCombine"].ToString();
-                if (averageList.Contains(bounceCombine))
-                    pmat.bounceCombine = PhysicsMaterialCombine.Average;
-        ...
-                modified = true;
+                string raw = properties["bounceCombine"].ToString();
+                PhysicsMaterialCombine parsed;
+                if (combineAliases.TryGetValue(raw, out parsed) ||
+                    Enum.TryParse(raw, true, out parsed))
+                {
+                    if (pmat.bounceCombine != parsed)
+                    {
+                        pmat.bounceCombine = parsed;
+                        modified = true;
+                    }
+                }
             }

809-819: Normalize to canonical 'Assets/' casing (nit).

Unity APIs accept lowercase, but canonical casing avoids surprises on case-sensitive filesystems.

-            if (!path.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase))
+            if (!path.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase))
             {
-                return "Assets/" + path.TrimStart('/');
+                var tail = path.StartsWith("assets/", StringComparison.OrdinalIgnoreCase)
+                    ? path.Substring(6).TrimStart('/', '\\')
+                    : path.TrimStart('/', '\\');
+                return "Assets/" + tail;
             }
TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/CustomComponent.cs (1)

5-17: LGTM; simple test component is well-scoped.

Optional: add [AddComponentMenu("Test/CustomComponent")] to make it easier to add from the editor during manual tests.

UnityMcpBridge/Editor/Tools/ManageGameObject.cs (8)

49-51: Confirm default for includeNonPublicSerialized.

Defaulting to true exposes private [SerializeField] data in editor responses. Confirm this is intentional; otherwise default to false and let callers opt in.


878-949: Avoid O(n^2) list reordering in get_components.

Iterating backward and inserting at index 0 causes repeated shifts. You already copied the array; iterate forward and append.

-                var componentData = new List<object>();
-                
-                for (int i = componentCount - 1; i >= 0; i--) // Iterate backwards over the COPY
-                {
-                    Component c = componentsToIterate[i]; // Use the copy
+                var componentData = new List<object>(componentCount);
+                for (int i = 0; i < componentCount; i++)
+                {
+                    Component c = componentsToIterate[i];
                     if (c == null) 
                     {
                         continue; // Safety check
                     }
                     try 
                     {
                         var data = Helpers.GameObjectSerializer.GetComponentData(c, includeNonPublicSerialized);
                         if (data != null) // Ensure GetComponentData didn't return null
                         {
-                            componentData.Insert(0, data); // Insert at beginning to maintain original order in final list
+                            componentData.Add(data);
                         }
                     }
                     catch (Exception ex)
                     {
                         Debug.LogError($"[GetComponentsFromTarget REVERSE for] Error processing component {c.GetType().FullName} (ID: {c.GetInstanceID()}) on {targetGo.name}: {ex.Message}\n{ex.StackTrace}");
-                        componentData.Insert(0, new JObject( // Insert error marker at beginning
+                        componentData.Add(new JObject(
                             new JProperty("typeName", c.GetType().FullName + " (Serialization Error)"),
                             new JProperty("instanceID", c.GetInstanceID()),
                             new JProperty("error", ex.Message)
                         ));
                     }
                 }
-                // Debug.Log($"[GetComponentsFromTarget] Finished REVERSE for loop.");
+                // Debug.Log($"[GetComponentsFromTarget] Finished forward loop.");

1106-1127: Deduplicate ParseVector3; reuse Vector3Helper for consistency.

You already have Helpers.Vector3Helper.ParseVector3(JArray). Wrap it to return nullable instead of reimplementing.

-        private static Vector3? ParseVector3(JArray array)
-        {
-            if (array != null && array.Count == 3)
-            {
-                try
-                {
-                    return new Vector3(
-                        array[0].ToObject<float>(),
-                        array[1].ToObject<float>(),
-                        array[2].ToObject<float>()
-                    );
-                }
-                catch (Exception ex)
-                {
-                    Debug.LogWarning($"Failed to parse JArray as Vector3: {array}. Error: {ex.Message}");
-                }
-            }
-            return null;
-        }
+        private static Vector3? ParseVector3(JArray array)
+        {
+            if (array == null) return null;
+            try { return MCPForUnity.Editor.Helpers.Vector3Helper.ParseVector3(array); }
+            catch (Exception ex)
+            {
+                Debug.LogWarning($"Failed to parse Vector3: {ex.Message}");
+                return null;
+            }
+        }

1570-1589: Avoid per-call JsonSerializer allocations in SetProperty.

Create a single static serializer with converters and reuse it.

-             JsonSerializerSettings inputSerializerSettings = new JsonSerializerSettings
-             {
-                 Converters = new List<JsonConverter>
-                 {
-                    // Add specific converters needed for INPUT deserialization if different from output
-                    new Vector3Converter(),
-                    new Vector2Converter(),
-                    new QuaternionConverter(),
-                    new ColorConverter(),
-                    new RectConverter(),
-                    new BoundsConverter(),
-                    new UnityEngineObjectConverter() // Crucial for finding references from instructions
-                 }
-                 // No ReferenceLoopHandling needed typically for input
-             };
-             JsonSerializer inputSerializer = JsonSerializer.Create(inputSerializerSettings);
+             var inputSerializer = InputSerializer; // reuse static instance

Add (outside this hunk, at class scope):

// Serializer reused across setters
private static readonly JsonSerializer InputSerializer = JsonSerializer.Create(
    new JsonSerializerSettings
    {
        Converters = new List<JsonConverter>
        {
            new Vector3Converter(),
            new Vector2Converter(),
            new QuaternionConverter(),
            new ColorConverter(),
            new RectConverter(),
            new BoundsConverter(),
            new UnityEngineObjectConverter()
        }
    });

840-845: Avoid reflection to build delete confirmation message.

Store the name in the loop and reuse it instead of reflecting anonymous types.

-                string message =
-                    targets.Count == 1
-                        ? $"GameObject '{deletedObjects[0].GetType().GetProperty("name").GetValue(deletedObjects[0])}' deleted successfully."
+                string message =
+                    targets.Count == 1
+                        ? $"GameObject '{((dynamic)deletedObjects[0]).name}' deleted successfully."
                         : $"{deletedObjects.Count} GameObjects deleted successfully.";

Or keep a parallel List deletedNames to compose the message.


2161-2177: Use short-name cache to avoid repeated scans.

You cache by Name but never read it. Check CacheByName for queries without a dot.

-        public static bool TryResolve(string nameOrFullName, out Type type, out string error)
+        public static bool TryResolve(string nameOrFullName, out Type type, out string error)
         {
             error = string.Empty;
             type = null!;
             // Handle null/empty input
             if (string.IsNullOrWhiteSpace(nameOrFullName))
             {
                 error = "Component name cannot be null or empty";
                 return false;
             }
-            // 1) Exact FQN via Type.GetType
+            // 1) Cache hit
+            if (!nameOrFullName.Contains('.') && CacheByName.TryGetValue(nameOrFullName, out type)) return true;
+            if (CacheByFqn.TryGetValue(nameOrFullName, out type)) return true;
+            // 2) Exact FQN via Type.GetType
-            if (CacheByFqn.TryGetValue(nameOrFullName, out type)) return true;
             type = Type.GetType(nameOrFullName, throwOnError: false);
             if (IsValidComponent(type)) { Cache(type); return true; }
-            // 2) Search loaded assemblies (prefer Player assemblies)
+            // 3) Search loaded assemblies (prefer Player assemblies)

2304-2320: Remove unused 'prompt' variable.

Dead code; keep noise down.

-                var prompt = $"A Unity developer is trying to set a component property but used an incorrect name.\n\n" +
-                             $"User requested: \"{userInput}\"\n" +
-                             $"Available properties: [{string.Join(", ", availableProperties)}]\n\n" +
-                             $"Find 1-3 most likely matches considering:\n" +
-                             $"- Unity Inspector display names vs actual field names (e.g., \"Max Reach Distance\" → \"maxReachDistance\")\n" +
-                             $"- camelCase vs PascalCase vs spaces\n" +
-                             $"- Similar meaning/semantics\n" +
-                             $"- Common Unity naming patterns\n\n" +
-                             $"Return ONLY the matching property names, comma-separated, no quotes or explanation.\n" +
-                             $"If confidence is low (<70%), return empty string.\n\n" +
-                             $"Examples:\n" +
-                             $"- \"Max Reach Distance\" → \"maxReachDistance\"\n" +
-                             $"- \"Health Points\" → \"healthPoints, hp\"\n" +
-                             $"- \"Move Speed\" → \"moveSpeed, movementSpeed\"";
+                // Placeholder kept for future AI integration (removed unused variable).

2409-2428: Drop duplicate ParseVector3 in ComponentResolver region.

This method isn’t used by the resolver and duplicates logic elsewhere. Remove to reduce maintenance.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ComponentResolverTests.cs (1)

5-5: Remove unused using static.

The static import of ManageGameObject isn’t needed here.

-using static MCPForUnity.Editor.Tools.ManageGameObject;
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs (2)

33-39: Strengthen assertion by inspecting Response.Success flag.

Parse the result to assert error vs success explicitly.

-            Assert.IsNotNull(result, "Should return a result object");
-            // Note: Actual error checking would need access to Response structure
+            Assert.IsNotNull(result);
+            dynamic r = result;
+            Assert.IsFalse((bool)r.success, "Expected error response for null params");

50-69: Assert creation success via response and cleanup.

Validate Response.success/message to catch regressions.

-            Assert.IsNotNull(result, "Should return a result for valid create action");
+            Assert.IsNotNull(result);
+            dynamic r = result;
+            Assert.IsTrue((bool)r.success, "Expected success for create action");
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/AIPropertyMatchingTests.cs (5)

94-100: Use an actual typo to exercise Levenshtein, not just case normalization.

Current input ("movespeed") only tests case-insensitivity. Make it a real typo so we validate edit-distance logic.

-        public void GetAIPropertySuggestions_FindsSimilarStrings_WithTypos()
+        public void GetAIPropertySuggestions_HandlesTypos_ViaLevenshtein()
@@
-            var suggestions = ComponentResolver.GetAIPropertySuggestions("movespeed", sampleProperties); // missing capital S
+            var suggestions = ComponentResolver.GetAIPropertySuggestions("moev speed", sampleProperties); // transposed letters + space
@@
-            Assert.Contains("moveSpeed", suggestions, "Should find moveSpeed despite missing capital");
+            Assert.Contains("moveSpeed", suggestions, "Should find moveSpeed despite typo");

102-109: Clarify expectations for “semantic” matches.

As written, this always passes (non-null list). Either assert the current behavior explicitly (empty until synonyms are added) or mark with a TODO referencing planned semantic mapping.

If you want to lock current behavior:

-            // Note: Current algorithm might not find "mass" but should handle it gracefully
-            Assert.IsNotNull(suggestions, "Should return valid suggestions list");
+            // Note: Rule-based matcher has no synonym map yet; expect no suggestions for "weight".
+            Assert.IsEmpty(suggestions, "Should return empty until semantic mapping is added");

59-66: Add whitespace-only input case.

Covers IsNullOrWhiteSpace guard and prevents regressions.

         public void GetAIPropertySuggestions_ReturnsEmpty_ForEmptyInput()
         {
             var suggestions = ComponentResolver.GetAIPropertySuggestions("", sampleProperties);
             
             Assert.IsEmpty(suggestions, "Empty input should return no suggestions");
         }
+
+        [Test]
+        public void GetAIPropertySuggestions_ReturnsEmpty_ForWhitespaceInput()
+        {
+            var suggestions = ComponentResolver.GetAIPropertySuggestions("   ", sampleProperties);
+            Assert.IsEmpty(suggestions, "Whitespace input should return no suggestions");
+        }

135-147: Include hyphen/underscore input variants for Unity-style names.

Strengthens delimiter normalization coverage without changing behavior.

             var suggestions1 = ComponentResolver.GetAIPropertySuggestions("is kinematic", unityStyleProperties);
             var suggestions2 = ComponentResolver.GetAIPropertySuggestions("use gravity", unityStyleProperties);
             var suggestions3 = ComponentResolver.GetAIPropertySuggestions("max linear velocity", unityStyleProperties);
+            var suggestions4 = ComponentResolver.GetAIPropertySuggestions("max-linear-velocity", unityStyleProperties);
+            var suggestions5 = ComponentResolver.GetAIPropertySuggestions("max_linear_velocity", unityStyleProperties);
             
             Assert.Contains("isKinematic", suggestions1, "Should handle 'is' prefix convention");
             Assert.Contains("useGravity", suggestions2, "Should handle 'use' prefix convention");
             Assert.Contains("maxLinearVelocity", suggestions3, "Should handle 'max' prefix convention");
+            Assert.Contains("maxLinearVelocity", suggestions4, "Should handle hyphen-delimited input");
+            Assert.Contains("maxLinearVelocity", suggestions5, "Should handle underscore-delimited input");

120-133: Verify result stability across different property orders.

Caching key is order-sensitive, but result ordering should be distance-driven and independent of list order. Add a test to lock this in.

         public void GetAIPropertySuggestions_CachesResults()
         {
             var input = "Max Reach Distance";
             
             // First call
             var suggestions1 = ComponentResolver.GetAIPropertySuggestions(input, sampleProperties);
             
             // Second call should use cache (tested indirectly by ensuring consistency)
             var suggestions2 = ComponentResolver.GetAIPropertySuggestions(input, sampleProperties);
             
             Assert.AreEqual(suggestions1.Count, suggestions2.Count, "Cached results should be consistent");
             CollectionAssert.AreEqual(suggestions1, suggestions2, "Cached results should be identical");
         }
+
+        [Test]
+        public void GetAIPropertySuggestions_ResultsIndependentOfPropertyOrder()
+        {
+            var input = "max distance";
+            var reversed = new List<string>(sampleProperties);
+            reversed.Reverse();
+            var s1 = ComponentResolver.GetAIPropertySuggestions(input, sampleProperties);
+            var s2 = ComponentResolver.GetAIPropertySuggestions(input, reversed);
+            CollectionAssert.AreEqual(s1, s2, "Results should not depend on availableProperties order");
+        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7c7a893 and aac237c.

📒 Files selected for processing (14)
  • TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/CustomComponent.cs (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/CustomComponent.cs.meta (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/TestAsmdef.asmdef (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/TestAsmdef.asmdef.meta (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/AIPropertyMatchingTests.cs (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/AIPropertyMatchingTests.cs.meta (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ComponentResolverTests.cs (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ComponentResolverTests.cs.meta (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs.meta (1 hunks)
  • UnityMcpBridge/Editor/AssemblyInfo.cs (1 hunks)
  • UnityMcpBridge/Editor/AssemblyInfo.cs.meta (1 hunks)
  • UnityMcpBridge/Editor/Tools/ManageAsset.cs (2 hunks)
  • UnityMcpBridge/Editor/Tools/ManageGameObject.cs (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/CustomComponent.cs (1)
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (1)
  • UnityEngine (2026-2123)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs (1)
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (12)
  • UnityEngine (2026-2123)
  • GameObject (1132-1155)
  • ManageGameObject (22-2145)
  • HandleCommand (26-159)
  • ComponentResolver (2151-2432)
  • TryResolve (2161-2196)
  • Type (2130-2144)
  • List (1160-1319)
  • List (2211-2248)
  • List (2271-2289)
  • List (2294-2333)
  • List (2341-2376)
UnityMcpBridge/Editor/Tools/ManageAsset.cs (1)
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (3)
  • Type (2130-2144)
  • ComponentResolver (2151-2432)
  • TryResolve (2161-2196)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/AIPropertyMatchingTests.cs (1)
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (7)
  • ManageGameObject (22-2145)
  • List (1160-1319)
  • List (2211-2248)
  • List (2271-2289)
  • List (2294-2333)
  • List (2341-2376)
  • ComponentResolver (2151-2432)
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (3)
UnityMcpBridge/Editor/Tools/ManageScript.cs (1)
  • System (2267-2333)
UnityMcpBridge/Editor/Helpers/Response.cs (2)
  • Response (10-61)
  • Error (41-60)
UnityMcpBridge/Editor/Helpers/Vector3Helper.cs (1)
  • Vector3 (17-22)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ComponentResolverTests.cs (1)
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (5)
  • UnityEngine (2026-2123)
  • ManageGameObject (22-2145)
  • ComponentResolver (2151-2432)
  • TryResolve (2161-2196)
  • Type (2130-2144)
🔇 Additional comments (16)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs.meta (2)

1-2: Meta file present with GUID — OK.

Nothing to change.


1-2: No duplicate Unity GUIDs found. Sanity check across all .meta files confirms each GUID is unique.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ComponentResolverTests.cs.meta (1)

1-2: Meta file looks good.

GUID recorded; no issues.

TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/CustomComponent.cs.meta (1)

1-2: Meta file OK for CustomComponent.

All good.

UnityMcpBridge/Editor/AssemblyInfo.cs (2)

1-3: Expose internals to EditMode tests — good placement.

Attribute is in Editor assembly; safe for player builds.


3-3: Verified asmdef name matches InternalsVisibleTo attribute.
The MCPForUnityTests.Editor.asmdef defines "name": "MCPForUnityTests.EditMode", matching the attribute.

TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/TestAsmdef.asmdef (1)

1-14: Asmdef added — OK for test fixture assembly.

Name/rootNamespace are consistent; defaults are fine.

TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/TestAsmdef.asmdef.meta (1)

1-7: Meta looks correct and consistent with the new asmdef.

GUID present, importer block valid. Nothing to change.

UnityMcpBridge/Editor/AssemblyInfo.cs.meta (1)

1-2: Meta addition is fine.

GUID only; aligns with adding AssemblyInfo.cs for InternalsVisibleTo.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/AIPropertyMatchingTests.cs.meta (1)

1-2: Meta file is fine.

Nothing actionable.

UnityMcpBridge/Editor/Tools/ManageGameObject.cs (2)

28-31: Good guard for null parameters.

Early-return with a clear error keeps the handler robust.


2132-2144: FindType wrapper correctly delegates and logs resolver diagnostics.

This centralization is good.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs (1)

111-126: Good coverage of resolver against built-ins.

Gracefully handling abstract types with error checks is sensible.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/AIPropertyMatchingTests.cs (3)

1-169: Solid coverage and clear intent.

Good breadth: null/empty inputs, exact/word/typo matches, limits, caching, Unity naming. Tests read well and should be stable across Unity versions.


149-157: Exact-match prioritization test is on point.

Good assertion that distance 0 ranks first.


159-167: Case-insensitivity covered well.

Validates both upper and lower cases against the same target.

Critical Bug Fixes:
- Fix operator precedence bug in ManageAsset.cs that could cause null reference exceptions
- Fix GameObject memory leak in primitive creation when name validation fails
- Add proper cleanup with DestroyImmediate when primitive creation fails

ComponentResolver Integration:
- Replace fragile string-based GetComponent() calls with robust ComponentResolver
- Add ComponentResolver integration in ManageAsset.cs for component lookups
- Add fallback to string-based lookup in ManageGameObject.cs for compatibility

Enhanced Error Handling:
- Surface specific ComponentResolver error context in ScriptableObject creation failures
- Add support for setting private [SerializeField] fields in property matching
- Improve debugging with detailed error messages

Assembly Definition Fixes:
- Configure TestAsmdef as Editor-only to prevent build bloat
- Add explicit TestAsmdef reference to test assembly for proper component resolution
- Fix ComponentResolverTests to use accessible CustomComponent instead of TicTacToe3D

Code Quality:
- Disable nullable reference types for legacy codebase to eliminate 100+ warnings
- Maintain backward compatibility while improving reliability

All 45 unit tests pass, ensuring no regressions while significantly improving robustness.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
coderabbitai[bot]

This comment was marked as resolved.

@dsarno

This comment was marked as resolved.

Copy link
Contributor

coderabbitai bot commented Sep 3, 2025

Tip

For best results, initiate chat on the files or code changes.

@dsarno Thank you for the correction! You're absolutely right about the using static import. I can see from the code that ComponentResolver is indeed a nested static class within ManageGameObject, so the static import is necessary to access ComponentResolver.TryResolve() directly rather than having to write ManageGameObject.ComponentResolver.TryResolve().

The automated review comment incorrectly assumed ComponentResolver was a sibling type in the same namespace. Your clarification about the nested class architecture and the resulting compilation errors (CS0117) without the static import is spot on.


✏️ Learnings added
Learnt from: dsarno
URL: https://github.com/CoplayDev/unity-mcp/pull/256

Timestamp: 2025-09-03T16:00:55.812Z
Learning: ComponentResolver in UnityMcpBridge/Editor/Tools/ManageGameObject.cs is a nested static class within ManageGameObject, not a sibling type. The `using static MCPForUnity.Editor.Tools.ManageGameObject;` import is required to access ComponentResolver methods directly without the outer class qualifier.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Performance & Quality Improvements:
- Add shared JsonSerializer to eliminate per-call allocation overhead
- Optimize ComponentResolver with CacheByName for short-name lookups
- Deduplicate Vector3 parsing implementations to reduce maintenance burden
- Improve property name normalization for better fuzzy matching quality
- Reduce log noise by avoiding duplicate component resolution warnings

Code Quality:
- Keep using static import for ComponentResolver (CodeRabbit was incorrect about this)
- Normalize property names consistently in AI suggestions algorithm
- Remove duplicate ParseVector3 implementation

TestAsmdef Fix:
- Revert TestAsmdef back to runtime-compatible (remove "includePlatforms": ["Editor"])
- CustomComponent now works correctly for asmdef testing as originally intended
- Validates that ComponentResolver properly handles both short and FQN for asmdef components

Live Testing Validation:
- All ComponentResolver functionality verified through live MCP connection
- AI property matching working perfectly with natural language input
- Assembly definition support fully functional for both default and custom assemblies
- Error handling provides helpful suggestions and complete context

All 45 C# tests + 31 Python tests still passing. Production ready!

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (8)
UnityMcpBridge/Editor/Tools/ManageAsset.cs (2)

358-373: Gate duplicate warnings when resolution already failed.

Currently, when resolution fails you log both the resolver failure and the “component not found” warning, creating noise. Skip the second warning if resolution didn’t succeed.

Apply:

-                            // Only warn about resolution failure if component also not found
-                            if (targetComponent == null && !resolved)
+                            // Only warn about resolution failure; avoid duplicate "not found" below
+                            if (!resolved)
                             {
                                 Debug.LogWarning(
                                     $"[ManageAsset.ModifyAsset] Failed to resolve component '{componentName}' on '{gameObject.name}': {compError}"
                                 );
                             }
...
-                            else
+                            else if (targetComponent == null && resolved)
                             {
                                 // Log a warning if a specified component couldn't be found
                                 Debug.LogWarning(
                                     $"[ManageAsset.ModifyAsset] Component '{componentName}' not found on GameObject '{gameObject.name}' in asset '{fullPath}'. Skipping modification for this component."
                                 );
                             }

Also applies to: 385-389


1037-1054: Normalize combine-mode parsing and avoid per-call allocations.

Use a single case-insensitive parser or cached HashSet with OrdinalIgnoreCase. This trims allocations on every call and simplifies maintenance.

-            List<String> averageList = new List<String> { "ave", "Ave", "average", "Average" };
-            List<String> multiplyList = new List<String> { "mul", "Mul", "mult", "Mult", "multiply", "Multiply" };
-            List<String> minimumList = new List<String> { "min", "Min", "minimum", "Minimum" };
-            List<String> maximumList = new List<String> { "max", "Max", "maximum", "Maximum" };
+            static PhysicsMaterialCombine ParseCombine(string s)
+            {
+                if (string.IsNullOrEmpty(s)) return PhysicsMaterialCombine.Average;
+                s = s.Trim().ToLowerInvariant();
+                return s switch
+                {
+                    "ave" or "average" => PhysicsMaterialCombine.Average,
+                    "mul" or "mult" or "multiply" => PhysicsMaterialCombine.Multiply,
+                    "min" or "minimum" => PhysicsMaterialCombine.Minimum,
+                    "max" or "maximum" => PhysicsMaterialCombine.Maximum,
+                    _ => PhysicsMaterialCombine.Average
+                };
+            }
...
-                string frictionCombine = properties["frictionCombine"].ToString();
-                if (averageList.Contains(frictionCombine))
-                    pmat.frictionCombine = PhysicsMaterialCombine.Average;
-                else if (multiplyList.Contains(frictionCombine))
-                    pmat.frictionCombine = PhysicsMaterialCombine.Multiply;
-                else if (minimumList.Contains(frictionCombine))
-                    pmat.frictionCombine = PhysicsMaterialCombine.Minimum;
-                else if (maximumList.Contains(frictionCombine))
-                    pmat.frictionCombine = PhysicsMaterialCombine.Maximum;
+                pmat.frictionCombine = ParseCombine(properties["frictionCombine"].ToString());
                 modified = true;
...
-                string bounceCombine = properties["bounceCombine"].ToString();
-                if (averageList.Contains(bounceCombine))
-                    pmat.bounceCombine = PhysicsMaterialCombine.Average;
-                else if (multiplyList.Contains(bounceCombine))
-                    pmat.bounceCombine = PhysicsMaterialCombine.Multiply;
-                else if (minimumList.Contains(bounceCombine))
-                    pmat.bounceCombine = PhysicsMaterialCombine.Minimum;
-                else if (maximumList.Contains(bounceCombine))
-                    pmat.bounceCombine = PhysicsMaterialCombine.Maximum;
+                pmat.bounceCombine = ParseCombine(properties["bounceCombine"].ToString());
                 modified = true;

Also applies to: 1058-1070

UnityMcpBridge/Editor/Tools/ManageGameObject.cs (6)

1-1: Avoid file-wide nullable suppression.

Prefer enabling nullable and adding targeted suppressions or annotations where needed. This keeps nullability checks useful across the file.

-#nullable disable
+#nullable enable

If migration right now is heavy, limit disable to the few regions that need it with pragmas.


1114-1147: Deduplicate Vector3 parsing via a shared helper.

We already have Helpers/Vector3Helper.ParseVector3; consider centralizing to one helper (e.g., add Vector3Helper.TryParse(JArray, out Vector3) that doesn’t throw) and call it here to reduce divergence.

-        private static Vector3? ParseVector3(JArray array)
-        {
-            if (array != null && array.Count == 3)
-            {
-                try
-                {
-                    return new Vector3(
-                        array[0].ToObject<float>(),
-                        array[1].ToObject<float>(),
-                        array[2].ToObject<float>()
-                    );
-                }
-                catch (Exception ex)
-                {
-                    Debug.LogWarning($"Failed to parse JArray as Vector3: {array}. Error: {ex.Message}");
-                }
-            }
-            return null;
-        }
+        private static Vector3? ParseVector3(JArray array)
+        {
+            return Helpers.Vector3Helper.TryParse(array, out var v) ? v : (Vector3?)null;
+        }

Additional helper (in Helpers/Vector3Helper.cs) to support this:

public static bool TryParse(JArray array, out Vector3 v)
{
    v = default;
    if (array is { Count: 3 })
    {
        try { v = new Vector3(array[0].ToObject<float>(), array[1].ToObject<float>(), array[2].ToObject<float>()); return true; }
        catch { }
    }
    return false;
}

918-961: Avoid O(n^2) inserts while preserving order.

Inserting at index 0 repeatedly shifts the list. Iterate forward and Add, or keep reverse build and call Reverse at the end.

-                for (int i = componentCount - 1; i >= 0; i--) // Iterate backwards over the COPY
+                for (int i = 0; i < componentCount; i++) // Iterate forward
                 {
-                    Component c = componentsToIterate[i]; // Use the copy
+                    Component c = componentsToIterate[i];
...
-                        if (data != null) // Ensure GetComponentData didn't return null
-                        {
-                            componentData.Insert(0, data); // Insert at beginning to maintain original order in final list
-                        }
+                        if (data != null) componentData.Add(data);
...
-                        componentData.Insert(0, new JObject(
+                        componentData.Add(new JObject(

1647-1656: NonPublic [SerializeField] lookup should be case-sensitive.

Using IgnoreCase here risks writing the wrong private backing field. Keep it strict for NonPublic, while public members can remain IgnoreCase.

-                        var npField = type.GetField(memberName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase);
+                        var npField = type.GetField(memberName, BindingFlags.NonPublic | BindingFlags.Instance);

1782-1854: Skip redundant writes to avoid unnecessary dirty flags.

Optionally compare current and new values before setting nested fields/properties, mirroring your equality check patterns elsewhere. Helps reduce prefab/scene churn.

-                    if (convertedValue != null || value.Type == JTokenType.Null)
+                    if (convertedValue != null || value.Type == JTokenType.Null)
                     {
-                        finalPropInfo.SetValue(currentObject, convertedValue);
+                        if (!Equals(finalPropInfo.GetValue(currentObject), convertedValue))
+                            finalPropInfo.SetValue(currentObject, convertedValue);
                         return true;
                     }
...
-                        if (convertedValue != null || value.Type == JTokenType.Null)
+                        if (convertedValue != null || value.Type == JTokenType.Null)
                         {
-                            finalFieldInfo.SetValue(currentObject, convertedValue);
+                            if (!Equals(finalFieldInfo.GetValue(currentObject), convertedValue))
+                                finalFieldInfo.SetValue(currentObject, convertedValue);
                             return true;
                         }

2070-2080: Use ordinal comparison for namespace prefix check.

String.StartsWith without a comparison can be culture-sensitive. Use Ordinal.

-                 targetType.FullName.StartsWith("UnityEngine.U2D") || // Sprites etc.
+                 targetType.FullName.StartsWith("UnityEngine.U2D", StringComparison.Ordinal) || // Sprites etc.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 17ad011 and 43abb00.

📒 Files selected for processing (3)
  • TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/TestAsmdef.asmdef (1 hunks)
  • UnityMcpBridge/Editor/Tools/ManageAsset.cs (4 hunks)
  • UnityMcpBridge/Editor/Tools/ManageGameObject.cs (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/TestAsmdef.asmdef
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: dsarno
PR: CoplayDev/unity-mcp#0
File: :0-0
Timestamp: 2025-09-03T16:00:55.812Z
Learning: ComponentResolver in UnityMcpBridge/Editor/Tools/ManageGameObject.cs is a nested static class within ManageGameObject, not a sibling type. The `using static MCPForUnity.Editor.Tools.ManageGameObject;` import is required to access ComponentResolver methods directly without the outer class qualifier.
📚 Learning: 2025-09-03T16:00:55.812Z
Learnt from: dsarno
PR: CoplayDev/unity-mcp#0
File: :0-0
Timestamp: 2025-09-03T16:00:55.812Z
Learning: ComponentResolver in UnityMcpBridge/Editor/Tools/ManageGameObject.cs is a nested static class within ManageGameObject, not a sibling type. The `using static MCPForUnity.Editor.Tools.ManageGameObject;` import is required to access ComponentResolver methods directly without the outer class qualifier.

Applied to files:

  • UnityMcpBridge/Editor/Tools/ManageAsset.cs
  • UnityMcpBridge/Editor/Tools/ManageGameObject.cs
🧬 Code graph analysis (2)
UnityMcpBridge/Editor/Tools/ManageAsset.cs (2)
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (4)
  • ManageGameObject (22-2173)
  • Type (2158-2172)
  • ComponentResolver (2179-2439)
  • TryResolve (2189-2225)
UnityMcpBridge/Editor/Helpers/Response.cs (2)
  • Response (10-61)
  • Error (41-60)
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (5)
UnityMcpBridge/Editor/Tools/ManageScript.cs (1)
  • HandleCommand (111-284)
UnityMcpBridge/Runtime/Serialization/UnityTypeConverters.cs (9)
  • Vector3Converter (11-34)
  • Vector2Converter (36-56)
  • QuaternionConverter (58-84)
  • ColorConverter (86-112)
  • RectConverter (114-140)
  • BoundsConverter (142-161)
  • UnityEngineObjectConverter (164-265)
  • UnityEngine (222-264)
  • Vector3 (25-33)
UnityMcpBridge/Editor/Tools/ManageAsset.cs (2)
  • HandleCommand (45-110)
  • ConvertJTokenToType (1157-1234)
UnityMcpBridge/Editor/Helpers/Response.cs (2)
  • Response (10-61)
  • Error (41-60)
UnityMcpBridge/Editor/Helpers/Vector3Helper.cs (1)
  • Vector3 (17-22)
🔇 Additional comments (5)
UnityMcpBridge/Editor/Tools/ManageAsset.cs (3)

10-10: Keep the static using (required to reach nested ComponentResolver).

This is correct given ComponentResolver is a nested static type inside ManageGameObject. Avoid removing it.


205-215: Good switch to resolver with clear error surface.

Replacing ad-hoc type lookup with ComponentResolver.TryResolve and echoing the resolver error in the message improves diagnostics for ScriptableObject creation.


953-955: Operator-precedence fix for float property guard is correct.

The new grouping ensures a non-empty name and numeric value are both required.

UnityMcpBridge/Editor/Tools/ManageGameObject.cs (2)

24-37: Shared JsonSerializer with converters: good allocation and consistency win.

Centralizing converters and reusing a single serializer reduces GC and keeps conversions uniform across paths.


2155-2172: Good: FindType now delegates to resolver and logs resolver context.

This aligns the whole toolchain on the centralized resolution logic and surfaces ambiguity/not-found hints.

…perations

- Change from fail-fast to collect-and-continue at component iteration level
- Previously: first component error would halt processing of remaining components
- Now: all components are processed, errors collected and returned together
- Maintain existing collect-and-continue behavior within individual components
- Add comprehensive tests validating the collect-and-continue behavior works correctly
- All valid properties are applied even when invalid ones fail
- Processing continues through exceptions with proper error aggregation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

coderabbitai bot commented Sep 3, 2025

Tip

For best results, initiate chat on the files or code changes.

@dsarno Thank you for the correction! You're absolutely right about the using static import. I can see from the code that ComponentResolver is indeed a nested static class within ManageGameObject, so the static import is necessary to access ComponentResolver.TryResolve() directly rather than having to write ManageGameObject.ComponentResolver.TryResolve().

The automated review comment incorrectly assumed ComponentResolver was a sibling type in the same namespace. Your clarification about the nested class architecture and the resulting compilation errors (CS0117) without the static import is spot on.

I appreciate you taking the time to correct this - it helps improve the accuracy of future reviews!


🧠 Learnings used
Learnt from: dsarno
PR: CoplayDev/unity-mcp#0
File: :0-0
Timestamp: 2025-09-03T16:00:55.812Z
Learning: ComponentResolver in UnityMcpBridge/Editor/Tools/ManageGameObject.cs is a nested static class within ManageGameObject, not a sibling type. The `using static MCPForUnity.Editor.Tools.ManageGameObject;` import is required to access ComponentResolver methods directly without the outer class qualifier.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (1)

787-813: Good: collect-and-continue on component property setting

This addresses the earlier early-return feedback and improves UX.

🧹 Nitpick comments (24)
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (17)

1-1: Avoid file-wide nullable suppression

Replace the global #nullable disable with local, targeted pragmas where needed, or better, enable nullable and annotate. Keeps null-safety intact across the file.

-#nullable disable
+// Consider removing global nullable suppression. Prefer local pragmas or proper annotations.

24-37: Shared JsonSerializer: good reuse; add minor safety tweaks

LGTM on centralizing converters. Consider:

  • Marking FloatParseHandling = FloatParseHandling.Double and Culture to Invariant for deterministic numeric parsing.
  • Making the serializer Lazy<> to guard static init order and potential editor domain reload quirks (optional).
- private static readonly JsonSerializer InputSerializer = JsonSerializer.Create(new JsonSerializerSettings
+ private static readonly Lazy<JsonSerializer> _inputSerializer = new(() => JsonSerializer.Create(new JsonSerializerSettings
  {
    Converters = new List<JsonConverter>
    {
      new Vector3Converter(),
      new Vector2Converter(), 
      new QuaternionConverter(),
      new ColorConverter(),
      new RectConverter(),
      new BoundsConverter(),
      new UnityEngineObjectConverter()
-   }
- });
+   },
+   FloatParseHandling = FloatParseHandling.Double,
+   Culture = System.Globalization.CultureInfo.InvariantCulture,
+ }));
+ private static JsonSerializer InputSerializer => _inputSerializer.Value;

56-57: Support both 'searchMethod' and 'search_method'

Tests and callers sometimes send snake_case. Read both keys to avoid silent fallback.

- string searchMethod = @params["searchMethod"]?.ToString().ToLower();
+ string searchMethod = (@params["searchMethod"] ?? @params["search_method"])?.ToString().ToLower();

193-231: Prefab name search: use name: filter for precision

Use AssetDatabase name: filter to avoid substring matches and reduce ambiguity.

- string[] guids = AssetDatabase.FindAssets($"t:Prefab {prefabNameOnly}");
+ string[] guids = AssetDatabase.FindAssets($"t:Prefab name:{prefabNameOnly}");

302-317: Minor: validate name before creating primitive

Avoid creating then immediately destroying when name is missing.

- newGo = GameObject.CreatePrimitive(type);
- // Set name *after* creation for primitives
- if (!string.IsNullOrEmpty(name))
- {
-     newGo.name = name;
- }
- else
- {
-     UnityEngine.Object.DestroyImmediate(newGo); // cleanup leak
-     return Response.Error("'name' parameter is required when creating a primitive.");
- }
+ if (string.IsNullOrEmpty(name))
+     return Response.Error("'name' parameter is required when creating a primitive.");
+ newGo = GameObject.CreatePrimitive(type);
+ newGo.name = name;

374-385: Deduplicate ParseVector3: reuse Vector3Helper

You already have Helpers.Vector3Helper.ParseVector3. Remove the local helper and call the shared one for consistency.

- Vector3? position = ParseVector3(@params["position"] as JArray);
- Vector3? rotation = ParseVector3(@params["rotation"] as JArray);
- Vector3? scale = ParseVector3(@params["scale"] as JArray);
+ Vector3? position = MCPForUnity.Editor.Helpers.Vector3Helper.ParseVector3(@params["position"] as JArray);
+ Vector3? rotation = MCPForUnity.Editor.Helpers.Vector3Helper.ParseVector3(@params["rotation"] as JArray);
+ Vector3? scale = MCPForUnity.Editor.Helpers.Vector3Helper.ParseVector3(@params["scale"] as JArray);
- /// <summary>
- /// Parses a JArray like [x, y, z] into a Vector3.
- /// </summary>
- private static Vector3? ParseVector3(JArray array)
- {
-     if (array != null && array.Count == 3)
-     {
-         try
-         {
-             return new Vector3(
-                 array[0].ToObject<float>(),
-                 array[1].ToObject<float>(),
-                 array[2].ToObject<float>()
-             );
-         }
-         catch (Exception ex)
-         {
-             Debug.LogWarning($"Failed to parse JArray as Vector3: {array}. Error: {ex.Message}");
-         }
-     }
-     return null;
- }
+ // Removed: use Vector3Helper.ParseVector3 instead

Also applies to: 1141-1162


814-822: Consider returning partial-success instead of hard error

Currently any component error flips the whole modify to Response.Error. Consider returning Success with a data.errors array for partial success, or include a status field, to better signal that some changes did apply.

Would you prefer Success with { warnings/errors } when at least one property applied?


913-984: Reverse-iterate + Insert(0, ...) is O(n^2)

Since you copied the array, just iterate forward and Add to maintain order. This reduces allocations and shifts.

- for (int i = componentCount - 1; i >= 0; i--)
- {
-     Component c = componentsToIterate[i];
-     ...
-     if (data != null)
-     {
-         componentData.Insert(0, data);
-     }
-     ...
-     componentData.Insert(0, new JObject(...));
- }
+ for (int i = 0; i < componentCount; i++)
+ {
+     Component c = componentsToIterate[i];
+     ...
+     if (data != null)
+     {
+         componentData.Add(data);
+     }
+     ...
+     componentData.Add(new JObject(...));
+ }

1524-1584: Differentiate “not found” from “conversion failed”

SetProperty returns false for multiple reasons; emitting “Property not found” on conversion errors is misleading (see velocity test). Return a reason enum to tailor messages.

- bool setResult = SetProperty(targetComponent, propName, propValue);
- if (!setResult)
+ var setResult = SetProperty(targetComponent, propName, propValue, out var reason);
+ if (!setResult)
  {
-   var availableProperties = ComponentResolver.GetAllComponentProperties(targetComponent.GetType());
-   var suggestions = ComponentResolver.GetAIPropertySuggestions(propName, availableProperties);
-   var msg = suggestions.Any()
-       ? $"Property '{propName}' not found. Did you mean: {string.Join(", ", suggestions)}? Available: [{string.Join(", ", availableProperties)}]"
-       : $"Property '{propName}' not found. Available: [{string.Join(", ", availableProperties)}]";
+   string msg;
+   if (reason == SetFailureReason.NotFound)
+   {
+     var availableProperties = ComponentResolver.GetAllComponentProperties(targetComponent.GetType());
+     var suggestions = ComponentResolver.GetAIPropertySuggestions(propName, availableProperties);
+     msg = suggestions.Any()
+       ? $"Property '{propName}' not found. Did you mean: {string.Join(", ", suggestions)}? Available: [{string.Join(", ", availableProperties)}]"
+       : $"Property '{propName}' not found. Available: [{string.Join(", ", availableProperties)}]";
+   }
+   else
+   {
+     msg = $"Failed to convert value for '{propName}'. See previous errors for details.";
+   }
    Debug.LogWarning($"[ManageGameObject] {msg}");
    failures.Add(msg);
  }

Add this minimal helper near SetProperty:

+ private enum SetFailureReason { Unknown, NotFound, ConversionFailed }

And adjust SetProperty signature to set reason accordingly.


1591-1661: Tighten NonPublic field lookup: drop IgnoreCase

Case-insensitive matching on private fields increases risk of touching the wrong backing field; keep IgnoreCase for public, not for NonPublic.

- var npField = type.GetField(memberName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase);
+ var npField = type.GetField(memberName, BindingFlags.NonPublic | BindingFlags.Instance);

1669-1865: Nested property setter: solid coverage; add IList/arrays beyond Material

The traversal is robust. Consider supporting typed arrays beyond Material[] (e.g., Component[], Vector4[]), since you already handle IList.


1907-1946: Exception rethrow policy

ConvertJTokenToType rethrows after logging; SetProperty catches and logs again, leading to double logs. Either don’t rethrow here or suppress the outer log on JsonSerializationException to reduce noise.

- catch (JsonSerializationException jsonEx)
- {
-   Debug.LogError(...);
-   throw;
- }
+ catch (JsonSerializationException jsonEx)
+ {
+   Debug.LogError(...);
+   return null; // Let caller handle a null conversion result.
+ }

2173-2219: TryResolve: case-sensitivity and UX

Current Ordinal match enforces exact case. Consider OrdinalIgnoreCase for short names to be forgiving during interactive use.

- if (CacheByFqn.TryGetValue(nameOrFullName, out type)) return true;
- if (!nameOrFullName.Contains(".") && CacheByName.TryGetValue(nameOrFullName, out type)) return true;
+ if (CacheByFqn.TryGetValue(nameOrFullName, out type)) return true;
+ if (!nameOrFullName.Contains("."))
+ {
+     if (CacheByName.TryGetValue(nameOrFullName, out type)) return true;
+     if (CacheByName.TryGetValue(nameOrFullName, out type, StringComparer.OrdinalIgnoreCase)) return true; // optional helper overload
+ }

Alternatively, normalize keying to OrdinalIgnoreCase for CacheByName.


2234-2271: Cache player assembly names

CompilationPipeline.GetAssemblies is relatively heavy; cache the player assembly name set.

+#if UNITY_EDITOR
+ private static readonly Lazy<HashSet<string>> PlayerAsmNames = new(() =>
+     new HashSet<string>(
+         CompilationPipeline.GetAssemblies(AssembliesType.Player).Select(a => a.name),
+         StringComparer.Ordinal));
+#endif
...
- var playerAsmNames = new HashSet<string>(
-     UnityEditor.Compilation.CompilationPipeline.GetAssemblies(UnityEditor.Compilation.AssembliesType.Player).Select(a => a.name),
-     StringComparer.Ordinal);
- IEnumerable<System.Reflection.Assembly> playerAsms = loaded.Where(a => playerAsmNames.Contains(a.GetName().Name));
+ IEnumerable<System.Reflection.Assembly> playerAsms = loaded.Where(a => PlayerAsmNames.Value.Contains(a.GetName().Name));
  IEnumerable<System.Reflection.Assembly> editorAsms = loaded.Except(playerAsms);
...
- var player = new HashSet<string>(
-     UnityEditor.Compilation.CompilationPipeline.GetAssemblies(UnityEditor.Compilation.AssembliesType.Player).Select(a => a.name),
-     StringComparer.Ordinal);
- return seq.OrderBy(t => player.Contains(t.Assembly.GetName().Name) ? 0 : 1);
+ return seq.OrderBy(t => PlayerAsmNames.Value.Contains(t.Assembly.GetName().Name) ? 0 : 1);

Also applies to: 2274-2282


2294-2312: GetAllComponentProperties: consider caching per type

Compute-once-and-cache per Type to avoid repeated reflection during suggestion/error flows.


2316-2355: Remove unused 'prompt' construction

The prompt string is built then discarded. Drop it to reduce GC pressure.

- var prompt = $"A Unity developer is trying to set a component property but used an incorrect name.\n\n" +
-              ...
-              $"- \"Move Speed\" → \"moveSpeed, movementSpeed\"";
-
 // For now, we'll use a simple rule-based approach that mimics AI behavior
 // This can be replaced with actual AI calls later
 var suggestions = GetRuleBasedSuggestions(userInput, availableProperties);

2429-2430: Comment contradicts reality

“Removed duplicate ParseVector3 - using the one at line 1114” but a local ParseVector3 still exists above. After dedup (see earlier comment), update this note or remove it.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs (7)

33-41: Assert Response shape, not just non-null

Convert the result to JObject and assert success == false for error cases to make tests meaningful.

- Assert.IsNotNull(result, "Should return a result object");
+ var jo = JObject.FromObject(result);
+ Assert.IsFalse((bool)jo["success"], "Should be an error response");
+ StringAssert.Contains("cannot be null", (string)jo["error"]);

42-50: Same as above: verify error on empty params

Also assert the specific error message ("Action parameter is required.").

- Assert.IsNotNull(result, "Should return a result object for empty params");
+ var jo = JObject.FromObject(result);
+ Assert.IsFalse((bool)jo["success"]);
+ StringAssert.Contains("Action parameter is required", (string)jo["error"]);

95-110: Built-ins loop: nice; handle abstract types comment

LGTM. You might also include “MeshRenderer” to cover concrete subclasses.


176-197: Cache perf test: reduce flakiness

Use a more lenient ratio or assert secondCallTime <= firstCallTime + epsilon. Editor environments can be noisy.

- Assert.LessOrEqual(secondCallTime, firstCallTime * 2, "Cached call should not be significantly slower");
+ Assert.LessOrEqual(secondCallTime, firstCallTime * 3 + 1.0, "Cached call should not be materially slower");

199-227: Use 'searchMethod' to match API

You pass search_method but the handler consumes searchMethod. Either update tests or rely on alias support (suggested in code). Updating tests keeps them stricter.

- ["search_method"] = "by_name", 
+ ["searchMethod"] = "by_name", 

Also applies to: 276-290


235-247: Drop noisy debug logs in tests

The BEFORE/AFTER Debug.Log lines add noise without assertions. Remove to keep logs clean.

- Debug.Log($"BEFORE TEST - Mass: {rigidbody.mass}, UseGravity: {rigidbody.useGravity}");
...
- Debug.Log($"AFTER TEST - Mass: {rigidbody.mass}, UseGravity: {rigidbody.useGravity}");
- Debug.Log($"AFTER TEST - LocalPosition: {transform.localPosition}");
- Debug.Log($"AFTER TEST - LocalScale: {transform.localScale}");

292-300: Log expectations: robust but brittle regex

Consider anchoring patterns less tightly or asserting counts to avoid coupling to message wording if you adopt the “conversion failed” vs “not found” refinement.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 43abb00 and 7156009.

📒 Files selected for processing (2)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs (1 hunks)
  • UnityMcpBridge/Editor/Tools/ManageGameObject.cs (11 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: dsarno
PR: CoplayDev/unity-mcp#0
File: :0-0
Timestamp: 2025-09-03T16:00:55.812Z
Learning: ComponentResolver in UnityMcpBridge/Editor/Tools/ManageGameObject.cs is a nested static class within ManageGameObject, not a sibling type. The `using static MCPForUnity.Editor.Tools.ManageGameObject;` import is required to access ComponentResolver methods directly without the outer class qualifier.
📚 Learning: 2025-09-03T16:00:55.812Z
Learnt from: dsarno
PR: CoplayDev/unity-mcp#0
File: :0-0
Timestamp: 2025-09-03T16:00:55.812Z
Learning: ComponentResolver in UnityMcpBridge/Editor/Tools/ManageGameObject.cs is a nested static class within ManageGameObject, not a sibling type. The `using static MCPForUnity.Editor.Tools.ManageGameObject;` import is required to access ComponentResolver methods directly without the outer class qualifier.

Applied to files:

  • UnityMcpBridge/Editor/Tools/ManageGameObject.cs
🧬 Code graph analysis (2)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs (1)
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (14)
  • UnityEngine (2048-2145)
  • GameObject (1167-1190)
  • ManageGameObject (22-2167)
  • HandleCommand (41-174)
  • ComponentResolver (2173-2433)
  • TryResolve (2183-2219)
  • Type (2152-2166)
  • List (1195-1354)
  • List (2234-2271)
  • List (2294-2312)
  • List (2317-2356)
  • List (2364-2399)
  • Vector3 (1144-1162)
  • Vector3 (1951-1966)
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (6)
UnityMcpBridge/Editor/Tools/ManageScript.cs (1)
  • System (2267-2333)
UnityMcpBridge/Runtime/Serialization/UnityTypeConverters.cs (4)
  • Vector3Converter (11-34)
  • UnityEngineObjectConverter (164-265)
  • UnityEngine (222-264)
  • Vector3 (25-33)
UnityMcpBridge/Editor/Tools/ManageAsset.cs (1)
  • ConvertJTokenToType (1157-1234)
UnityMcpBridge/Editor/Helpers/Response.cs (2)
  • Response (10-61)
  • Error (41-60)
UnityMcpBridge/Editor/Helpers/Vector3Helper.cs (1)
  • Vector3 (17-22)
UnityMcpBridge/Editor/Helpers/GameObjectSerializer.cs (1)
  • JToken (506-525)
🔇 Additional comments (6)
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (2)

2149-2166: FindType: good delegation and context logging

Clean pass-through to ComponentResolver with helpful warnings. LGTM.


2363-2398: Nice: normalization + Levenshtein with 3-suggestion cap

Solid, pragmatic heuristics here. LGTM.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs (4)

72-81: Resolver integration: good coverage

Directly validating Transform resolution and error being empty is perfect.


112-128: AI suggestions test: good signal

Exercising “Use Gravity” → “useGravity” validates normalization. Looks good.


129-138: Property set for MonoBehaviour: OK

Covers base Component properties. LGTM.


140-159: Case variations table: concise and valuable

Good data-driven coverage across cases/separators. LGTM.

@dsarno dsarno merged commit 4401de1 into CoplayDev:main Sep 3, 2025
1 check passed
@dsarno dsarno deleted the feat/robust-component-resolver branch September 5, 2025 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant