Skip to content

Conversation

@dsarno
Copy link
Collaborator

@dsarno dsarno commented Jan 26, 2026

Summary

Follow-up to #611 (Prefab Feature Updates). Addresses remaining issues from #97 (Prefab Editor Inspection & Modification Support).

Changes

  1. Mark prefab stage scene as dirty in ManageComponents: When manage_components adds, removes, or modifies components on a GameObject in an open prefab stage, the prefab stage scene is now marked as dirty. This ensures save_open_stage correctly detects unsaved changes.

  2. Prefab root rename without dialog: When renaming the root GameObject of an open prefab stage via manage_gameobject, the prefab asset file is now automatically renamed to match. This prevents Unity dialog from interrupting automated workflows.

  3. Test fix: Fixed GetHierarchy_IncludesNestingInfo_ForNestedPrefabs test:

    • Fixed cleanup order (nested container must be deleted before the prefabs it references)
    • Removed incorrect LogAssert.Expect for an error that does not occur
  4. New prefab MCP resources (per @msanatan suggestion):

    • mcpforunity://prefab-api - Documentation for prefab resources
    • mcpforunity://prefab/{encoded_path} - Get prefab asset info by URL-encoded path
    • mcpforunity://prefab/{encoded_path}/hierarchy - Get full prefab hierarchy with nested prefab info

Test plan

  • Verified prefab root rename works without triggering Unity dialog
  • Verified prefab asset is renamed alongside the root GameObject
  • Verified component changes in prefab stage are saved correctly
  • Tested new prefab resources work correctly
  • All 22 ManagePrefabsCrudTests pass

Generated with Claude Code

Summary by Sourcery

Ensure prefab modifications in Unity are properly tracked and expose new read-only prefab resource endpoints.

New Features:

  • Add MCP prefab resources to retrieve prefab metadata and full prefab hierarchies by URL-encoded asset path, plus a helper prefab API documentation resource.

Bug Fixes:

  • Mark component add/remove/property changes in prefab stages as dirty so unsaved prefab edits are detected and saved.
  • Rename prefab asset files automatically when renaming the root GameObject in an open prefab stage, avoiding blocking Unity dialogs and handling conflicts/errors.
  • Fix nested prefab hierarchy test cleanup ordering and remove an invalid expected error log.

Tests:

  • Adjust ManagePrefabsCrudTests nested prefab hierarchy test to reflect correct behavior and asset lifecycle.

Summary by CodeRabbit

  • New Features

    • Read-only prefab endpoints: fetch prefab info and full prefab hierarchy from Unity.
  • Improvements

    • Renaming a prefab root now updates the underlying asset name.
    • Scene state now correctly marked dirty after component add/remove and property changes in prefab stages.
  • Documentation

    • Available resources list updated to include prefab endpoints.
  • Tests

    • Prefab-related tests adjusted to match updated behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 26, 2026

Reviewer's Guide

Marks prefab stage scenes dirty when modifying components, adds automatic prefab asset renaming when the prefab root is renamed, fixes a nested prefab hierarchy test’s expectations and cleanup order, and introduces new MCP prefab resources for querying prefab info and hierarchies from Unity.

Sequence diagram for prefab info MCP resource request

sequenceDiagram
    actor Client
    participant MCPServer
    participant PrefabResource as PrefabResourceService
    participant UnityInstance
    participant UnityTransport as UnityTransportLayer
    participant UnityEditor

    Client->>MCPServer: HTTP request for mcpforunity://prefab/{encoded_path}
    MCPServer->>PrefabResource: get_prefab_info(ctx, encoded_path)
    PrefabResource->>PrefabResource: _decode_prefab_path(encoded_path)
    PrefabResource->>UnityInstance: get_unity_instance_from_context(ctx)
    PrefabResource->>UnityTransport: send_with_unity_instance(async_send_command_with_retry unity_instance manage_prefabs { action: get_info, prefabPath: decoded_path })
    UnityTransport->>UnityEditor: manage_prefabs get_info(prefabPath)
    UnityEditor-->>UnityTransport: Prefab info response dict
    UnityTransport-->>PrefabResource: response
    PrefabResource->>PrefabResource: _normalize_response(response)
    PrefabResource-->>MCPServer: PrefabInfoResponse
    MCPServer-->>Client: Serialized MCPResponse
Loading

Class diagram for new prefab MCP resource models

classDiagram
    class MCPResponse {
        +bool success
        +str message
        +Any data
    }

    class PrefabInfoData {
        +str assetPath
        +str guid
        +str prefabType
        +str rootObjectName
        +list~str~ rootComponentTypes
        +int childCount
        +bool isVariant
        +str parentPrefab
    }

    class PrefabInfoResponse {
        +PrefabInfoData data
    }

    class PrefabHierarchyItem {
        +str name
        +int instanceId
        +str path
        +bool activeSelf
        +int childCount
        +list~str~ componentTypes
        +dict~str, Any~ prefab
    }

    class PrefabHierarchyData {
        +str prefabPath
        +int total
        +list~PrefabHierarchyItem~ items
    }

    class PrefabHierarchyResponse {
        +PrefabHierarchyData data
    }

    MCPResponse <|-- PrefabInfoResponse
    MCPResponse <|-- PrefabHierarchyResponse
    PrefabInfoResponse o--> PrefabInfoData
    PrefabHierarchyResponse o--> PrefabHierarchyData
    PrefabHierarchyData o--> PrefabHierarchyItem
Loading

File-Level Changes

Change Details Files
Ensure component operations in ManageComponents correctly mark the owning scene or prefab stage as dirty so prefab changes are detected and saved.
  • Import UnityEditor.SceneManagement to access EditorSceneManager APIs.
  • After adding a component, mark either the current prefab stage scene or the target GameObject’s scene as dirty.
  • After removing a component, mark either the current prefab stage scene or the target GameObject’s scene as dirty.
  • After setting a component property, mark either the current prefab stage scene or the target GameObject’s scene as dirty.
MCPForUnity/Editor/Tools/ManageComponents.cs
Support automatic prefab asset renaming when the root GameObject of an open prefab stage is renamed, avoiding Unity’s blocking rename dialog and handling conflicts.
  • Detect when the target GameObject is the prefabContentsRoot of the currently open prefab stage before renaming.
  • Compute the new prefab asset path based on the requested name and the existing asset directory.
  • Prevent rename when another prefab already exists at the computed path and return an error response instead.
  • Use AssetDatabase.RenameAsset to rename the prefab asset and return an error response if the rename fails.
  • Log a successful prefab asset rename, then update the GameObject’s name and mark the modify operation as having changed state.
MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs
Correct the nested prefab hierarchy test to match actual behavior and clean up assets in a safe order.
  • Remove the LogAssert.Expect for a nested prefab error that no longer occurs in this test scenario.
  • Adjust the finally block to delete the nested container prefab before deleting the parent and child prefabs it references.
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManagePrefabsCrudTests.cs
Add new MCP resources to expose prefab information and hierarchies via URL-encoded prefab paths, with typed response models and helper documentation.
  • Introduce helper functions to normalize Unity transport responses into MCPResponse and to decode URL-encoded prefab asset paths.
  • Add a static prefab-api resource that returns documentation describing prefab resources, path encoding, and related tools.
  • Define typed Pydantic models for prefab info and hierarchy payloads, including hierarchy items and metadata.
  • Register mcpforunity://prefab/{encoded_path} as a resource that calls manage_prefabs action=get_info for a decoded prefab path.
  • Register mcpforunity://prefab/{encoded_path}/hierarchy as a resource that calls manage_prefabs action=get_hierarchy for a decoded prefab path.
Server/src/services/resources/prefab.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 26, 2026

Warning

Rate limit exceeded

@dsarno has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 15 minutes and 22 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds prefab-root asset rename handling when renaming open-prefab roots, ensures prefab-stage scenes are marked dirty after component edits, and introduces server read-only endpoints to fetch prefab info and hierarchy by encoded path.

Changes

Cohort / File(s) Summary
Prefab rename
MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs
When renaming a prefab root in an open prefab stage, compute a new asset path, validate collisions via GUID, call AssetDatabase.RenameAsset, handle errors, and log the rename.
Component management / scene dirtying
MCPForUnity/Editor/Tools/ManageComponents.cs
Add UnityEditor.SceneManagement, introduce MarkOwningSceneDirty(GameObject), and call it after AddComponent/RemoveComponent/SetProperty to mark prefab-stage or scene dirty.
Server-side prefab resources
Server/src/services/resources/prefab.py
New read-only prefab module exposing docs, prefab_info and prefab_hierarchy endpoints; decode encoded paths, call Unity via transport with retry, normalize responses, and add data models for info and hierarchy.
Tests cleanup
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManagePrefabsCrudTests.cs
Remove nested-prefab warning assertion and reorder cleanup to delete NestedContainer.prefab earlier in teardown.
Docs
README.md
Update available resources list to include prefab_api, prefab_info, and prefab_hierarchy.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client/LLM
    participant Server as MCP Server
    participant Context as Server Context
    participant Transport as Transport Layer
    participant Unity as Unity Editor

    Client->>Server: GET /prefab/{encoded_path} or /prefab/{encoded_path}/hierarchy
    Server->>Server: Decode URL-encoded path
    Server->>Context: Resolve Unity instance from context
    Server->>Transport: Send action ("get_info"/"get_hierarchy") with retry
    Transport->>Unity: Request prefab info/hierarchy
    Unity-->>Transport: Return prefab data
    Transport-->>Server: Deliver response
    Server->>Server: Normalize to PrefabInfo/Hierarchy model
    Server-->>Client: Return JSON response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Suggested reviewers

  • msanatan

Poem

🐰
I hop through prefabs, tidy and bright,
Renaming roots beneath the night,
Scenes wake tidy, components cheer,
Server sings hierarchies clear,
Assets prance — a rabbit's delight. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes all major changes: prefab stage dirty flag, root rename, test fix, and new prefab resources.
Description check ✅ Passed The PR description is comprehensive and follows the template structure with all required sections: Summary, Changes Made, Type of Change, Test plan, and Related Issues.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The ManageComponents changes repeat the same PrefabStageUtility.GetCurrentPrefabStage / EditorSceneManager.MarkSceneDirty block three times; consider extracting this into a small helper method (e.g., MarkOwningSceneDirty(GameObject targetGo)) to avoid duplication and keep future changes to this logic in one place.
  • In prefab.py the specific response models (PrefabInfoResponse, PrefabHierarchyResponse) are defined but never actually used in the resource handlers or _normalize_response; either wire these into the returned types for stronger typing/validation or remove them to avoid dead code.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `ManageComponents` changes repeat the same `PrefabStageUtility.GetCurrentPrefabStage` / `EditorSceneManager.MarkSceneDirty` block three times; consider extracting this into a small helper method (e.g., `MarkOwningSceneDirty(GameObject targetGo)`) to avoid duplication and keep future changes to this logic in one place.
- In `prefab.py` the specific response models (`PrefabInfoResponse`, `PrefabHierarchyResponse`) are defined but never actually used in the resource handlers or `_normalize_response`; either wire these into the returned types for stronger typing/validation or remove them to avoid dead code.

## Individual Comments

### Comment 1
<location> `Server/src/services/resources/prefab.py:21-25` </location>
<code_context>
+from transport.legacy.unity_connection import async_send_command_with_retry
+
+
+def _normalize_response(response: dict | Any) -> MCPResponse:
+    """Normalize Unity transport response to MCPResponse."""
+    if isinstance(response, dict):
+        return MCPResponse(**response)
+    return response
+
+
</code_context>

<issue_to_address>
**issue:** The normalization helper may return different types, which can complicate downstream handling.

`_normalize_response` is annotated to return `MCPResponse`, but if `response` is not a `dict` it returns the original object, so callers may receive non-`MCPResponse` values. Either constrain the input to `MCPResponse | dict` and always return an `MCPResponse`, or update the return type to a union that reflects the actual behavior so downstream code can handle it correctly.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@dsarno dsarno force-pushed the feature/prefab-root-rename-and-text-search branch from bd812b7 to 1e951af Compare January 26, 2026 01:25
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

🤖 Fix all issues with AI agents
In `@MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs`:
- Around line 40-66: The collision check blocks renames when the new filename
equals the current prefab's filename because LoadAssetAtPath returns the same
asset; update the logic in the PrefabStage rename block (symbols:
PrefabStageUtility.GetCurrentPrefabStage, prefabStageForRename,
prefabContentsRoot, targetGo, assetPath, newAssetPath) to first skip any work if
newAssetPath == assetPath, otherwise compare GUIDs via
AssetDatabase.AssetPathToGUID on assetPath and newAssetPath and treat it as a
collision only if the GUIDs differ and the new path already has an asset; only
call AssetDatabase.RenameAsset when the path actually changes and no conflicting
GUID exists, and return the existing error handling (renameError) as before.

@dsarno dsarno force-pushed the feature/prefab-root-rename-and-text-search branch from 1e951af to ae4f931 Compare January 26, 2026 01:29
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

🤖 Fix all issues with AI agents
In
`@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManagePrefabsCrudTests.cs`:
- Around line 444-448: The cleanup contains a duplicate deletion:
SafeDeleteAsset(parentPath) and SafeDeleteAsset(Path.Combine(TempDirectory,
"ParentPrefab.prefab").Replace('\\','/')) both delete the same ParentPrefab;
remove the redundant call so ParentPrefab is deleted only once. Specifically,
keep the deletion that uses the existing parentPath variable and delete the
SafeDeleteAsset(...) that constructs "ParentPrefab.prefab" from TempDirectory
(or vice versa), ensuring the remaining order still deletes NestedContainer,
then the parent (parentPath), then ChildPrefab via SafeDeleteAsset.
♻️ Duplicate comments (2)
MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs (1)

40-66: False collision detection when renaming a prefab root to its current filename.

The collision check at line 53 incorrectly blocks rename operations when the new name matches the current prefab asset filename because LoadAssetAtPath returns the same asset. Use GUID comparison to distinguish between the current asset and actual collisions, and skip the rename operation if the path hasn't changed.

🛠️ Proposed fix
                 // Check if we're renaming the root object of an open prefab stage
                 var prefabStageForRename = PrefabStageUtility.GetCurrentPrefabStage();
                 bool isRenamingPrefabRoot = prefabStageForRename != null &&
                                             prefabStageForRename.prefabContentsRoot == targetGo;

                 if (isRenamingPrefabRoot)
                 {
                     // Rename the prefab asset file to match the new name (avoids Unity dialog)
                     string assetPath = prefabStageForRename.assetPath;
                     string directory = System.IO.Path.GetDirectoryName(assetPath);
                     string newAssetPath = System.IO.Path.Combine(directory, name + ".prefab").Replace('\\', '/');

-                    // Check if a file already exists at the new path
-                    if (AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(newAssetPath) != null)
-                    {
-                        return new ErrorResponse($"Cannot rename prefab root to '{name}': a prefab already exists at '{newAssetPath}'.");
-                    }
-
-                    // Rename the asset file first
-                    string renameError = AssetDatabase.RenameAsset(assetPath, name);
-                    if (!string.IsNullOrEmpty(renameError))
-                    {
-                        return new ErrorResponse($"Failed to rename prefab asset: {renameError}");
-                    }
-
-                    McpLog.Info($"[GameObjectModify] Renamed prefab asset from '{assetPath}' to '{newAssetPath}'");
+                    // Check if a different asset already exists at the new path
+                    string currentGuid = AssetDatabase.AssetPathToGUID(assetPath);
+                    string targetGuid = AssetDatabase.AssetPathToGUID(newAssetPath);
+                    if (!string.IsNullOrEmpty(targetGuid) && targetGuid != currentGuid)
+                    {
+                        return new ErrorResponse($"Cannot rename prefab root to '{name}': a prefab already exists at '{newAssetPath}'.");
+                    }
+
+                    // Rename the asset file only if the path actually changes
+                    if (newAssetPath != assetPath)
+                    {
+                        string renameError = AssetDatabase.RenameAsset(assetPath, name);
+                        if (!string.IsNullOrEmpty(renameError))
+                        {
+                            return new ErrorResponse($"Failed to rename prefab asset: {renameError}");
+                        }
+
+                        McpLog.Info($"[GameObjectModify] Renamed prefab asset from '{assetPath}' to '{newAssetPath}'");
+                    }
                 }
Server/src/services/resources/prefab.py (1)

21-25: Return type inconsistency in _normalize_response.

The function is annotated to return MCPResponse, but when response is not a dict, it returns the original object unchanged (which could be any type). This inconsistency can cause downstream type confusion.

🛠️ Proposed fix
-def _normalize_response(response: dict | Any) -> MCPResponse:
+def _normalize_response(response: dict | MCPResponse | Any) -> MCPResponse:
     """Normalize Unity transport response to MCPResponse."""
     if isinstance(response, dict):
         return MCPResponse(**response)
+    if isinstance(response, MCPResponse):
+        return response
+    # Fallback: wrap unexpected types in an error response
+    return MCPResponse(success=False, error=f"Unexpected response type: {type(response).__name__}")
-    return response
🧹 Nitpick comments (1)
Server/src/services/resources/prefab.py (1)

93-111: Consider using typed response models for better API documentation.

The Pydantic models (PrefabInfoResponse, PrefabHierarchyResponse) are defined but unused, as noted by the TODO comment. While the current implementation works, using these typed responses would provide better IDE support and automatic schema validation.

For now, this is acceptable since the TODO documents the intent for future improvement.

Also applies to: 143-163

…rces

- Mark prefab stage scene as dirty when manage_components adds/removes/
  modifies components, ensuring save_open_stage correctly detects changes

- When renaming the root GameObject of an open prefab stage, also rename
  the prefab asset file to match, preventing Unity's "file name must
  match" dialog from interrupting automated workflows

- Fix ManagePrefabsCrudTests cleanup order: delete NestedContainer.prefab
  before ChildPrefab.prefab to avoid missing prefab reference errors

- Remove incorrect LogAssert.Expect that expected an error that doesn't
  occur in the test scenario

- Add new prefab MCP resources for inspecting prefabs:
  - mcpforunity://prefab-api: Documentation for prefab resources
  - mcpforunity://prefab/{path}: Get prefab asset info
  - mcpforunity://prefab/{path}/hierarchy: Get full prefab hierarchy

Addresses #97 (Prefab Editor Inspection & Modification Support)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@dsarno dsarno closed this Jan 26, 2026
@dsarno dsarno force-pushed the feature/prefab-root-rename-and-text-search branch from ae4f931 to c896abd Compare January 26, 2026 01:31
@dsarno dsarno reopened this Jan 26, 2026
@dsarno dsarno merged commit ed11e30 into CoplayDev:beta Jan 26, 2026
1 check passed
@dsarno dsarno deleted the feature/prefab-root-rename-and-text-search branch January 29, 2026 19:41
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