Skip to content

Conversation

@msanatan
Copy link
Member

@msanatan msanatan commented Jan 7, 2026

As we're making going to a major bump, I've made some changes on the code side that hopefully makes it easier to maintain, mostly because of less duplication + smaller files:

  • Fix a warning with ParseColorOrDefault
  • Split ManageVfx into multiple, more manageable, files. Tested each call to ensure it works the same
  • Split ManageGameObject into multiple, more manageable, files. Tested each call to ensure it works the same
  • Remove v1 of editor state, the recent v2 version is the default editor state now. It's accessible via unity://editor/state to remain consistent with other editor resources
    • Also added classes that explicitly define what data is being sent
  • Async tests are now the default run_tests
  • Changed all resource URIs to be under mcpforunity instead of unity - this should avoid conflicts with other MCPs, and avoid issues with Unity themselves 😅
  • Regenerate .meta files to close GUID confict #524
  • Minor things like code formatting on Python and Unity plugin tools/resources

Summary by CodeRabbit

  • New Features

    • Rich in-editor GameObject tooling (create/modify/delete/duplicate/move + component property ops) and expanded VFX tooling (ParticleSystem, LineRenderer, TrailRenderer). Async test runs now start immediately and support polling.
  • API Updates

    • Public resource URIs migrated from unity:// to mcpforunity://; editor state consolidated into a single canonical snapshot.
  • Behavioral Changes

    • Test runner now saves dirty scenes for all modes.
  • Documentation

    • README and docs updated to reflect new tools, URIs and usage.

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

Tested everything, works like a charm
We know what it's managing
We also update the namespace for ManageVFX
…plementation

Merged EditorStateV2 into EditorState, making get_editor_state the canonical resource. Updated Unity C# to use EditorStateCache directly. Enhanced Python implementation with advice/staleness enrichment, external changes detection, and instance ID inference. Removed duplicate EditorStateV2 resource and legacy fallback mapping.
Added strongly-typed Pydantic models for EditorStateV2 schema in Python and corresponding C# classes with JsonProperty attributes. Updated C# to serialize using typed classes instead of anonymous objects. Python now validates the editor state payload before returning it, catching schema mismatches early.
@msanatan msanatan self-assigned this Jan 7, 2026
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.

Sorry @msanatan, your pull request is larger than the review limit of 300000 diff characters

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 7, 2026

📝 Walkthrough

Walkthrough

Replaced many unity:// URIs with mcpforunity://, consolidated editor snapshotting into a typed EditorStateCache, introduced granular GameObject and VFX editor tooling, converted test runs to a job-based API with polling, tightened transport timeouts, and added parsing/validation helpers (VectorParsing, ParamCoercion) and many Unity editor helpers.

Changes

Cohort / File(s) Summary of changes (attention points)
Editor state & snapshot
\MCPForUnity/Editor/Resources/Editor/EditorState.cs`, `MCPForUnity/Editor/Services/EditorStateCache.cs``
EditorState now returns a typed snapshot via EditorStateCache.GetSnapshot(); snapshot shape moved from anonymous object to strongly-typed classes (property casing changed). EditorStateV2 provider file removed—verify server resource mapping and any consumers.
Server editor-state (removed v2)
\Server/src/services/resources/editor_state_v2.py`, `Server/src/services/resources/editor_state.py``
editor_state_v2.py deleted; server now uses editor_state.get_editor_state with new EditorStateV2* Pydantic models. Confirm no leftover callers of the removed module.
GameObject tools (new)
\MCPForUnity/Editor/Tools/GameObjects/*``
New split handlers: Create/Modify/Delete/Duplicate/MoveRelative, plus facade GameObjectHandlers. Check prefab instantiation/save, Undo/dirty handling, parent/tag/layer logic, and aggregated error propagation.
Component/property helpers
\MCPForUnity/Editor/Tools/GameObjects/ComponentResolver.cs`, `.../GameObjectComponentHelpers.cs``
New type resolution, property enumeration, fuzzy suggestions, nested path parsing, shader property writes, JToken→Type conversion and caching. Audit reflection access and fuzzy-matching correctness.
VFX tooling (new)
\MCPForUnity/Editor/Tools/Vfx/*`, `Server/src/services/tools/manage_vfx.py``
Large new VFX surface (Particle/Line/Trail, optional VFX Graph) with read/write/control handlers and parsing helpers (gradients/curves/colors). Watch conditional UNITY_VFX_GRAPH codepaths, material resolution, and parser edge cases.
ManageGameObject legacy removal
removed \MCPForUnity/Editor/Tools/ManageGameObject.cs``
A monolithic ManageGameObject file was removed and replaced by the new modular GameObjects toolset—verify no stray references remain.
VectorParsing & validation helpers
\MCPForUnity/Editor/Helpers/VectorParsing.cs`, `MCPForUnity/Editor/Helpers/ParamCoercion.cs``
Color parse overload signature changed (removed default-parameter overload; added parameterless and explicit-default). Added ValidateAnimationCurveFormat and ValidateQuaternionFormat. New numeric/token validators in ParamCoercion. Audit callsites for signature changes and dry‑run validation usage.
Run tests → job-based
\MCPForUnity/Editor/Tools/RunTests.cs`, `MCPForUnity/Editor/Services/TestJobManager.cs`, `Server/src/services/tools/run_tests.py``
Run tests now starts a job and returns job_id; new polling endpoint get_test_job. Some TestJobManager methods made internal. Update callers/tests for non-blocking semantics.
Server resource URIs renamed
\Server/src/services/resources/`, `Server/src/services/tools/``
Many mcp_for_unity_resource decorator URIs changed from unity://mcpforunity://. Update clients, docs, and tests that reference resource URIs.
Transport & PluginHub tweaks
\Server/src/transport/plugin_hub.py`, `Server/src/transport/unity_transport.py``
Added FAST_FAIL_TIMEOUT and applied it for fast-path commands; various timeout and messaging refinements. Validate fast-fail behavior and environment-var fallbacks.
Tests, docs & URI updates
\Server/tests/`, `TestProjects/`, `README*`, `docs/*``
Tests and docs updated to new URI scheme and new endpoints; several tests/files removed or relocated—run full test suite to catch contract mismatches.
Many Unity .meta & minor formatting changes
many \*.meta``, whitespace-only edits
Numerous GUID/meta updates (new .meta files added). Watch for GUID collisions (see linked issue).

Sequence Diagram(s)

sequenceDiagram
    %% High-level flow for ManageGameObject command (new modular flow)
    participant Client
    participant Server
    participant Resource as "ManageGameObject Resource"
    participant Editor as "Editor Tool (GameObjectHandlers)"
    participant Unity as "Unity Editor"

    Client->>Server: HTTP/MCP call (mcpforunity://tools/manage_gameobject)
    Server->>Resource: validate & route command
    Resource->>Editor: invoke GameObjectHandlers.Create/Modify/Delete/...
    Editor->>Unity: scene operations (instantiate/prefab/transform/components/Undo)
    Unity-->>Editor: operation result (success/data or error)
    Editor-->>Resource: serialize SuccessResponse / ErrorResponse
    Resource-->>Server: deliver response
    Server-->>Client: HTTP/MCP response
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

  • #525 — TestRunnerService: the change unconditionally calling SaveDirtyScenesIfNeeded (all modes) aligns with the objective in this issue (dirty-scene save behavior).

Possibly related PRs

Suggested reviewers

  • dsarno
  • JosvanderWesthuizen
  • Scriptwonder

Poem

"I hopped through code with tiny paws,
I swapped your URIs and fixed the flaws.
I parsed your colors, nudged a prefab tree,
I started test jobs, and set scenes free.
A rabbit’s refactor — hop, review, glee! 🐇"

🚥 Pre-merge checks | ✅ 2 | ❌ 3
❌ Failed checks (1 warning, 2 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'v9 pre-release pruning' is concise but vague, using a generic term 'pruning' that doesn't clearly convey the specific main changes (refactoring, URI scheme migration, GUID fixes, or async test defaults). Consider using a more specific title that highlights the primary change, such as 'Refactor ManageVfx/GameObjects, migrate URIs to mcpforunity scheme, and fix GUID conflicts' or focus on the most significant aspect.
Out of Scope Changes check ❓ Inconclusive While the PR's primary scope includes GUID fixes, refactoring, and URI migration, it also introduces substantial new VFX and GameObject tool implementations, async test infrastructure changes, and editor state restructuring that extend beyond simple pruning or GUID resolution. Clarify whether the new tool implementations (LineCreate, LineRead, ParticleControl, etc.) and server-side async test changes are necessary for v9 pre-release or should be in a separate feature PR to maintain focused change scope.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR directly addresses issue #524 by regenerating .meta files to resolve GUID conflicts. The changeset includes systematic GUID updates across multiple affected assets, matching the stated objective to prevent asset conflicts.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

@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: 9

🤖 Fix all issues with AI agents
In @MCPForUnity/Editor/Services/EditorStateCache.cs:
- Around line 156-169: The C# model EditorStateV2Assets is missing the fields
external_changes_dirty_since_unix_ms and external_changes_last_cleared_unix_ms
present on the Python side; add nullable long properties named
ExternalChangesDirtySinceUnixMs and ExternalChangesLastClearedUnixMs (with
matching JsonProperty names "external_changes_dirty_since_unix_ms" and
"external_changes_last_cleared_unix_ms") to EditorStateV2Assets and update
BuildSnapshot to populate and persist these properties when constructing the
EditorStateV2Assets instance so the serialized schema matches the server model.

In @MCPForUnity/Editor/Tools/GameObjects/GameObjectDelete.cs:
- Around line 21-40: The message construction is using reflection on the
anonymous object in deletedObjects to get the name; instead capture the first
deleted name when building deletedObjects (e.g., store firstName = goName when
adding the first item) or use a concrete type for entries instead of anonymous
objects, then use that firstName (or the typed object's name property) in the
ternary that builds message for SuccessResponse; update the message expression
that references deletedObjects[0].GetType().GetProperty("name").GetValue(...) to
use the stored firstName (or typed entry.name) and keep deletedObjects as the
payload for SuccessResponse.

In @MCPForUnity/Editor/Tools/GameObjects/GameObjectDuplicate.cs:
- Around line 59-62: The warning in ManageGameObject.Duplicate (McpLog.Warn
call) is misleading because when the requested parent token isn't found the
duplicate stays at root (from Instantiate) rather than "keeping original
parent"; update the warning message to accurately state that the parent token
wasn't found and the duplicate remains at root level (include parentToken in the
message for context) so logs reflect actual behavior.

In @MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs:
- Around line 190-214: The empty catch in the reflection loop that builds
aggregatedErrors from componentErrors hides exceptions; update the catch to log
the exception and context (e.g., include which errorObj/type caused it) using
UnityEngine.Debug.LogException or Debug.LogError so failures in
dataProp/errorsProp reflection are visible, then continue processing; reference
the loop handling componentErrors, the reflection variables
dataProp/dataVal/errorsProp/errorsEnum and the aggregatedErrors list when adding
the logging and preserving existing behavior.

In @MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs:
- Around line 47-54: The switch in the Control method that switches on the
action string (cases "play","stop","pause","restart","clear") lacks a default
branch; update the switch inside the Control method in ParticleControl.cs to add
a default case that handles unknown action values by returning or throwing an
appropriate error response (e.g., log an error and return a failure status or
throw an ArgumentException) so callers receive a clear failure for unrecognized
actions; ensure the default references the same response mechanism used by
Control (or the method's return type) and include the unknown action value in
the message for debugging.

In @MCPForUnity/Editor/Tools/Vfx/ParticleRead.cs:
- Around line 31-47: ParticleRead.GetInfo currently reads MinMaxCurve fields by
using .constant only, losing the MinMaxCurve.mode needed by the write side
(ParticleWrite.SetMain/SetEmission and ParticleCommon.ParseMinMaxCurve); update
GetInfo to include the curve mode and corresponding data for each MinMaxCurve
(startLifetime, startSpeed, startSize, gravityModifier, and
emission.rateOverTime): include a mode field (e.g., Constant,
TwoConstants/RandomBetweenConstants, Curve) plus either constant value, min/max
values, or serialized AnimationCurve + multiplier so consumers can reconstruct
via ParticleCommon.ParseMinMaxCurve when writing.

In @MCPForUnity/Editor/Tools/Vfx/ParticleWrite.cs:
- Around line 1-11: Remove the unused using directive for the
MCPForUnity.Editor.Tools.GameObjects namespace at the top of ParticleWrite.cs;
keep the existing MCPForUnity.Editor.Helpers import (which provides
ObjectResolver) and ensure there are no references to any types from
MCPForUnity.Editor.Tools.GameObjects (e.g., check within the ParticleWrite class
and related methods) before deleting the using line to avoid compilation errors.
🧹 Nitpick comments (25)
Server/src/services/tools/find_in_file.py (1)

16-65: Consider extracting _split_uri to a shared module.

This function is duplicated verbatim in manage_script.py (lines 14-63). Consider extracting it to a common utility module to reduce duplication and ensure consistent behavior across both tools.

Server/src/services/tools/manage_vfx.py (1)

42-43: Consider iterable unpacking for cleaner list construction.

The static analysis tool (Ruff RUF005) suggests using iterable unpacking instead of concatenation. This is slightly more idiomatic and avoids creating intermediate lists.

♻️ Suggested refactor
-ALL_ACTIONS = ["ping"] + PARTICLE_ACTIONS + \
-    VFX_ACTIONS + LINE_ACTIONS + TRAIL_ACTIONS
+ALL_ACTIONS = ["ping", *PARTICLE_ACTIONS, *VFX_ACTIONS, *LINE_ACTIONS, *TRAIL_ACTIONS]
Server/src/services/tools/batch_execute.py (1)

45-46: LGTM - Improved readability.

The reformatted error messages improve readability without changing functionality.

Optional: Consider using TypeError for type validation.

Lines 56-57 and 69-70 validate parameter types. Using TypeError instead of ValueError would more precisely signal the nature of the error:

♻️ Optional refactor
     for index, command in enumerate(commands):
         if not isinstance(command, dict):
-            raise ValueError(
+            raise TypeError(
                 f"Command at index {index} must be an object with 'tool' and 'params' keys")
         if not isinstance(params, dict):
-            raise ValueError(
+            raise TypeError(
                 f"Command '{tool_name}' must specify parameters as an object/dict")

Also applies to: 56-57, 63-64, 69-70

Server/src/transport/plugin_hub.py (1)

51-58: Optional: Annotate mutable class attributes with ClassVar.

Static analysis flags that mutable class attributes like _FAST_FAIL_COMMANDS, _connections, _pending, etc. should be annotated with typing.ClassVar to clarify they are shared class-level state rather than instance attributes. This is a pre-existing pattern and not introduced by this formatting change, so it can be addressed separately if desired.

Example annotation
+from typing import ClassVar
+
 class PluginHub(WebSocketEndpoint):
     ...
-    _FAST_FAIL_COMMANDS: set[str] = {
+    _FAST_FAIL_COMMANDS: ClassVar[set[str]] = {
         "read_console", "get_editor_state", "ping"}
 
     _registry: PluginRegistry | None = None
-    _connections: dict[str, WebSocket] = {}
-    _pending: dict[str, dict[str, Any]] = {}
-    _lock: asyncio.Lock | None = None
-    _loop: asyncio.AbstractEventLoop | None = None
+    _connections: ClassVar[dict[str, WebSocket]] = {}
+    _pending: ClassVar[dict[str, dict[str, Any]]] = {}
+    _lock: ClassVar[asyncio.Lock | None] = None
+    _loop: ClassVar[asyncio.AbstractEventLoop | None] = None
MCPForUnity/Editor/Tools/ManageMaterial.cs (1)

98-112: Verify instruction detection logic for texture handling.

The condition (obj.ContainsKey("find") || obj.ContainsKey("method")) is loose. Per the ObjectResolver.Resolve docs, "find" is mandatory while "method" is optional. An object with only "method" would fail resolution and return null, falling through to MaterialOps.TrySetShaderProperty, which may not be the intended behavior.

Consider tightening this to check only obj.ContainsKey("find"):

if (value is JObject obj && obj.ContainsKey("find"))

That said, the graceful null check (if (tex != null && ...)) mitigates the risk. This is safe as-is but could be clearer semantically.

MCPForUnity/Editor/Tools/GameObjects/ComponentResolver.cs (2)

58-60: Cache key may be inefficient for large property lists.

The cache key concatenates all available properties, which could create very long keys and impact dictionary lookup performance for components with many properties. Consider using a hash of the property list instead.

♻️ Proposed optimization
-var cacheKey = $"{userInput.ToLowerInvariant()}:{string.Join(",", availableProperties)}";
+var propertiesHash = string.Join(",", availableProperties).GetHashCode();
+var cacheKey = $"{userInput.ToLowerInvariant()}:{propertiesHash}";

75-75: Unbounded static cache.

PropertySuggestionCache grows without limit during an editor session. For typical editor usage this is acceptable, but consider adding a size limit or LRU eviction if this becomes a concern.

MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs (1)

390-393: Unused inputSerializer parameter.

The inputSerializer parameter is passed to ConvertJTokenToType but is never used—the method delegates directly to PropertyConversion.ConvertToType(token, targetType). This parameter should either be removed or utilized for consistency.

♻️ Option 1: Remove unused parameter
-private static object ConvertJTokenToType(JToken token, Type targetType, JsonSerializer inputSerializer)
+private static object ConvertJTokenToType(JToken token, Type targetType)
 {
     return PropertyConversion.ConvertToType(token, targetType);
 }

Then update all call sites to remove the inputSerializer argument.

♻️ Option 2: Use the serializer if custom conversion is needed

If the serializer is intended for future use or specific edge cases, consider using it:

 private static object ConvertJTokenToType(JToken token, Type targetType, JsonSerializer inputSerializer)
 {
-    return PropertyConversion.ConvertToType(token, targetType);
+    return token.ToObject(targetType, inputSerializer);
 }
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectCreateTests.cs (1)

427-432: Consider adding a brief comment explaining the empty catch.

The empty catch block is acceptable for test cleanup, but a brief inline comment would clarify intent for future maintainers.

💡 Suggested improvement
             // Clean up the created tag
-            try { UnityEditorInternal.InternalEditorUtility.RemoveTag(testTag); } catch { }
+            try { UnityEditorInternal.InternalEditorUtility.RemoveTag(testTag); } catch { /* Tag may already be removed or not exist */ }
Server/tests/integration/test_refresh_unity_retry_recovery.py (1)

29-36: Consider renaming the mock function for clarity.

The function fake_get_editor_state_v2 retains the "v2" suffix but now mocks get_editor_state (not get_editor_state_v2). Consider renaming it to fake_get_editor_state to avoid confusion.

♻️ Suggested rename
-    async def fake_get_editor_state_v2(_ctx):
+    async def fake_get_editor_state(_ctx):
         return MCPResponse(success=True, data={"advice": {"ready_for_tools": True}})
 
     import services.tools.refresh_unity as refresh_mod
     monkeypatch.setattr(refresh_mod.unity_transport, "send_with_unity_instance", fake_send_with_unity_instance)
 
     import services.resources.editor_state as es_mod
-    monkeypatch.setattr(es_mod, "get_editor_state", fake_get_editor_state_v2)
+    monkeypatch.setattr(es_mod, "get_editor_state", fake_get_editor_state)
Server/tests/integration/test_editor_state_v2_contract.py (1)

14-14: Remove unnecessary noqa directive.

The noqa: F401 directive on line 14 appears to be unused according to static analysis. Consider removing it unless there's a specific linter configuration that requires it.

♻️ Proposed fix
-    import services.resources.editor_state  # noqa: F401
+    import services.resources.editor_state
MCPForUnity/Editor/Tools/GameObjects/GameObjectDelete.cs (1)

24-30: The null check is likely unnecessary.

FindObjectsInternal filters or conditionally adds GameObjects in all branches. The by_component case explicitly filters nulls with Where(go => go != null), and other cases use conditional checks (if (obj != null)) before adding to results. Since the method's logic prevents null entries from being added to the results list, the defensive null check at line 24 can be removed.

MCPForUnity/Editor/Tools/GameObjects/ManageGameObject.cs (2)

1-1: Consider removing #nullable disable.

The #nullable disable directive can hide potential null reference issues and makes the code less safe. Given that the file performs parameter validation and uses null checks, consider enabling nullable reference types and making nullability explicit.

If nullable annotations are enabled project-wide, removing this directive would improve type safety:

♻️ Consider enabling nullable reference types
-#nullable disable
 using System;
 using System.Collections.Generic;

Then ensure proper nullable annotations on parameters and fields (e.g., string? for optional fields).


38-47: Document the name→target aliasing behavior in user-facing documentation.

The aliasing of name to target for "modify" actions is tested (see Modify_WithNameAlias_UsesNameAsTarget() test), but it's not documented in the tool's schema or README. While similar aliasing patterns exist elsewhere in the codebase (e.g., FindGameObjects, ManagePrefabs) without apparent issues, users need to understand that providing name will populate target so they aren't surprised by the behavior when inspecting parameter objects or composing downstream requests.

MCPForUnity/Editor/Tools/Vfx/TrailControl.cs (1)

24-27: Consider adding Undo.RecordObject before AddPosition for consistency.

The Clear method records an undo operation, but Emit does not. For consistency with other trail write operations (e.g., TrailWrite.SetTime, TrailWrite.SetWidth), consider recording the undo state before modifying the trail:

 #if UNITY_2021_1_OR_NEWER
             Vector3 pos = ManageVfxCommon.ParseVector3(@params["position"]);
+            Undo.RecordObject(tr, "Emit Trail Position");
             tr.AddPosition(pos);

Note: If AddPosition modifies purely runtime state that isn't serialized, undo may not be meaningful here, in which case this is fine as-is.

MCPForUnity/Editor/Tools/Vfx/TrailWrite.cs (1)

26-41: Handle case where no width properties were actually changed.

If @params contains no recognized width properties (no width, startWidth, endWidth, widthCurve, or widthMultiplier), the changes list will be empty, and the message will be "Updated: ". Consider returning a no-op success message or validating that at least one property was provided.

This same pattern appears in SetColor (lines 43-58) and SetProperties (lines 66-88).

Example improvement
             EditorUtility.SetDirty(tr);
-            return new { success = true, message = $"Updated: {string.Join(", ", changes)}" };
+            return changes.Count > 0
+                ? new { success = true, message = $"Updated: {string.Join(", ", changes)}" }
+                : new { success = true, message = "No properties to update" };
MCPForUnity/Editor/Tools/Vfx/ManageVFX.cs (2)

312-323: Empty catch blocks suppress errors silently in template search.

Lines 321-322 swallow exceptions during template file search. Similar pattern at lines 452-453 in VFXListTemplates. While this prevents crashes during file enumeration, it could hide permission or path issues.

Consider logging warnings:

-                catch { }
+                catch (Exception ex)
+                {
+                    McpLog.Warn($"[ManageVFX] Error searching for templates: {ex.Message}");
+                }

458-466: Consider using a typed class instead of anonymous types with dynamic casts.

Line 461 uses ((dynamic)t).path to access properties of anonymous objects in the templates list. While functional, this bypasses compile-time type checking.

A small DTO class would provide better type safety:

private class TemplateInfo { public string name; public string path; public string source; }
MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs (1)

76-80: Burst array expansion may leave uninitialized data before GetBursts call.

The array bursts is created with size idx + 1, but GetBursts only fills the first idx elements (indices 0 to idx-1). This works because you then assign bursts[idx] = burst, but the code reads slightly confusingly. Consider reordering for clarity:

var existingBursts = new ParticleSystem.Burst[idx];
emission.GetBursts(existingBursts);
var bursts = new ParticleSystem.Burst[idx + 1];
Array.Copy(existingBursts, bursts, idx);
bursts[idx] = burst;

However, the current implementation is functionally correct since Unity's GetBursts writes up to the array length or burst count (whichever is smaller).

MCPForUnity/Editor/Tools/GameObjects/GameObjectCreate.cs (2)

100-108: Redundant name validation after early return.

The name validation at lines 100-108 is unreachable because lines 17-21 already return an error if name is null or empty. This dead code can be removed.

♻️ Suggested cleanup
                 try
                 {
                     PrimitiveType type = (PrimitiveType)Enum.Parse(typeof(PrimitiveType), primitiveType, true);
                     newGo = GameObject.CreatePrimitive(type);
-                    if (!string.IsNullOrEmpty(name))
-                    {
-                        newGo.name = name;
-                    }
-                    else
-                    {
-                        UnityEngine.Object.DestroyImmediate(newGo);
-                        return new ErrorResponse("'name' parameter is required when creating a primitive.");
-                    }
+                    newGo.name = name;
                     createdNewObject = true;
                 }

120-128: Similar redundant check for empty GameObject creation.

The check at line 122-125 is also unreachable due to the early validation at lines 17-21.

♻️ Suggested cleanup
                 else
                 {
-                    if (string.IsNullOrEmpty(name))
-                    {
-                        return new ErrorResponse("'name' parameter is required for 'create' action when not instantiating a prefab or creating a primitive.");
-                    }
                     newGo = new GameObject(name);
                     createdNewObject = true;
                 }
MCPForUnity/Editor/Tools/Vfx/LineCreate.cs (1)

36-39: Duplicated perpendicular axis calculation.

The logic for computing perpendicular axes from a normal vector is duplicated between CreateCircle and CreateArc. Consider extracting this into a shared helper method within the class or in ManageVfxCommon.

♻️ Suggested extraction
+        private static (Vector3 right, Vector3 forward) GetPerpendicularAxes(Vector3 normal)
+        {
+            Vector3 right = Vector3.Cross(normal, Vector3.forward);
+            if (right.sqrMagnitude < 0.001f) right = Vector3.Cross(normal, Vector3.up);
+            right = right.normalized;
+            Vector3 forward = Vector3.Cross(right, normal).normalized;
+            return (right, forward);
+        }

Also applies to: 68-71

MCPForUnity/Editor/Tools/Vfx/LineWrite.cs (1)

66-98: Consider guarding against empty changes in the response message.

If no recognized width/color properties are present in @params, changes remains empty and the message becomes "Updated: ". While not incorrect, it could confuse callers.

🔧 Optional: Return early or clarify when no changes applied
             EditorUtility.SetDirty(lr);
-            return new { success = true, message = $"Updated: {string.Join(", ", changes)}" };
+            return new { success = true, message = changes.Count > 0 ? $"Updated: {string.Join(", ", changes)}" : "No width properties updated" };
Server/src/services/resources/editor_state.py (2)

136-170: Consider logging or narrowing broad exception handlers.

Multiple except Exception blocks silently swallow all errors, reducing observability. At minimum, logging at DEBUG level would aid troubleshooting transport/discovery issues.

🔧 Example: Add debug logging
+import logging
+
+logger = logging.getLogger(__name__)
+
 async def _infer_single_instance_id(ctx: Context) -> str | None:
     ...
     try:
         transport = unity_transport._current_transport()
-    except Exception:
+    except Exception as e:
+        logger.debug("Failed to determine transport: %s", e)
         transport = None

254-285: Avoid silent try-except-pass—log or propagate partial failures.

Lines 284-285 catch all exceptions during external-changes detection and discard them. If this scanner fails, the caller has no indication. Consider logging the exception or including a flag in the response.

🔧 Proposed: Log instead of silent pass
+import logging
+
+logger = logging.getLogger(__name__)
+
     except Exception:
-        pass
+        logger.debug("External changes detection failed", exc_info=True)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7de4d0f and 0a95563.

📒 Files selected for processing (101)
  • MCPForUnity/Editor/Helpers/VectorParsing.cs
  • MCPForUnity/Editor/Resources/Editor/EditorState.cs
  • MCPForUnity/Editor/Resources/Editor/EditorStateV2.cs
  • MCPForUnity/Editor/Resources/Scene/GameObjectResource.cs
  • MCPForUnity/Editor/Services/EditorStateCache.cs
  • MCPForUnity/Editor/Tools/FindGameObjects.cs
  • MCPForUnity/Editor/Tools/GameObjects.meta
  • MCPForUnity/Editor/Tools/GameObjects/ComponentResolver.cs
  • MCPForUnity/Editor/Tools/GameObjects/ComponentResolver.cs.meta
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs.meta
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectCreate.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectCreate.cs.meta
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectDelete.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectDelete.cs.meta
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectDuplicate.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectDuplicate.cs.meta
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectHandlers.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectHandlers.cs.meta
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs.meta
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectMoveRelative.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectMoveRelative.cs.meta
  • MCPForUnity/Editor/Tools/GameObjects/ManageGameObject.cs
  • MCPForUnity/Editor/Tools/GameObjects/ManageGameObject.cs.meta
  • MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs
  • MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs.meta
  • MCPForUnity/Editor/Tools/GetTestJob.cs
  • MCPForUnity/Editor/Tools/JsonUtil.cs
  • MCPForUnity/Editor/Tools/ManageAsset.cs
  • MCPForUnity/Editor/Tools/ManageComponents.cs
  • MCPForUnity/Editor/Tools/ManageGameObject.cs
  • MCPForUnity/Editor/Tools/ManageMaterial.cs
  • MCPForUnity/Editor/Tools/ManageVFX.cs
  • MCPForUnity/Editor/Tools/RefreshUnity.cs
  • MCPForUnity/Editor/Tools/RunTestsAsync.cs
  • MCPForUnity/Editor/Tools/Vfx.meta
  • MCPForUnity/Editor/Tools/Vfx/LineCreate.cs
  • MCPForUnity/Editor/Tools/Vfx/LineCreate.cs.meta
  • MCPForUnity/Editor/Tools/Vfx/LineRead.cs
  • MCPForUnity/Editor/Tools/Vfx/LineRead.cs.meta
  • MCPForUnity/Editor/Tools/Vfx/LineWrite.cs
  • MCPForUnity/Editor/Tools/Vfx/LineWrite.cs.meta
  • MCPForUnity/Editor/Tools/Vfx/ManageVFX.cs
  • MCPForUnity/Editor/Tools/Vfx/ManageVFX.cs.meta
  • MCPForUnity/Editor/Tools/Vfx/ManageVfxCommon.cs
  • MCPForUnity/Editor/Tools/Vfx/ManageVfxCommon.cs.meta
  • MCPForUnity/Editor/Tools/Vfx/ParticleCommon.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleCommon.cs.meta
  • MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs.meta
  • MCPForUnity/Editor/Tools/Vfx/ParticleRead.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleRead.cs.meta
  • MCPForUnity/Editor/Tools/Vfx/ParticleWrite.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleWrite.cs.meta
  • MCPForUnity/Editor/Tools/Vfx/TrailControl.cs
  • MCPForUnity/Editor/Tools/Vfx/TrailControl.cs.meta
  • MCPForUnity/Editor/Tools/Vfx/TrailRead.cs
  • MCPForUnity/Editor/Tools/Vfx/TrailRead.cs.meta
  • MCPForUnity/Editor/Tools/Vfx/TrailWrite.cs
  • MCPForUnity/Editor/Tools/Vfx/TrailWrite.cs.meta
  • README.md
  • Server/src/main.py
  • Server/src/routes/__init__.py
  • Server/src/services/custom_tool_service.py
  • Server/src/services/resources/editor_state.py
  • Server/src/services/resources/editor_state_v2.py
  • Server/src/services/resources/gameobject.py
  • Server/src/services/state/external_changes_scanner.py
  • Server/src/services/tools/batch_execute.py
  • Server/src/services/tools/debug_request_context.py
  • Server/src/services/tools/find_gameobjects.py
  • Server/src/services/tools/find_in_file.py
  • Server/src/services/tools/manage_components.py
  • Server/src/services/tools/manage_gameobject.py
  • Server/src/services/tools/manage_material.py
  • Server/src/services/tools/manage_scene.py
  • Server/src/services/tools/manage_scriptable_object.py
  • Server/src/services/tools/manage_shader.py
  • Server/src/services/tools/manage_vfx.py
  • Server/src/services/tools/preflight.py
  • Server/src/services/tools/read_console.py
  • Server/src/services/tools/refresh_unity.py
  • Server/src/services/tools/run_tests.py
  • Server/src/services/tools/script_apply_edits.py
  • Server/src/services/tools/set_active_instance.py
  • Server/src/services/tools/test_jobs.py
  • Server/src/services/tools/utils.py
  • Server/src/transport/legacy/port_discovery.py
  • Server/src/transport/plugin_hub.py
  • Server/src/transport/unity_instance_middleware.py
  • Server/src/transport/unity_transport.py
  • Server/tests/integration/test_editor_state_v2_contract.py
  • Server/tests/integration/test_refresh_unity_retry_recovery.py
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/GameObjectAPIStressTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MCPToolParameterTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectCreateTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectDeleteTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectModifyTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MaterialParameterToolTests.cs
💤 Files with no reviewable changes (11)
  • MCPForUnity/Editor/Tools/GetTestJob.cs
  • MCPForUnity/Editor/Resources/Editor/EditorStateV2.cs
  • MCPForUnity/Editor/Tools/RefreshUnity.cs
  • MCPForUnity/Editor/Resources/Scene/GameObjectResource.cs
  • MCPForUnity/Editor/Tools/JsonUtil.cs
  • MCPForUnity/Editor/Tools/ManageVFX.cs
  • MCPForUnity/Editor/Tools/FindGameObjects.cs
  • MCPForUnity/Editor/Tools/ManageComponents.cs
  • Server/src/services/resources/editor_state_v2.py
  • MCPForUnity/Editor/Tools/RunTestsAsync.cs
  • MCPForUnity/Editor/Tools/ManageGameObject.cs
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-10-13T13:27:23.040Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 316
File: TestProjects/UnityMCPTests/Assets/Tests/EditMode/Resources.meta:1-8
Timestamp: 2025-10-13T13:27:23.040Z
Learning: UnityMcpBridge is a legacy project kept for backwards compatibility; MCPForUnity is the only active Unity plugin project. GUID collisions between UnityMcpBridge and MCPForUnity are acceptable.

Applied to files:

  • MCPForUnity/Editor/Tools/Vfx/ParticleRead.cs.meta
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectDeleteTests.cs
  • MCPForUnity/Editor/Tools/GameObjects/ComponentResolver.cs
  • MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs.meta
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectCreate.cs.meta
  • MCPForUnity/Editor/Tools/GameObjects/ComponentResolver.cs.meta
  • MCPForUnity/Editor/Services/EditorStateCache.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MaterialParameterToolTests.cs
  • MCPForUnity/Editor/Resources/Editor/EditorState.cs
  • MCPForUnity/Editor/Tools/Vfx/ManageVfxCommon.cs.meta
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectCreateTests.cs
  • MCPForUnity/Editor/Tools/GameObjects.meta
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectModifyTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MCPToolParameterTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/GameObjectAPIStressTests.cs
  • MCPForUnity/Editor/Tools/ManageMaterial.cs
  • MCPForUnity/Editor/Tools/ManageAsset.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs
📚 Learning: 2025-09-03T16:00:55.839Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 0
File: :0-0
Timestamp: 2025-09-03T16:00:55.839Z
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:

  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectDeleteTests.cs
  • MCPForUnity/Editor/Tools/GameObjects/ComponentResolver.cs
  • MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs.meta
  • MCPForUnity/Editor/Tools/GameObjects/ComponentResolver.cs.meta
  • MCPForUnity/Editor/Services/EditorStateCache.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MaterialParameterToolTests.cs
  • MCPForUnity/Editor/Resources/Editor/EditorState.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectCreate.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectCreateTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectModifyTests.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleCommon.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MCPToolParameterTests.cs
  • MCPForUnity/Editor/Tools/Vfx/ManageVfxCommon.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/GameObjectAPIStressTests.cs
  • MCPForUnity/Editor/Tools/ManageMaterial.cs
  • MCPForUnity/Editor/Tools/GameObjects/ManageGameObject.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectHandlers.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs.meta
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectMoveRelative.cs
  • MCPForUnity/Editor/Tools/ManageAsset.cs
  • MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs
📚 Learning: 2025-12-29T15:23:17.871Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:17.871Z
Learning: In the MCPForUnity project, the team prefers to rely on their testing process to catch UI initialization issues rather than adding defensive null checks for UI elements in editor windows.

Applied to files:

  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectDeleteTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MaterialParameterToolTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectCreateTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectModifyTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MCPToolParameterTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/GameObjectAPIStressTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs
📚 Learning: 2025-12-29T15:23:11.613Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:11.613Z
Learning: In MCPForUnity, prefer relying on the established testing process to catch UI initialization issues instead of adding defensive null checks for UI elements in editor windows. This means during reviews, verify that tests cover UI initialization paths and that code avoids repetitive null-check boilerplate in Editor Windows. If a UI element can be null, ensure there is a well-tested fallback or that its initialization is guaranteed by design, rather than sprinkling null checks throughout editor code.

Applied to files:

  • MCPForUnity/Editor/Tools/GameObjects/ComponentResolver.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleRead.cs
  • MCPForUnity/Editor/Services/EditorStateCache.cs
  • MCPForUnity/Editor/Resources/Editor/EditorState.cs
  • MCPForUnity/Editor/Helpers/VectorParsing.cs
  • MCPForUnity/Editor/Tools/Vfx/LineRead.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectCreate.cs
  • MCPForUnity/Editor/Tools/Vfx/ManageVFX.cs
  • MCPForUnity/Editor/Tools/Vfx/LineWrite.cs
  • MCPForUnity/Editor/Tools/Vfx/TrailControl.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleCommon.cs
  • MCPForUnity/Editor/Tools/Vfx/ManageVfxCommon.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectDuplicate.cs
  • MCPForUnity/Editor/Tools/ManageMaterial.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectDelete.cs
  • MCPForUnity/Editor/Tools/Vfx/LineCreate.cs
  • MCPForUnity/Editor/Tools/GameObjects/ManageGameObject.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectHandlers.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs
  • MCPForUnity/Editor/Tools/Vfx/TrailRead.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectMoveRelative.cs
  • MCPForUnity/Editor/Tools/ManageAsset.cs
  • MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleWrite.cs
  • MCPForUnity/Editor/Tools/Vfx/TrailWrite.cs
📚 Learning: 2025-11-05T18:23:12.349Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 368
File: MCPForUnity/UnityMcpServer~/src/resources/menu_items.py:15-15
Timestamp: 2025-11-05T18:23:12.349Z
Learning: In Unity MCP, the `name` parameter in the `mcp_for_unity_resource` decorator is the external API name exposed to MCP clients (LLMs, AI agents). The command string passed to `async_send_command_with_retry` or `async_send_with_unity_instance` (e.g., "get_menu_items") is the internal command identifier that must match the C# side. These are decoupled, allowing external API naming to evolve independently of internal command routing.

Applied to files:

  • Server/src/transport/unity_transport.py
  • Server/src/services/tools/refresh_unity.py
📚 Learning: 2025-10-03T22:11:46.002Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 301
File: docs/CUSTOM_TOOLS.md:54-62
Timestamp: 2025-10-03T22:11:46.002Z
Learning: In Unity MCP, the `description` parameter in the `mcp_for_unity_tool` decorator is technically optional but should always be included as a best practice. Without it, there's a higher chance that MCP clients will not parse the tool correctly. All Unity MCP tools should include the description in the decorator for compatibility.

Applied to files:

  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MaterialParameterToolTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MCPToolParameterTests.cs
📚 Learning: 2025-10-13T13:41:00.086Z
Learnt from: JohanHoltby
Repo: CoplayDev/unity-mcp PR: 309
File: MCPForUnity/Editor/Helpers/ServerInstaller.cs:478-508
Timestamp: 2025-10-13T13:41:00.086Z
Learning: In the MCPForUnityTools feature (MCPForUnity/Editor/Helpers/ServerInstaller.cs), the design intentionally forces users to have only one .py file per MCPForUnityTools folder to keep file tracking simple. Package-style tools (subdirectories with __init__.py) are not supported.

Applied to files:

  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MaterialParameterToolTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectCreateTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectModifyTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MCPToolParameterTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/GameObjectAPIStressTests.cs
  • MCPForUnity/Editor/Tools/ManageMaterial.cs
  • Server/src/services/tools/refresh_unity.py
📚 Learning: 2025-09-04T01:01:11.927Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 260
File: UnityMcpBridge/UnityMcpServer~/src/server_version.txt:1-1
Timestamp: 2025-09-04T01:01:11.927Z
Learning: The UnityMcpBridge project is not maintaining changelogs yet, so don't suggest adding changelog entries for version bumps.

Applied to files:

  • MCPForUnity/Editor/Tools/Vfx/ManageVfxCommon.cs.meta
  • MCPForUnity/Editor/Tools/GameObjects.meta
🧬 Code graph analysis (25)
Server/src/services/tools/manage_vfx.py (2)
Server/src/transport/unity_transport.py (1)
  • send_with_unity_instance (79-114)
Server/src/transport/legacy/unity_connection.py (1)
  • async_send_command_with_retry (838-870)
MCPForUnity/Editor/Services/EditorStateCache.cs (1)
Server/src/services/resources/editor_state.py (11)
  • EditorStateV2Unity (16-21)
  • EditorStateV2Editor (36-39)
  • EditorStateV2Activity (42-45)
  • EditorStateV2Compilation (48-54)
  • EditorStateV2Assets (63-69)
  • EditorStateV2Tests (78-84)
  • EditorStateV2Transport (87-89)
  • EditorStateV2PlayMode (24-27)
  • EditorStateV2ActiveScene (30-33)
  • EditorStateV2Refresh (57-60)
  • EditorStateV2LastRun (72-75)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MaterialParameterToolTests.cs (2)
MCPForUnity/Editor/Tools/ManageAsset.cs (1)
  • DeleteAsset (495-523)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/TestUtilities.cs (1)
  • CleanupEmptyParentFolders (103-137)
MCPForUnity/Editor/Resources/Editor/EditorState.cs (1)
MCPForUnity/Editor/Services/EditorStateCache.cs (1)
  • EditorStateCache (225-253)
MCPForUnity/Editor/Tools/Vfx/LineRead.cs (3)
MCPForUnity/Editor/Tools/Vfx/ManageVfxCommon.cs (3)
  • GameObject (16-17)
  • ManageVfxCommon (7-21)
  • Vector3 (10-10)
MCPForUnity/Editor/Tools/Vfx/ParticleRead.cs (1)
  • GetInfo (9-73)
MCPForUnity/Editor/Tools/Vfx/TrailRead.cs (1)
  • GetInfo (14-47)
MCPForUnity/Editor/Tools/Vfx/ManageVFX.cs (4)
MCPForUnity/Editor/Tools/Vfx/ParticleRead.cs (2)
  • ParticleRead (7-74)
  • GetInfo (9-73)
MCPForUnity/Editor/Tools/Vfx/LineRead.cs (2)
  • GetInfo (15-50)
  • LineRead (7-51)
MCPForUnity/Editor/Tools/Vfx/TrailRead.cs (2)
  • GetInfo (14-47)
  • TrailRead (6-48)
MCPForUnity/Editor/Tools/Vfx/ManageVfxCommon.cs (7)
  • GameObject (16-17)
  • ManageVfxCommon (7-21)
  • Vector4 (11-11)
  • Vector3 (10-10)
  • Color (9-9)
  • Gradient (12-12)
  • AnimationCurve (13-14)
Server/src/services/tools/preflight.py (2)
Server/src/services/resources/editor_state.py (1)
  • get_editor_state (220-305)
Server/tests/integration/test_helpers.py (1)
  • model_dump (10-13)
MCPForUnity/Editor/Tools/Vfx/TrailControl.cs (2)
MCPForUnity/Editor/Tools/Vfx/TrailRead.cs (2)
  • TrailRenderer (8-12)
  • TrailRead (6-48)
MCPForUnity/Editor/Tools/Vfx/ManageVfxCommon.cs (2)
  • Vector3 (10-10)
  • ManageVfxCommon (7-21)
MCPForUnity/Editor/Tools/Vfx/ParticleCommon.cs (2)
MCPForUnity/Editor/Tools/Vfx/ManageVfxCommon.cs (4)
  • GameObject (16-17)
  • ManageVfxCommon (7-21)
  • AnimationCurve (13-14)
  • Color (9-9)
MCPForUnity/Editor/Tools/BatchExecute.cs (1)
  • JArray (153-161)
Server/src/services/tools/manage_scene.py (1)
Server/src/services/tools/utils.py (2)
  • coerce_int (64-78)
  • coerce_bool (12-25)
MCPForUnity/Editor/Tools/ManageMaterial.cs (4)
MCPForUnity/Editor/Helpers/ObjectResolver.cs (5)
  • UnityEngine (32-105)
  • UnityEngine (170-199)
  • Texture (146-153)
  • ObjectResolver (13-200)
  • Material (134-141)
MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs (2)
  • UnityEngine (311-353)
  • Color (102-111)
MCPForUnity/Editor/Helpers/AssetPathUtility.cs (1)
  • JObject (98-149)
MCPForUnity/Editor/Helpers/MaterialOps.cs (1)
  • Color (345-395)
MCPForUnity/Editor/Tools/GameObjects/GameObjectDelete.cs (1)
MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs (3)
  • List (31-179)
  • GameObject (15-29)
  • ManageGameObjectCommon (13-209)
MCPForUnity/Editor/Tools/GameObjects/ManageGameObject.cs (6)
MCPForUnity/Editor/Helpers/McpLog.cs (3)
  • McpLog (7-52)
  • Warn (43-46)
  • Error (48-51)
MCPForUnity/Editor/Tools/GameObjects/GameObjectCreate.cs (2)
  • GameObjectCreate (13-308)
  • Handle (15-307)
MCPForUnity/Editor/Tools/GameObjects/GameObjectDelete.cs (2)
  • Handle (12-43)
  • GameObjectDelete (10-44)
MCPForUnity/Editor/Tools/GameObjects/GameObjectDuplicate.cs (2)
  • Handle (12-84)
  • GameObjectDuplicate (10-85)
MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs (2)
  • Handle (15-235)
  • GameObjectModify (13-236)
MCPForUnity/Editor/Tools/GameObjects/GameObjectMoveRelative.cs (2)
  • Handle (12-79)
  • GameObjectMoveRelative (10-118)
MCPForUnity/Editor/Tools/GameObjects/GameObjectHandlers.cs (5)
MCPForUnity/Editor/Tools/GameObjects/GameObjectCreate.cs (2)
  • GameObjectCreate (13-308)
  • Handle (15-307)
MCPForUnity/Editor/Tools/GameObjects/GameObjectDelete.cs (2)
  • Handle (12-43)
  • GameObjectDelete (10-44)
MCPForUnity/Editor/Tools/GameObjects/GameObjectDuplicate.cs (2)
  • Handle (12-84)
  • GameObjectDuplicate (10-85)
MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs (2)
  • Handle (15-235)
  • GameObjectModify (13-236)
MCPForUnity/Editor/Tools/GameObjects/GameObjectMoveRelative.cs (2)
  • Handle (12-79)
  • GameObjectMoveRelative (10-118)
Server/src/services/custom_tool_service.py (1)
Server/tests/integration/test_helpers.py (1)
  • warning (39-40)
MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs (5)
MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs (3)
  • GameObject (15-29)
  • Type (195-208)
  • List (31-179)
MCPForUnity/Editor/Helpers/ComponentOps.cs (1)
  • Component (23-73)
MCPForUnity/Editor/Helpers/UnityJsonSerializer.cs (1)
  • UnityJsonSerializer (11-31)
MCPForUnity/Editor/Helpers/ParamCoercion.cs (2)
  • ParamCoercion (11-200)
  • NormalizePropertyName (170-199)
MCPForUnity/Editor/Helpers/PropertyConversion.cs (2)
  • PropertyConversion (14-91)
  • ConvertToType (22-44)
MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs (1)
MCPForUnity/Editor/Tools/Vfx/ParticleCommon.cs (4)
  • ParticleSystem (8-12)
  • ParticleSystem (14-50)
  • ParticleSystem (52-85)
  • ParticleCommon (6-86)
MCPForUnity/Editor/Tools/Vfx/TrailRead.cs (3)
MCPForUnity/Editor/Tools/Vfx/ManageVfxCommon.cs (2)
  • GameObject (16-17)
  • ManageVfxCommon (7-21)
MCPForUnity/Editor/Tools/Vfx/LineRead.cs (1)
  • GetInfo (15-50)
MCPForUnity/Editor/Tools/ManageVFX.cs (1)
  • TrailSetProperties (1649-1674)
Server/src/services/tools/refresh_unity.py (1)
Server/src/services/resources/editor_state.py (2)
  • get_editor_state (220-305)
  • _infer_single_instance_id (128-171)
MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs (2)
MCPForUnity/Editor/Tools/GameObjects/ComponentResolver.cs (5)
  • List (29-47)
  • List (53-73)
  • List (81-112)
  • ComponentResolver (15-141)
  • TryResolve (21-24)
MCPForUnity/Editor/Helpers/McpLog.cs (2)
  • McpLog (7-52)
  • Warn (43-46)
Server/src/services/resources/gameobject.py (3)
Server/src/transport/unity_transport.py (1)
  • send_with_unity_instance (79-114)
Server/src/transport/legacy/unity_connection.py (1)
  • async_send_command_with_retry (838-870)
Server/src/services/custom_tool_service.py (1)
  • _normalize_response (228-260)
Server/tests/integration/test_editor_state_v2_contract.py (1)
Server/tests/integration/test_helpers.py (1)
  • DummyContext (16-55)
Server/src/services/tools/find_in_file.py (1)
Server/src/services/tools/manage_script.py (1)
  • _split_uri (15-64)
Server/src/services/tools/manage_material.py (1)
Server/src/services/tools/utils.py (2)
  • parse_json_payload (28-61)
  • normalize_properties (99-131)
MCPForUnity/Editor/Tools/Vfx/TrailWrite.cs (3)
MCPForUnity/Editor/Tools/Vfx/TrailRead.cs (2)
  • TrailRenderer (8-12)
  • TrailRead (6-48)
MCPForUnity/Editor/Helpers/RendererHelpers.cs (6)
  • RendererHelpers (13-166)
  • ApplyWidthProperties (75-91)
  • ApplyColorProperties (104-120)
  • SetRendererMaterial (130-145)
  • ApplyLineTrailProperties (151-164)
  • ApplyCommonRendererProperties (20-45)
MCPForUnity/Editor/Tools/Vfx/ManageVfxCommon.cs (1)
  • ManageVfxCommon (7-21)
🪛 Ruff (0.14.10)
Server/src/services/tools/manage_vfx.py

42-43: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)

Server/src/transport/plugin_hub.py

51-52: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


456-457: Avoid specifying long messages outside the exception class

(TRY003)

Server/src/services/tools/batch_execute.py

45-46: Avoid specifying long messages outside the exception class

(TRY003)


56-57: Prefer TypeError exception for invalid type

(TRY004)


56-57: Avoid specifying long messages outside the exception class

(TRY003)


63-64: Avoid specifying long messages outside the exception class

(TRY003)


69-70: Prefer TypeError exception for invalid type

(TRY004)


69-70: Avoid specifying long messages outside the exception class

(TRY003)

Server/tests/integration/test_editor_state_v2_contract.py

14-14: Unused noqa directive (non-enabled: F401)

Remove unused noqa directive

(RUF100)


27-27: Unused function argument: send_fn

(ARG001)


27-27: Unused function argument: unity_instance

(ARG001)


27-27: Unused function argument: params

(ARG001)


27-27: Unused function argument: kwargs

(ARG001)

Server/src/services/resources/editor_state.py

128-128: Unused function argument: ctx

(ARG001)


138-138: Do not catch blind exception: Exception

(BLE001)


155-155: Do not catch blind exception: Exception

(BLE001)


169-169: Do not catch blind exception: Exception

(BLE001)


179-179: Do not catch blind exception: Exception

(BLE001)


284-285: try-except-pass detected, consider logging the exception

(S110)


284-284: Do not catch blind exception: Exception

(BLE001)


297-297: Do not catch blind exception: Exception

(BLE001)

Comment on lines +85 to +91
case "by_path":
Transform foundTransform = rootSearchObject
? rootSearchObject.transform.Find(searchTerm)
: GameObject.Find(searchTerm)?.transform;
if (foundTransform != null)
results.Add(foundTransform.gameObject);
break;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

GameObject.Find ignores inactive objects, contradicting searchInactive flag.

When searchMethod is "by_path" and searchInactive is true, calling GameObject.Find(searchTerm) will still only return active GameObjects. This inconsistency may cause unexpected behavior when users expect to find inactive objects by path.

Consider using a manual hierarchy traversal that respects the searchInactive flag, or document this limitation.

…ation

Merged run_tests_async into run_tests, making async job-based execution the default behavior. Removed synchronous blocking test execution. Updated RunTests.cs to start test jobs immediately and return job_id for polling. Changed TestJobManager methods to internal visibility. Updated README to reflect single run_tests_async tool. Python implementation now uses async job pattern exclusively.
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 @Server/src/transport/plugin_hub.py:
- Line 149: PluginHub references an undefined class attribute FAST_FAIL_TIMEOUT
causing AttributeError; add a FAST_FAIL_TIMEOUT constant to the PluginHub class
alongside the other timeout definitions (e.g., define FAST_FAIL_TIMEOUT = 5.0 or
set it equal to FAST_TIMEOUT) so read_console/get_editor_state/ping can use it
without raising at runtime.
🧹 Nitpick comments (3)
MCPForUnity/Editor/Tools/RunTests.cs (1)

34-53: Consider logging parse failures for includeDetails/includeFailedTests.

The silent exception handling (lines 36-53) for boolean parameter parsing is defensive, but could hide malformed input that might indicate client-side issues. Since these are optional flags with sensible defaults, the current approach is acceptable, but consider adding a debug log on parse failure to aid troubleshooting.

🔍 Optional: Add debug logging for parse failures
 try
 {
     var includeDetailsToken = @params?["includeDetails"];
     if (includeDetailsToken != null && bool.TryParse(includeDetailsToken.ToString(), out var parsedIncludeDetails))
     {
         includeDetails = parsedIncludeDetails;
     }

     var includeFailedTestsToken = @params?["includeFailedTests"];
     if (includeFailedTestsToken != null && bool.TryParse(includeFailedTestsToken.ToString(), out var parsedIncludeFailedTests))
     {
         includeFailedTests = parsedIncludeFailedTests;
     }
 }
-catch
+catch (Exception ex)
 {
-    // ignore parse failures
+    McpLog.Debug($"[RunTests] Failed to parse boolean flags: {ex.Message}");
+    // Use defaults
 }
Server/src/transport/plugin_hub.py (2)

51-52: Formatting improvement looks good.

The multiline format improves readability. Optionally, consider annotating this class attribute with typing.ClassVar to clarify it's a class-level variable.

📝 Optional: Add ClassVar annotation
-    _FAST_FAIL_COMMANDS: set[str] = {
+    _FAST_FAIL_COMMANDS: ClassVar[set[str]] = {
         "read_console", "get_editor_state", "ping"}

Add the import at the top:

from typing import Any, ClassVar

452-453: Consider moving exception message to the exception class.

The exception message is clear, but static analysis suggests defining default messages within the exception class itself for better maintainability.

♻️ Optional refactor

Define a default message in the NoUnitySessionError class:

 class NoUnitySessionError(RuntimeError):
-    """Raised when no Unity plugins are available."""
+    """Raised when no Unity plugins are available."""
+    
+    def __init__(self, message: str = "No Unity plugins are currently connected"):
+        super().__init__(message)

Then simplify the raise statement:

-        raise NoUnitySessionError(
-            "No Unity plugins are currently connected")
+        raise NoUnitySessionError()
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a95563 and 93f1084.

📒 Files selected for processing (11)
  • MCPForUnity/Editor/Services/TestJobManager.cs
  • MCPForUnity/Editor/Tools/RunTests.cs
  • MCPForUnity/Editor/Tools/RunTests.cs.meta
  • MCPForUnity/Editor/Tools/RunTestsAsync.cs
  • README.md
  • Server/src/services/tools/run_tests.py
  • Server/src/services/tools/test_jobs.py
  • Server/src/transport/plugin_hub.py
  • Server/tests/integration/test_run_tests_async.py
  • Server/tests/integration/test_run_tests_busy_semantics.py
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/RunTestsTests.cs
💤 Files with no reviewable changes (3)
  • MCPForUnity/Editor/Tools/RunTestsAsync.cs
  • Server/tests/integration/test_run_tests_busy_semantics.py
  • Server/src/services/tools/test_jobs.py
✅ Files skipped from review due to trivial changes (1)
  • MCPForUnity/Editor/Tools/RunTests.cs.meta
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-29T15:23:11.613Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:11.613Z
Learning: In MCPForUnity, prefer relying on the established testing process to catch UI initialization issues instead of adding defensive null checks for UI elements in editor windows. This means during reviews, verify that tests cover UI initialization paths and that code avoids repetitive null-check boilerplate in Editor Windows. If a UI element can be null, ensure there is a well-tested fallback or that its initialization is guaranteed by design, rather than sprinkling null checks throughout editor code.

Applied to files:

  • MCPForUnity/Editor/Services/TestJobManager.cs
  • MCPForUnity/Editor/Tools/RunTests.cs
🧬 Code graph analysis (3)
Server/src/services/tools/run_tests.py (5)
Server/src/services/tools/preflight.py (1)
  • preflight (27-110)
Server/src/transport/legacy/unity_connection.py (1)
  • async_send_command_with_retry (838-870)
MCPForUnity/Editor/Services/TestJobManager.cs (1)
  • TestJobFailure (20-24)
Server/tests/integration/test_helpers.py (1)
  • error (46-47)
Server/src/transport/unity_transport.py (1)
  • send_with_unity_instance (79-114)
MCPForUnity/Editor/Tools/RunTests.cs (4)
MCPForUnity/Editor/Tools/ManageEditor.cs (2)
  • McpForUnityTool (13-377)
  • HandleCommand (25-127)
MCPForUnity/Editor/Services/TestRunnerService.cs (3)
  • Task (34-59)
  • Task (61-144)
  • Task (351-379)
MCPForUnity/Editor/Resources/Tests/GetTests.cs (4)
  • Task (18-36)
  • Task (46-74)
  • ModeParser (77-105)
  • TryParse (79-104)
MCPForUnity/Editor/Resources/Editor/Windows.cs (1)
  • HandleCommand (16-57)
Server/tests/integration/test_run_tests_async.py (2)
Server/src/services/tools/run_tests.py (2)
  • run_tests (97-155)
  • get_test_job (165-191)
Server/tests/integration/test_helpers.py (1)
  • DummyContext (16-55)
🪛 Ruff (0.14.10)
Server/src/transport/plugin_hub.py

51-52: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


452-453: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (11)
MCPForUnity/Editor/Services/TestJobManager.cs (1)

419-419: LGTM: Appropriate encapsulation improvement.

The visibility reduction from public static to internal static for both GetJob and ToSerializable aligns well with the PR's shift toward internal test job orchestration. These methods are now accessed only through the new run_tests/get_test_job workflow.

Also applies to: 431-431

MCPForUnity/Editor/Tools/RunTests.cs (2)

55-76: LGTM: Clean job-based execution model.

The immediate return with job_id and status metadata is the correct pattern for async job management. The normalization of "already in progress" errors to a structured response with retry_after_ms hint (lines 70-73) provides good client guidance.


78-125: LGTM: Robust filter parameter handling.

The GetFilterOptions implementation correctly handles both string and array inputs for filter parameters, with appropriate null/whitespace filtering. The local ParseStringArray function is well-structured and handles edge cases properly.

Server/src/services/tools/run_tests.py (3)

43-88: LGTM: Well-structured Pydantic models for async job lifecycle.

The new data models provide strong typing for the job-based workflow. The use of optional fields and nested structures (e.g., TestJobProgress within GetTestJobData) properly represents the asynchronous job lifecycle states.


97-155: LGTM: Clean async job start implementation.

The refactored run_tests properly:

  • Uses preflight checks to prevent concurrent test runs (line 116)
  • Handles string/list parameter coercion elegantly with _coerce_string_list (lines 120-128)
  • Employs walrus operator for concise parameter building (lines 131-142)
  • Returns typed RunTestsStartResponse for immediate job acknowledgment

165-191: LGTM: Straightforward job polling endpoint.

The get_test_job implementation correctly polls job status by job_id and returns typed GetTestJobResponse. The parameter handling and response wrapping are consistent with the run_tests pattern.

Server/tests/integration/test_run_tests_async.py (2)

8-31: LGTM: Test correctly updated for new run_tests API.

The test properly validates:

  • Import from services.tools.run_tests (line 8)
  • Monkeypatch on the new module path (lines 17-19)
  • Command type "run_tests" (line 27)
  • Parameter forwarding to Unity transport (lines 28-30)

35-52: LGTM: get_test_job polling test properly structured.

The test validates the new polling endpoint with correct:

  • Import path (line 36)
  • Monkeypatch setup (lines 45-47)
  • Command type assertion for "get_test_job" (line 50)
  • Job ID parameter forwarding (lines 51-52)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/RunTestsTests.cs (2)

17-49: Reflection-based testing is appropriate but fragile.

The use of reflection to set TestJobManager._currentJobId (lines 19-30) is necessary for testing the busy-state behavior without triggering actual test runs. The cleanup in the finally block (lines 45-48) properly restores state.

However, be aware that reflection-based tests are fragile and will break if internal field names change. Based on learnings, this approach is acceptable in MCPForUnity's testing strategy.

Consider adding a comment documenting the fragility:

// NOTE: This test uses reflection to manipulate internal state.
// If TestJobManager refactoring breaks this test, consider exposing
// a test-only method to simulate busy state.

52-63: LGTM: Clean validation of invalid mode handling.

The test properly validates that an invalid mode parameter returns an ErrorResponse with an appropriate error message (line 62). The synchronous .GetAwaiter().GetResult() pattern (line 57) correctly handles the non-async Task<object> return type.

Server/src/transport/plugin_hub.py (1)

122-123: Formatting improvements enhance readability.

The multiline reformatting of conditionals, dictionary assignments, and function calls improves code readability without changing behavior.

Also applies to: 182-183, 373-374, 486-487, 489-490, 506-507

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: 4

Caution

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

⚠️ Outside diff range comments (2)
.claude/prompts/nl-unity-suite-t.md (1)

272-272: Update template example to reflect the new canonical URI scheme.

The template example at line 272 still references the old unity://path/... scheme, but the canonical URI format established at line 40 now mandates mcpforunity://path/Assets/.... Update the example to reflect the new scheme for consistency.

💡 Proposed fix
- Edit via unity://path/... succeeded.
+ Edit via mcpforunity://path/Assets/Scripts/LongUnityScriptClaudeTest.cs succeeded.
.claude/prompts/nl-gameobject-suite.md (1)

6-6: AllowedTools missing read_resource tool used in tests GO-2, GO-3, GO-7.

Lines 54, 62, and 97 call mcp__UnityMCP__read_resource, but this tool is not declared in the AllowedTools list. The test suite will fail at runtime when these test cases attempt to execute.

Add the missing tool to the AllowedTools declaration:

-AllowedTools: Write,mcp__UnityMCP__manage_editor,mcp__UnityMCP__manage_gameobject,mcp__UnityMCP__find_gameobjects,mcp__UnityMCP__manage_components,mcp__UnityMCP__manage_scene,mcp__UnityMCP__read_console
+AllowedTools: Write,mcp__UnityMCP__manage_editor,mcp__UnityMCP__manage_gameobject,mcp__UnityMCP__find_gameobjects,mcp__UnityMCP__manage_components,mcp__UnityMCP__manage_scene,mcp__UnityMCP__read_resource,mcp__UnityMCP__read_console
🤖 Fix all issues with AI agents
In @Server/src/services/resources/editor_state.py:
- Around line 128-172: Function _infer_single_instance_id declares an unused
ctx: Context parameter—remove the ctx parameter from the function signature and
all internal/type hints so the function becomes async def
_infer_single_instance_id() -> str | None, then update every call site that
passes a Context (the reviewed call site that previously invoked
_infer_single_instance_id(ctx) must be changed to call
_infer_single_instance_id() instead); while here the broad excepts are
acceptable for best-effort behavior, consider narrowing them
(ImportError/AttributeError) if you want more precise error handling.

In @Server/src/services/resources/menu_items.py:
- Around line 14-18: The decorator call using mcp_for_unity_resource has a typo
in the uri argument: change the duplicated scheme string
"mcpformcpforunity://menu-items" to the correct "mcpforunity://menu-items" in
the mcp_for_unity_resource(...) invocation so the resource URI matches the rest
of the project.

In @Server/src/services/resources/tests.py:
- Line 24: The URI strings in the mcp_for_unity_resource decorators are
misspelled with a duplicated prefix ("mcpformcpforunity://"); update the uri
values used in the @mcp_for_unity_resource decorators (e.g., the one annotating
get_tests and the other at the second occurrence) to use the correct scheme
"mcpforunity://" (so "mcpformcpforunity://tests" → "mcpforunity://tests" and
similarly for the other decorator).

In @Server/src/transport/plugin_hub.py:
- Line 149: The class references cls.FAST_FAIL_TIMEOUT in methods like
read_console, get_editor_state and ping but that attribute is missing; add a
FAST_FAIL_TIMEOUT class attribute alongside the other timeout constants in the
PluginHub class (e.g. define FAST_FAIL_TIMEOUT as a numeric timeout in seconds
consistent with the existing TIMEOUT/STARTUP_TIMEOUT values) so
cls.FAST_FAIL_TIMEOUT resolves and fast-fail commands use the intended timeout.
🧹 Nitpick comments (4)
Server/src/transport/plugin_hub.py (2)

51-52: Consider annotating mutable class attribute with ClassVar.

Mutable class attributes like _FAST_FAIL_COMMANDS should be annotated with typing.ClassVar to clarify that they are class-level attributes and prevent accidental instance-level overrides.

♻️ Proposed refactor
+from typing import Any, ClassVar
+
 class PluginHub(WebSocketEndpoint):
     """Manages persistent WebSocket connections to Unity plugins."""
 
     encoding = "json"
     KEEP_ALIVE_INTERVAL = 15
     SERVER_TIMEOUT = 30
     COMMAND_TIMEOUT = 30
     # Fast-path commands should never block the client for long; return a retry hint instead.
     # This helps avoid the Cursor-side ~30s tool-call timeout when Unity is compiling/reloading
     # or is throttled while unfocused.
-    _FAST_FAIL_COMMANDS: set[str] = {
+    _FAST_FAIL_COMMANDS: ClassVar[set[str]] = {
         "read_console", "get_editor_state", "ping"}

452-453: Consider defining error messages as exception class attributes.

Static analysis suggests moving the error message to the NoUnitySessionError class definition for better reusability and consistency. However, this is a minor refactoring opportunity since the message is only used once.

♻️ Optional refactor

Define the message on the exception class:

class NoUnitySessionError(RuntimeError):
    """Raised when no Unity plugins are available."""
    DEFAULT_MESSAGE = "No Unity plugins are currently connected"

Then use it:

-        raise NoUnitySessionError(
-            "No Unity plugins are currently connected")
+        raise NoUnitySessionError(NoUnitySessionError.DEFAULT_MESSAGE)
Server/tests/integration/test_editor_state_v2_contract.py (1)

27-27: Consider prefixing unused parameters with underscore.

The unused parameters send_fn, unity_instance, params, and kwargs are intentionally present to match the expected signature for the monkeypatch. Consider prefixing them with underscore to signal they're deliberately unused and silence the linter.

♻️ Optional refactoring to clarify intent
-    async def fake_send_with_unity_instance(send_fn, unity_instance, command_type, params, **kwargs):
+    async def fake_send_with_unity_instance(_send_fn, _unity_instance, command_type, _params, **_kwargs):
         # Minimal stub payload for v2 resource tests. The server layer should enrich with staleness/advice.
         assert command_type == "get_editor_state"
Server/src/services/resources/editor_state.py (1)

254-285: Consider logging the exception in the external changes block.

The try-except-pass at lines 284-285 silently swallows all exceptions during external changes enrichment. While this prevents external change tracking failures from breaking the entire state response, it makes debugging difficult if issues occur.

🔧 Recommended improvement
     except Exception:
-        pass
+        # External changes enrichment is optional; failures shouldn't break editor_state.
+        # Log at debug level if observability is needed in the future.
+        pass

Or, if logging infrastructure is available:

-    except Exception:
-        pass
+    except Exception as e:
+        # External changes enrichment is optional; log and continue.
+        logger.debug(f"Failed to enrich external changes for instance {instance_id}: {e}")
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 93f1084 and 21db63a.

📒 Files selected for processing (41)
  • .claude/prompts/nl-gameobject-suite.md
  • .claude/prompts/nl-unity-suite-nl.md
  • .claude/prompts/nl-unity-suite-t.md
  • .github/scripts/mark_skipped.py
  • MCPForUnity/Editor/Tools/ManageScript.cs
  • README-zh.md
  • README.md
  • Server/src/main.py
  • Server/src/services/registry/resource_registry.py
  • Server/src/services/resources/active_tool.py
  • Server/src/services/resources/custom_tools.py
  • Server/src/services/resources/editor_state.py
  • Server/src/services/resources/gameobject.py
  • Server/src/services/resources/layers.py
  • Server/src/services/resources/menu_items.py
  • Server/src/services/resources/prefab_stage.py
  • Server/src/services/resources/project_info.py
  • Server/src/services/resources/selection.py
  • Server/src/services/resources/tags.py
  • Server/src/services/resources/tests.py
  • Server/src/services/resources/unity_instances.py
  • Server/src/services/resources/windows.py
  • Server/src/services/tools/execute_custom_tool.py
  • Server/src/services/tools/find_gameobjects.py
  • Server/src/services/tools/find_in_file.py
  • Server/src/services/tools/manage_components.py
  • Server/src/services/tools/manage_script.py
  • Server/src/services/tools/script_apply_edits.py
  • Server/src/services/tools/set_active_instance.py
  • Server/src/transport/legacy/unity_connection.py
  • Server/src/transport/plugin_hub.py
  • Server/tests/integration/test_edit_normalization_and_noop.py
  • Server/tests/integration/test_edit_strict_and_warnings.py
  • Server/tests/integration/test_editor_state_v2_contract.py
  • Server/tests/integration/test_gameobject_resources.py
  • Server/tests/integration/test_get_sha.py
  • Server/tests/integration/test_manage_script_uri.py
  • Server/tests/integration/test_script_tools.py
  • Server/tests/integration/test_validate_script_summary.py
  • docs/README-DEV-zh.md
  • docs/v8_NEW_NETWORKING_SETUP.md
✅ Files skipped from review due to trivial changes (5)
  • .claude/prompts/nl-unity-suite-nl.md
  • Server/src/services/tools/manage_components.py
  • Server/tests/integration/test_gameobject_resources.py
  • Server/src/services/registry/resource_registry.py
  • Server/src/services/resources/unity_instances.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • Server/src/services/tools/set_active_instance.py
  • Server/src/services/tools/script_apply_edits.py
  • Server/src/main.py
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2025-09-05T16:22:04.960Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 265
File: README.md:204-204
Timestamp: 2025-09-05T16:22:04.960Z
Learning: In the Unity MCP project, the ServerInstaller.cs creates a symlink from ~/Library/AppSupport to ~/Library/Application Support on macOS to mitigate argument parsing and quoting issues in some MCP clients. The README documentation should use the shortened AppSupport path, not the full "Application Support" path with spaces.

Applied to files:

  • .claude/prompts/nl-unity-suite-t.md
  • Server/tests/integration/test_script_tools.py
  • docs/v8_NEW_NETWORKING_SETUP.md
  • MCPForUnity/Editor/Tools/ManageScript.cs
  • Server/src/services/resources/project_info.py
  • README-zh.md
  • Server/src/services/tools/manage_script.py
📚 Learning: 2025-09-05T16:22:04.960Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 265
File: README.md:204-204
Timestamp: 2025-09-05T16:22:04.960Z
Learning: In the Unity MCP project, the ServerInstaller.cs creates a symlink from ~/Library/AppSupport to ~/Library/Application Support on macOS to avoid spaces in paths that can cause arg parsing and quoting issues in some MCP clients. The README documentation should use the shortened AppSupport path, not the full "Application Support" path.

Applied to files:

  • .claude/prompts/nl-unity-suite-t.md
  • Server/tests/integration/test_script_tools.py
  • docs/v8_NEW_NETWORKING_SETUP.md
  • MCPForUnity/Editor/Tools/ManageScript.cs
  • README-zh.md
📚 Learning: 2025-10-13T13:27:23.040Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 316
File: TestProjects/UnityMCPTests/Assets/Tests/EditMode/Resources.meta:1-8
Timestamp: 2025-10-13T13:27:23.040Z
Learning: UnityMcpBridge is a legacy project kept for backwards compatibility; MCPForUnity is the only active Unity plugin project. GUID collisions between UnityMcpBridge and MCPForUnity are acceptable.

Applied to files:

  • .claude/prompts/nl-unity-suite-t.md
  • docs/v8_NEW_NETWORKING_SETUP.md
  • Server/src/services/resources/custom_tools.py
  • MCPForUnity/Editor/Tools/ManageScript.cs
  • Server/src/services/resources/project_info.py
  • Server/src/services/tools/execute_custom_tool.py
  • README-zh.md
  • Server/src/services/resources/gameobject.py
  • README.md
📚 Learning: 2025-09-04T01:01:11.927Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 260
File: UnityMcpBridge/UnityMcpServer~/src/server_version.txt:1-1
Timestamp: 2025-09-04T01:01:11.927Z
Learning: The UnityMcpBridge project is not maintaining changelogs yet, so don't suggest adding changelog entries for version bumps.

Applied to files:

  • .claude/prompts/nl-unity-suite-t.md
  • MCPForUnity/Editor/Tools/ManageScript.cs
  • docs/README-DEV-zh.md
  • README.md
📚 Learning: 2025-10-13T13:41:00.086Z
Learnt from: JohanHoltby
Repo: CoplayDev/unity-mcp PR: 309
File: MCPForUnity/Editor/Helpers/ServerInstaller.cs:478-508
Timestamp: 2025-10-13T13:41:00.086Z
Learning: In the MCPForUnityTools feature (MCPForUnity/Editor/Helpers/ServerInstaller.cs), the design intentionally forces users to have only one .py file per MCPForUnityTools folder to keep file tracking simple. Package-style tools (subdirectories with __init__.py) are not supported.

Applied to files:

  • .claude/prompts/nl-unity-suite-t.md
  • Server/tests/integration/test_script_tools.py
  • docs/v8_NEW_NETWORKING_SETUP.md
  • Server/src/services/resources/custom_tools.py
  • MCPForUnity/Editor/Tools/ManageScript.cs
  • docs/README-DEV-zh.md
  • Server/src/services/resources/layers.py
  • README-zh.md
  • README.md
📚 Learning: 2025-12-29T15:23:17.871Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:17.871Z
Learning: In the MCPForUnity project, the team prefers to rely on their testing process to catch UI initialization issues rather than adding defensive null checks for UI elements in editor windows.

Applied to files:

  • .claude/prompts/nl-unity-suite-t.md
  • docs/v8_NEW_NETWORKING_SETUP.md
  • README-zh.md
📚 Learning: 2025-12-19T21:09:44.740Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 468
File: Server/DOCKER_OVERVIEW.md:22-22
Timestamp: 2025-12-19T21:09:44.740Z
Learning: In the Unity MCP project, the Docker workflow (.github/workflows/publish-docker.yml) intentionally uses the DOCKER_USERNAME secret instead of hardcoding "msanatan" to allow users who fork the repository to publish to their own Docker Hub repositories, while the documentation (Server/DOCKER_OVERVIEW.md) correctly references the official image location "msanatan/mcp-for-unity-server" for end users.

Applied to files:

  • .claude/prompts/nl-unity-suite-t.md
  • README-zh.md
📚 Learning: 2025-11-05T18:23:12.349Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 368
File: MCPForUnity/UnityMcpServer~/src/resources/menu_items.py:15-15
Timestamp: 2025-11-05T18:23:12.349Z
Learning: In Unity MCP, the `name` parameter in the `mcp_for_unity_resource` decorator is the external API name exposed to MCP clients (LLMs, AI agents). The command string passed to `async_send_command_with_retry` or `async_send_with_unity_instance` (e.g., "get_menu_items") is the internal command identifier that must match the C# side. These are decoupled, allowing external API naming to evolve independently of internal command routing.

Applied to files:

  • Server/src/services/resources/menu_items.py
  • docs/v8_NEW_NETWORKING_SETUP.md
  • Server/src/services/resources/custom_tools.py
  • .claude/prompts/nl-gameobject-suite.md
  • Server/src/services/resources/selection.py
  • MCPForUnity/Editor/Tools/ManageScript.cs
  • Server/src/services/resources/tests.py
  • Server/src/services/resources/tags.py
  • Server/src/services/resources/project_info.py
  • Server/src/services/resources/prefab_stage.py
  • Server/src/services/resources/layers.py
  • Server/src/services/tools/execute_custom_tool.py
  • Server/src/services/resources/active_tool.py
  • README-zh.md
  • Server/src/services/resources/gameobject.py
  • README.md
  • Server/src/services/resources/windows.py
📚 Learning: 2025-10-03T22:11:46.002Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 301
File: docs/CUSTOM_TOOLS.md:54-62
Timestamp: 2025-10-03T22:11:46.002Z
Learning: In Unity MCP, the `description` parameter in the `mcp_for_unity_tool` decorator is technically optional but should always be included as a best practice. Without it, there's a higher chance that MCP clients will not parse the tool correctly. All Unity MCP tools should include the description in the decorator for compatibility.

Applied to files:

  • Server/src/services/resources/menu_items.py
  • docs/v8_NEW_NETWORKING_SETUP.md
  • Server/src/services/resources/custom_tools.py
  • Server/src/services/resources/selection.py
  • Server/src/services/resources/project_info.py
  • Server/src/services/tools/execute_custom_tool.py
  • Server/src/services/resources/active_tool.py
  • README-zh.md
  • Server/src/services/tools/find_gameobjects.py
📚 Learning: 2025-09-03T16:00:55.839Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 0
File: :0-0
Timestamp: 2025-09-03T16:00:55.839Z
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:

  • docs/v8_NEW_NETWORKING_SETUP.md
  • .claude/prompts/nl-gameobject-suite.md
📚 Learning: 2025-12-29T15:23:11.613Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:11.613Z
Learning: In MCPForUnity, prefer relying on the established testing process to catch UI initialization issues instead of adding defensive null checks for UI elements in editor windows. This means during reviews, verify that tests cover UI initialization paths and that code avoids repetitive null-check boilerplate in Editor Windows. If a UI element can be null, ensure there is a well-tested fallback or that its initialization is guaranteed by design, rather than sprinkling null checks throughout editor code.

Applied to files:

  • MCPForUnity/Editor/Tools/ManageScript.cs
📚 Learning: 2025-11-27T21:09:35.011Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 401
File: MCPForUnity/Editor/Clients/Configurators/VSCodeConfigurator.cs:10-18
Timestamp: 2025-11-27T21:09:35.011Z
Learning: VS Code GitHub Copilot MCP configuration supports mcp.json files placed directly in the Code/User directory: %APPDATA%\Code\User\mcp.json on Windows, ~/Library/Application Support/Code/User/mcp.json on macOS, and ~/.config/Code/User/mcp.json on Linux. This is in addition to workspace-scoped .vscode/mcp.json files.

Applied to files:

  • README-zh.md
🧬 Code graph analysis (7)
Server/tests/integration/test_script_tools.py (1)
Server/tests/integration/test_helpers.py (1)
  • DummyContext (16-55)
Server/src/services/resources/tests.py (1)
Server/src/services/registry/resource_registry.py (1)
  • mcp_for_unity_resource (10-43)
Server/tests/integration/test_get_sha.py (2)
Server/src/services/tools/manage_script.py (1)
  • get_sha (622-645)
Server/tests/integration/test_helpers.py (1)
  • DummyContext (16-55)
Server/tests/integration/test_validate_script_summary.py (2)
Server/src/services/tools/manage_script.py (1)
  • validate_script (463-500)
Server/tests/integration/test_helpers.py (1)
  • DummyContext (16-55)
Server/src/services/tools/find_in_file.py (1)
Server/src/services/tools/manage_script.py (1)
  • _split_uri (15-64)
Server/src/services/resources/editor_state.py (6)
Server/src/transport/legacy/unity_connection.py (1)
  • async_send_command_with_retry (838-870)
Server/src/services/tools/preflight.py (1)
  • _in_pytest (11-14)
Server/src/transport/unity_transport.py (2)
  • _current_transport (23-25)
  • send_with_unity_instance (79-114)
Server/src/transport/plugin_hub.py (1)
  • get_sessions (217-231)
Server/src/services/resources/project_info.py (1)
  • get_project_info (30-39)
Server/src/services/state/external_changes_scanner.py (2)
  • set_project_root (51-54)
  • update_and_get (173-241)
Server/tests/integration/test_editor_state_v2_contract.py (2)
Server/src/services/registry/resource_registry.py (1)
  • get_registered_resources (46-48)
Server/tests/integration/test_helpers.py (1)
  • DummyContext (16-55)
🪛 LanguageTool
docs/README-DEV-zh.md

[uncategorized] ~206-~206: 动词的修饰一般为‘形容词(副词)+地+动词’。您的意思是否是:保守"地"响应
Context: ... - 当 include_properties=true 时,会启用保守的响应大小预算(约 ~250KB JSON 文本),返回条数可能少于 `p...

(wb4)

🪛 markdownlint-cli2 (0.18.1)
README-zh.md

214-214: Bare URL used

(MD034, no-bare-urls)


216-216: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


225-225: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


226-226: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


227-227: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


446-446: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

🪛 Ruff (0.14.10)
Server/src/services/resources/editor_state.py

128-128: Unused function argument: ctx

(ARG001)


138-138: Do not catch blind exception: Exception

(BLE001)


155-155: Do not catch blind exception: Exception

(BLE001)


169-169: Do not catch blind exception: Exception

(BLE001)


179-179: Do not catch blind exception: Exception

(BLE001)


284-285: try-except-pass detected, consider logging the exception

(S110)


284-284: Do not catch blind exception: Exception

(BLE001)


297-297: Do not catch blind exception: Exception

(BLE001)

Server/src/transport/plugin_hub.py

51-52: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


452-453: Avoid specifying long messages outside the exception class

(TRY003)

Server/tests/integration/test_editor_state_v2_contract.py

14-14: Unused noqa directive (non-enabled: F401)

Remove unused noqa directive

(RUF100)


27-27: Unused function argument: send_fn

(ARG001)


27-27: Unused function argument: unity_instance

(ARG001)


27-27: Unused function argument: params

(ARG001)


27-27: Unused function argument: kwargs

(ARG001)

🔇 Additional comments (41)
Server/src/services/tools/find_gameobjects.py (1)

17-43: LGTM! Clean URL scheme migration and formatting improvements.

The changes successfully migrate from unity:// to mcpforunity:// URL scheme as intended in the PR objectives. The formatting adjustments (multi-line Literal type, Annotated parameter wrapping, docstring whitespace) improve readability without affecting runtime behavior.

.github/scripts/mark_skipped.py (1)

27-27: LGTM! Pattern updated to match the new URI scheme.

The pattern correctly reflects the migration from unity:// to mcpforunity:// throughout the codebase, ensuring that unknown-resource errors using the new scheme are properly classified as environmental failures and marked as skipped.

Server/src/transport/legacy/unity_connection.py (1)

587-587: LGTM! URI scheme updated correctly.

The error message now references mcpforunity://instances instead of the legacy unity:// scheme, aligning with the PR-wide migration to avoid naming conflicts.

.claude/prompts/nl-gameobject-suite.md (1)

54-54: Confirm URI scheme migration is complete and correct.

All resource URIs have been migrated from unity:// to mcpforunity:// scheme, which aligns with the PR objectives. The changes are consistent across test specs (GO-2, GO-3, GO-7) and the Tool Reference section (lines 134–136).

Verify that the new mcpforunity:// URIs are registered server-side and match the corresponding handler implementations. If you have access to the server-side resource registration code, confirm that these URI patterns are properly defined.

Also applies to: 62-62, 97-97, 134-136

Server/src/services/resources/active_tool.py (1)

33-37: LGTM! URI scheme migration applied correctly.

The URI scheme has been updated from unity:// to mcpforunity:// as intended. The internal command identifier and external API name remain unchanged, ensuring compatibility with the C# side and a clean migration path.

docs/README-DEV-zh.md (2)

1-360: Verify URI scheme documentation reflects mcpforunity:// migration.

The AI summary notes that resource URIs were changed from unity:// to mcpforunity:// throughout the codebase. While the provided documentation appears to focus on development workflows rather than API references, you may want to verify that any examples or references to editor-state endpoints or tool resources elsewhere in the repo (or in complementary documentation like README-DEV.md) have been consistently updated to reflect the new URI scheme.


206-206: Fix Chinese grammar in line 206.

Per LanguageTool, the phrase "保守的响应大小预算" should be "保守地响应大小预算" to maintain the correct grammatical structure for a verb modifier (adjective/adverb + 地 + verb).

📝 Proposed grammar correction
-  - 当 `include_properties=true` 时,会启用保守的响应大小预算(约 **~250KB** JSON 文本),返回条数可能少于 `page_size`;使用 `next_cursor` 继续。
+  - 当 `include_properties=true` 时,会启用保守地响应大小预算(约 **~250KB** JSON 文本),返回条数可能少于 `page_size`;使用 `next_cursor` 继续。

Likely an incorrect or invalid review comment.

Server/src/services/resources/tags.py (1)

17-17: LGTM—URI scheme migration completed correctly.

The URI update from unity:// to mcpforunity://project/tags aligns with the PR objectives to avoid naming conflicts. Documentation (README.md and README-zh.md) has been updated to reflect the new URI scheme. The internal command identifier and external API name remain unchanged, ensuring proper decoupling.

Server/src/services/tools/find_in_file.py (1)

16-29: URI scheme migration is complete and verified.

The change from unity://path/ to mcpforunity://path/ has been successfully applied across the codebase. All callers—including test files and related tool modules (manage_script.py, script_apply_edits.py)—have been updated to use the new mcpforunity:// scheme. No remaining references to the old unity://path/ scheme exist.

MCPForUnity/Editor/Tools/ManageScript.cs (2)

2640-2641: LGTM! URI parsing logic correctly updated.

The SanitizeAssetsPath method now strips the new mcpforunity://path/ prefix. The substring length calculation is correct (19 characters), and the case-insensitive comparison is appropriate for URI scheme matching.


285-285: URI scheme migration is complete and consistent.

All response URI constructions have been properly updated from unity://path/ to mcpforunity://path/ across get_sha, create, read, update, apply_text_edits (both cases), and edit actions. The SanitizeAssetsPath function correctly handles the new scheme by checking and stripping the mcpforunity://path/ prefix. No lingering unity:// references remain in the file.

Also applies to: 375-375, 404-404, 484-484, 707-707, 808-808, 1378-1378, 1444-1444

Server/src/transport/plugin_hub.py (1)

420-420: URI migration looks good.

The updates from unity://instances to mcpforunity://instances are consistent with the PR objectives to avoid naming conflicts and align with Unity's requests.

Also applies to: 441-441

Server/tests/integration/test_get_sha.py (1)

42-53: LGTM! URI scheme migration properly applied.

The test correctly updates both the mock response data and the test input to use the new mcpforunity:// URI scheme, maintaining consistency between the mocked Unity response and the test invocation.

Server/tests/integration/test_validate_script_summary.py (1)

55-55: LGTM! Test updated for new URI scheme.

The test invocation correctly uses the mcpforunity:// URI scheme.

Server/tests/integration/test_script_tools.py (4)

71-71: LGTM! Test updated for new URI scheme.


99-104: LGTM! Sequential edits test updated consistently.

Both test invocations correctly use the mcpforunity:// URI scheme.


135-135: LGTM! Options forwarding test updated.


168-168: LGTM! Multi-span test updated.

Server/tests/integration/test_manage_script_uri.py (1)

52-52: LGTM! URI parsing test updated for new scheme.

The test correctly verifies that the mcpforunity:// URI scheme is properly parsed into name (MyScript) and path (Assets/Scripts) components.

Server/src/services/resources/custom_tools.py (1)

25-34: LGTM! Resource URI and error messaging updated consistently.

Both the resource decorator URI and the error message reference correctly use the new mcpforunity:// scheme. The error message appropriately directs users to mcpforunity://instances for resolving the active Unity instance.

docs/v8_NEW_NETWORKING_SETUP.md (1)

189-189: LGTM: Documentation correctly updated to reflect new URI scheme.

The URI update from unity://custom-tools to mcpforunity://custom-tools aligns with the broader migration to avoid naming conflicts with Unity and other MCPs.

Server/src/services/resources/windows.py (1)

34-34: LGTM: Clean URI migration.

The resource URI has been correctly updated to the new mcpforunity:// scheme while maintaining backward compatibility through unchanged internal command routing.

Server/src/services/resources/selection.py (1)

42-42: LGTM: URI scheme updated consistently.

The resource endpoint has been migrated to mcpforunity://editor/selection while preserving the internal command identifier and function behavior.

Server/src/services/resources/prefab_stage.py (1)

26-26: LGTM: Prefab stage resource URI updated.

The endpoint migration to mcpforunity://editor/prefab-stage is consistent with the broader URI scheme change across all resources.

Server/src/services/tools/manage_script.py (2)

15-64: LGTM: URI parsing correctly extended to support new scheme.

The _split_uri function has been properly updated to handle mcpforunity://path/ URIs alongside existing file:// and plain path formats. The prefix stripping logic is correct, and the normalized path flows through the same validation and Assets-relative path extraction as before.


89-89: LGTM: Parameter annotations consistently document new URI scheme.

All script management tools that accept a uri parameter (apply_text_edits, delete_script, validate_script, get_sha) have been updated to document support for the mcpforunity://path/Assets/... format alongside existing schemes. This provides clear guidance for MCP clients and developers.

Also applies to: 437-437, 465-465, 624-624

README-zh.md (2)

79-96: LGTM! Resource listings updated correctly.

The resource URIs have been properly migrated to the mcpforunity:// scheme, and the descriptions align with the expanded public API catalog mentioned in the PR objectives.


367-376: Good addition: Performance guidance for batch_execute.

The before/after examples clearly demonstrate the performance benefits of using batch_execute for multiple operations. This is helpful user-facing documentation.

Server/src/services/tools/execute_custom_tool.py (1)

24-27: LGTM! Error message updated to match new URI scheme.

The error message correctly guides users to mcpforunity://instances, aligning with the PR's URI migration from unity:// to mcpforunity://.

Server/tests/integration/test_edit_normalization_and_noop.py (1)

55-58: LGTM! Test URIs updated consistently.

All test cases correctly use the new mcpforunity:// URI scheme, maintaining test coverage while aligning with the PR's URI migration objectives.

Also applies to: 82-85, 111-116, 163-167

Server/src/services/resources/project_info.py (1)

25-29: LGTM! Resource URI updated to new scheme.

The URI correctly migrates from unity:// to mcpforunity://, aligning with the PR's objective to avoid naming conflicts. The internal command identifier remains unchanged, which is the correct approach per project conventions.

Server/tests/integration/test_edit_strict_and_warnings.py (1)

47-47: LGTM! URI scheme migration is correct.

The URI scheme updates from unity:// to mcpforunity:// align with the project-wide migration described in the PR objectives.

Also applies to: 80-80

Server/src/services/resources/layers.py (1)

16-16: LGTM! URI scheme migration is correct.

The URI scheme update from unity://project/layers to mcpforunity://project/layers is consistent with the project-wide migration to avoid conflicts with other MCPs and Unity naming.

Server/src/services/resources/gameobject.py (1)

5-7: LGTM! URI scheme migration and documentation updates are correct.

All GameObject resource URIs and documentation examples have been correctly updated from unity://scene/gameobject/... to mcpforunity://scene/gameobject/.... The changes are consistent and comprehensive.

Also applies to: 43-43, 66-83, 130-130, 171-171, 217-217

Server/tests/integration/test_editor_state_v2_contract.py (1)

11-11: LGTM! Editor state migration to unified resource is correct.

The test correctly migrates from v2-specific to the canonical mcpforunity://editor/state resource. The import on line 14 properly triggers decorator registration (the noqa: F401 comment is intentional and should remain), and the resource lookup and invocation logic have been appropriately updated.

Also applies to: 13-14, 18-25, 29-29, 45-45

README.md (1)

58-72: Documentation updates look excellent.

The tool and resource documentation changes are clear, consistent, and well-aligned with the PR objectives:

  • New tools (find_in_file, debug_request_context, apply_text_edits, script_apply_edits, validate_script, create_script, delete_script, get_sha) are properly documented with concise descriptions
  • URI scheme migration from unity:// to mcpforunity:// is consistently applied across all resources
  • The run_tests tool description accurately reflects the async/polling model

Also applies to: 81-97

Server/src/services/resources/editor_state.py (5)

1-13: LGTM! Imports are clean and purposeful.

All new imports support the refactored editor state infrastructure (external changes scanning, transport integration, timing utilities, type hints).


16-116: Well-structured data models for the v2 schema.

The EditorStateV2* models provide a clear, strongly-typed contract for the editor state snapshot. The use of optional fields throughout is appropriate for a flexible state representation that may be partially populated depending on Unity's current state.


119-126: LGTM! Utility functions are simple and correct.

Both helpers serve clear purposes: _now_unix_ms() provides consistent timestamp formatting, and _in_pytest() prevents discovery side effects during testing.


174-212: Solid readiness assessment logic.

The function correctly computes staleness, identifies blocking conditions (compilation, domain reload, tests, asset refresh), and provides actionable advice. The 2-second staleness threshold is well-documented and reasonable for editor polling scenarios.


215-305: Well-structured editor state handler with good error handling.

The refactored get_editor_state function properly:

  • Migrates to the mcpforunity://editor/state URI scheme
  • Ensures Unity instance identification (explicit or inferred)
  • Enriches state with server-side external changes detection
  • Validates the payload against the typed EditorStateV2Data schema
  • Returns structured errors on validation failure

The overall design is solid and handles edge cases appropriately.

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

🧹 Nitpick comments (1)
Server/src/transport/plugin_hub.py (1)

48-55: LGTM – Previous critical issue resolved.

The FAST_FAIL_TIMEOUT constant is now properly defined, resolving the AttributeError flagged in the previous review. The comment clearly explains the rationale for the short timeout.

Optional: Consider annotating _FAST_FAIL_COMMANDS with ClassVar to satisfy the static analysis hint (RUF012) and make the intent explicit that this is a class-level constant not meant to be overridden per-instance.

♻️ Optional type annotation fix
+from typing import Any, ClassVar
-from typing import Any

 ...

-    _FAST_FAIL_COMMANDS: set[str] = {
+    _FAST_FAIL_COMMANDS: ClassVar[set[str]] = {
         "read_console", "get_editor_state", "ping"}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21db63a and e03df44.

📒 Files selected for processing (4)
  • MCPForUnity/Editor/Dependencies/DependencyManager.cs
  • MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs
  • Server/src/transport/plugin_hub.py
  • tools/prepare_unity_asset_store_release.py
💤 Files with no reviewable changes (1)
  • tools/prepare_unity_asset_store_release.py
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-05T16:22:04.960Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 265
File: README.md:204-204
Timestamp: 2025-09-05T16:22:04.960Z
Learning: In the Unity MCP project, the ServerInstaller.cs creates a symlink from ~/Library/AppSupport to ~/Library/Application Support on macOS to mitigate argument parsing and quoting issues in some MCP clients. The README documentation should use the shortened AppSupport path, not the full "Application Support" path with spaces.

Applied to files:

  • MCPForUnity/Editor/Dependencies/DependencyManager.cs
📚 Learning: 2025-12-29T15:23:11.613Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:11.613Z
Learning: In MCPForUnity, prefer relying on the established testing process to catch UI initialization issues instead of adding defensive null checks for UI elements in editor windows. This means during reviews, verify that tests cover UI initialization paths and that code avoids repetitive null-check boilerplate in Editor Windows. If a UI element can be null, ensure there is a well-tested fallback or that its initialization is guaranteed by design, rather than sprinkling null checks throughout editor code.

Applied to files:

  • MCPForUnity/Editor/Dependencies/DependencyManager.cs
  • MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs
📚 Learning: 2025-09-05T16:22:04.960Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 265
File: README.md:204-204
Timestamp: 2025-09-05T16:22:04.960Z
Learning: In the Unity MCP project, the ServerInstaller.cs creates a symlink from ~/Library/AppSupport to ~/Library/Application Support on macOS to avoid spaces in paths that can cause arg parsing and quoting issues in some MCP clients. The README documentation should use the shortened AppSupport path, not the full "Application Support" path.

Applied to files:

  • MCPForUnity/Editor/Dependencies/DependencyManager.cs
📚 Learning: 2025-11-05T18:23:12.349Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 368
File: MCPForUnity/UnityMcpServer~/src/resources/menu_items.py:15-15
Timestamp: 2025-11-05T18:23:12.349Z
Learning: In Unity MCP, the `name` parameter in the `mcp_for_unity_resource` decorator is the external API name exposed to MCP clients (LLMs, AI agents). The command string passed to `async_send_command_with_retry` or `async_send_with_unity_instance` (e.g., "get_menu_items") is the internal command identifier that must match the C# side. These are decoupled, allowing external API naming to evolve independently of internal command routing.

Applied to files:

  • MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs
🪛 Ruff (0.14.10)
Server/src/transport/plugin_hub.py

54-55: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


455-456: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (10)
MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs (1)

23-23: LGTM! Clear improvement to menu label.

The addition of "Local" to the Setup Window menu item improves clarity and aligns with the PR's objective to differentiate local vs remote setup workflows.

MCPForUnity/Editor/Dependencies/DependencyManager.cs (1)

139-139: LGTM! Consistent with menu item update.

The message correctly references the updated "Local Setup Window" menu path, maintaining consistency with the MenuItem change in MCPForUnityMenu.cs.

Server/src/transport/plugin_hub.py (8)

125-126: LGTM – formatting only.


148-156: LGTM – cleaner timeout resolution.

Using the class constant cls.FAST_FAIL_TIMEOUT instead of repeated environment variable lookups improves consistency and maintainability.


185-186: LGTM – formatting only.


376-381: LGTM – proper fallback in exception path.


421-424: LGTM – URI scheme updated consistently.

The error messages now correctly reference mcpforunity://instances, aligning with the PR objective to migrate all resource URIs to the mcpforunity scheme to avoid conflicts.

Also applies to: 442-445


455-456: LGTM – clear error message.

The static analysis hint (TRY003) suggests moving the message into the exception class, but this is a minor stylistic preference. The current approach is readable and the message is appropriately concise.


489-497: LGTM – consistent environment variable handling.

The multiline formatting and exception handling pattern matches the approach used elsewhere in the file.


509-510: LGTM – formatting only.

…ypes

Added SerializeAnimationCurve and SerializeMinMaxCurve helper methods to properly serialize Unity's curve types. Updated GetInfo to use these helpers for startLifetime, startSpeed, startSize, gravityModifier, and rateOverTime instead of only reading constant values.
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: 7

Caution

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

⚠️ Outside diff range comments (1)
Server/src/services/tools/refresh_unity.py (1)

86-91: Add logging to the broad exception handler.

The broad except Exception block silently swallows all errors, which can hide real issues such as bugs in the editor_state module, network problems, or configuration errors. While the cleanup may be best-effort, completely silent failures make debugging very difficult.

Add at least debug-level logging to provide visibility into why cleanup failed.

🔍 Proposed fix to add logging
+import logging
+
+logger = logging.getLogger(__name__)
+
 ...
 
     # After readiness is restored, clear any external-dirty flag for this instance so future tools can proceed cleanly.
     try:
         inst = unity_instance or await editor_state._infer_single_instance_id(ctx)
         if inst:
             external_changes_scanner.clear_dirty(inst)
-    except Exception:
-        pass
+    except Exception as e:
+        logger.debug(f"Failed to clear dirty flag after refresh: {e}")
🤖 Fix all issues with AI agents
In @MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs:
- Around line 94-107: In GameObjectModify.cs update the layer validation in the
block that reads layerName and calls LayerMask.NameToLayer: remove the
unnecessary "Default" special-case from the condition `if (layerId == -1 &&
layerName != "Default")` so it simply checks `if (layerId == -1)` and returns
the ErrorResponse on invalid names; keep the subsequent `if (layerId != -1 &&
targetGo.layer != layerId)` branch that sets `targetGo.layer` and `modified =
true` unchanged.

In @MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs:
- Around line 60-85: In AddBurst, the int→short casts for minCount and maxCount
can overflow if JSON provides values > short.MaxValue or negative; parse the
ints into local int variables first (e.g., parsedMin, parsedMax), validate or
clamp them to the valid short range (and ensure non-negative for burst counts),
then safely cast to short (or fail/return an error if out-of-range) before
constructing the ParticleSystem.Burst; update references to minCount and
maxCount in the method accordingly to avoid overflow and unexpected behavior.

In @Server/src/services/tools/refresh_unity.py:
- Line 87: The code is calling the private function
editor_state._infer_single_instance_id(ctx); change this to use a public API:
either rename the function to editor_state.infer_single_instance_id (remove the
leading underscore) or add a new public wrapper function (e.g.,
infer_single_instance_id) in the editor_state module that delegates to the
existing implementation, then update the call here to use inst = unity_instance
or await editor_state.infer_single_instance_id(ctx) so no private member is
accessed directly.
- Line 16: You are calling the private helper
editor_state._infer_single_instance_id(ctx) and silently swallowing all
exceptions; replace the private call with a public API (either use an existing
public function in editor_state or add a new exported function like
infer_single_instance_id(ctx)) and update the call site to use that public
function, and change the try/except to not catch Exception blindly—catch
specific exceptions you expect (e.g., ValueError or RuntimeError), or log the
exception via the module logger and re-raise if unrecoverable; ensure the
subsequent clear_dirty() is only called when the public infer function succeeds.

In @Server/src/services/tools/run_tests.py:
- Around line 190-195: The function currently constructs
GetTestJobResponse(**response) but returns the raw dict, breaking the declared
return type (GetTestJobResponse | MCPResponse); replace the final successful
branch to return the typed object instead of the dict (i.e., return
GetTestJobResponse(**response)), and/or update the function's return annotation
to dict | MCPResponse if you intend to return raw dicts; ensure any redundant
GetTestJobResponse(...) construction is removed or used and that imports/types
remain consistent.
- Around line 151-158: The function currently validates the payload by calling
RunTestsStartResponse(**response) but then returns the raw dict, breaking the
declared return type RunTestsStartResponse | MCPResponse; change the
implementation to return the constructed typed object (the RunTestsStartResponse
instance) instead of the raw dict (i.e., replace the discarded
RunTestsStartResponse(**response) call with returning that instance), or if raw
dicts must be preserved update the function signature to dict | MCPResponse and
adjust callers; reference RunTestsStartResponse, MCPResponse and the local
response variable when making the fix.
🧹 Nitpick comments (3)
Server/src/services/tools/run_tests.py (1)

120-128: Consider raising TypeError for unexpected input types.

The _coerce_string_list helper silently returns None when value is neither None, str, nor list (line 128). This could hide programming errors where an unexpected type (e.g., int, dict) is passed.

♻️ Proposed fix
     def _coerce_string_list(value) -> list[str] | None:
         if value is None:
             return None
         if isinstance(value, str):
             return [value] if value.strip() else None
         if isinstance(value, list):
             result = [str(v).strip() for v in value if v and str(v).strip()]
             return result if result else None
-        return None
+        raise TypeError(f"Expected str, list, or None; got {type(value).__name__}")
MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs (1)

70-70: Simplify the LINQ Contains call.

The fully qualified System.Linq.Enumerable.Contains is unnecessarily verbose. Since using System.Linq; is typically included at the top of the file, you can simplify this.

♻️ Proposed simplification
-                if (tagToSet != "Untagged" && !System.Linq.Enumerable.Contains(InternalEditorUtility.tags, tagToSet))
+                if (tagToSet != "Untagged" && !InternalEditorUtility.tags.Contains(tagToSet))
Server/src/services/tools/refresh_unity.py (1)

74-80: Consider simplifying the nested state extraction logic.

The defensive extraction with multiple isinstance checks and nested .get() calls is correct but quite complex. While this might be intentional during the migration to handle various response formats, consider simplifying once the migration is complete.

♻️ Potential simplification
-            state_resp = await editor_state.get_editor_state(ctx)
-            state = state_resp.model_dump() if hasattr(
-                state_resp, "model_dump") else state_resp
-            data = (state or {}).get("data") if isinstance(
-                state, dict) else None
-            advice = (data or {}).get(
-                "advice") if isinstance(data, dict) else None
-            if isinstance(advice, dict) and advice.get("ready_for_tools") is True:
+            state_resp = await editor_state.get_editor_state(ctx)
+            state = state_resp.model_dump() if hasattr(state_resp, "model_dump") else state_resp
+            ready = state.get("data", {}).get("advice", {}).get("ready_for_tools", False) if isinstance(state, dict) else False
+            if ready is True:
                 break
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e03df44 and 9122e1e.

📒 Files selected for processing (9)
  • MCPForUnity/Editor/Services/EditorStateCache.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectDelete.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectDuplicate.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleRead.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleWrite.cs
  • Server/src/services/tools/refresh_unity.py
  • Server/src/services/tools/run_tests.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • MCPForUnity/Editor/Tools/Vfx/ParticleWrite.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectDelete.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleRead.cs
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:17.871Z
Learning: In the MCPForUnity project, the team prefers to rely on their testing process to catch UI initialization issues rather than adding defensive null checks for UI elements in editor windows.
📚 Learning: 2025-11-05T18:23:12.349Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 368
File: MCPForUnity/UnityMcpServer~/src/resources/menu_items.py:15-15
Timestamp: 2025-11-05T18:23:12.349Z
Learning: In Unity MCP, the `name` parameter in the `mcp_for_unity_resource` decorator is the external API name exposed to MCP clients (LLMs, AI agents). The command string passed to `async_send_command_with_retry` or `async_send_with_unity_instance` (e.g., "get_menu_items") is the internal command identifier that must match the C# side. These are decoupled, allowing external API naming to evolve independently of internal command routing.

Applied to files:

  • Server/src/services/tools/refresh_unity.py
📚 Learning: 2025-10-13T13:41:00.086Z
Learnt from: JohanHoltby
Repo: CoplayDev/unity-mcp PR: 309
File: MCPForUnity/Editor/Helpers/ServerInstaller.cs:478-508
Timestamp: 2025-10-13T13:41:00.086Z
Learning: In the MCPForUnityTools feature (MCPForUnity/Editor/Helpers/ServerInstaller.cs), the design intentionally forces users to have only one .py file per MCPForUnityTools folder to keep file tracking simple. Package-style tools (subdirectories with __init__.py) are not supported.

Applied to files:

  • Server/src/services/tools/refresh_unity.py
📚 Learning: 2025-09-03T16:00:55.839Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 0
File: :0-0
Timestamp: 2025-09-03T16:00:55.839Z
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:

  • MCPForUnity/Editor/Services/EditorStateCache.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs
📚 Learning: 2025-10-13T13:27:23.040Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 316
File: TestProjects/UnityMCPTests/Assets/Tests/EditMode/Resources.meta:1-8
Timestamp: 2025-10-13T13:27:23.040Z
Learning: UnityMcpBridge is a legacy project kept for backwards compatibility; MCPForUnity is the only active Unity plugin project. GUID collisions between UnityMcpBridge and MCPForUnity are acceptable.

Applied to files:

  • MCPForUnity/Editor/Services/EditorStateCache.cs
📚 Learning: 2025-12-29T15:23:11.613Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:11.613Z
Learning: In MCPForUnity, prefer relying on the established testing process to catch UI initialization issues instead of adding defensive null checks for UI elements in editor windows. This means during reviews, verify that tests cover UI initialization paths and that code avoids repetitive null-check boilerplate in Editor Windows. If a UI element can be null, ensure there is a well-tested fallback or that its initialization is guaranteed by design, rather than sprinkling null checks throughout editor code.

Applied to files:

  • MCPForUnity/Editor/Services/EditorStateCache.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectDuplicate.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs
🧬 Code graph analysis (3)
Server/src/services/tools/run_tests.py (3)
Server/src/services/tools/preflight.py (1)
  • preflight (27-110)
Server/src/transport/legacy/unity_connection.py (1)
  • async_send_command_with_retry (838-870)
Server/src/transport/unity_transport.py (1)
  • send_with_unity_instance (79-114)
MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs (3)
MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs (5)
  • Type (395-408)
  • GameObjectComponentHelpers (16-409)
  • RemoveComponentInternal (85-118)
  • AddComponentInternal (18-83)
  • SetComponentPropertiesInternal (120-172)
MCPForUnity/Editor/Helpers/McpLog.cs (3)
  • McpLog (7-52)
  • Info (37-41)
  • Warn (43-46)
MCPForUnity/Editor/Tools/ManageEditor.cs (1)
  • AddTag (168-191)
MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs (3)
MCPForUnity/Editor/Tools/Vfx/ParticleCommon.cs (4)
  • ParticleSystem (8-12)
  • ParticleSystem (14-50)
  • ParticleSystem (52-85)
  • ParticleCommon (6-86)
MCPForUnity/Editor/Tools/Vfx/LineWrite.cs (1)
  • Clear (126-137)
MCPForUnity/Editor/Tools/Vfx/TrailControl.cs (1)
  • Clear (9-17)
🔇 Additional comments (21)
MCPForUnity/Editor/Tools/GameObjects/GameObjectDuplicate.cs (1)

1-84: Well-structured duplication handler with proper Unity Editor integration.

The implementation correctly:

  • Registers undo for the created object before modifications
  • Handles all parent scenarios (inherit, explicit root, specified parent)
  • Marks the scene dirty for proper save behavior
  • Returns comprehensive response data

The past review comment about the misleading warning message has been addressed (line 61 now correctly states the object will remain at root level).

MCPForUnity/Editor/Services/EditorStateCache.cs (3)

36-229: Well-structured typed model for editor state snapshot.

The introduction of strongly-typed nested classes with JsonProperty attributes is a solid improvement over anonymous objects. This provides:

  • Compile-time type safety
  • Clear schema documentation
  • Better IDE support and refactoring capabilities

The sealed modifier on all classes is appropriate for internal DTOs.

One minor observation: EditorStateV2LastRun.Counts (line 219) is typed as object, which loses type safety. If the counts structure is known, consider defining a concrete type. However, this is acceptable if the structure varies or is currently undefined.


378-391: Placeholder values in Assets section are acceptable but may need future attention.

The ExternalChangesDirty, ExternalChangesLastSeenUnixMs, ExternalChangesDirtySinceUnixMs, ExternalChangesLastClearedUnixMs, and Refresh fields are currently hardcoded to false/null. Similarly, Transport fields (lines 408-412) and some Tests fields are placeholders.

This is fine for the current pre-release work. Consider adding TODO comments or tracking these for future implementation if external change detection and transport status are needed features.


418-429: Thread-safe snapshot retrieval with defensive cloning.

The GetSnapshot method correctly:

  • Acquires a lock before accessing cached state
  • Rebuilds the snapshot if _cached is null (defensive fallback)
  • Returns a deep clone to prevent external mutation of the cached object

This is a sound implementation for a cache that may be accessed from multiple contexts.

MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs (3)

10-38: LGTM! EnableModule follows best practices.

The method correctly handles null checks, records undo, provides a default case for unknown modules, and marks the object dirty. The underscore stripping at line 22 provides flexible input handling for module names.


40-58: Previous review concern addressed - default case now present.

The default case at line 54 now handles unknown actions appropriately, addressing the concern from the previous review. The method correctly omits Undo/SetDirty since these operations control runtime particle preview behavior rather than modifying serialized state.


87-99: LGTM! ClearBursts follows best practices.

The method correctly handles the particle system lookup, records undo, clears the bursts, marks the object dirty, and provides clear feedback about the operation.

Server/src/services/tools/run_tests.py (2)

1-16: LGTM!

The updated docstring and imports appropriately reflect the transition to an async job-based workflow.


43-87: LGTM!

The new data models are well-structured and provide comprehensive typing for the async job lifecycle, supporting detailed progress tracking and flexible result inclusion.

MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs (11)

15-21: LGTM!

The target resolution and error handling are appropriate and follow the established patterns in the codebase.


23-24: LGTM!

Proper undo recording for both transform and GameObject properties ensures changes can be reverted.


28-33: LGTM!

Name modification logic is straightforward and correct.


35-56: LGTM!

The parent modification logic includes proper validation for circularity and null handling, preventing hierarchy loops and supporting both parenting and unparenting operations.


58-63: LGTM!

SetActive modification is correctly implemented.


65-92: LGTM!

The tag modification logic includes dynamic tag creation, which provides good user experience by automatically creating missing tags rather than failing. Error handling for both tag creation and assignment is appropriate.


109-127: LGTM!

Transform modification logic correctly uses local transform properties and includes change detection to avoid unnecessary modifications.


129-142: LGTM!

Component removal logic uses early return on first error, ensuring all-or-nothing semantics for the removal operations.


144-166: LGTM!

Component addition logic supports both simple string type names and objects with properties, providing flexibility in the API. Error handling follows the same early-return pattern as component removal.


168-188: LGTM!

The component property setting logic intentionally accumulates errors rather than using early return, allowing partial success when updating multiple components. This design provides better UX compared to the all-or-nothing approach used for add/remove operations.


225-237: LGTM!

The success response logic appropriately distinguishes between no-op operations and actual modifications, providing clear feedback to the caller. Including serialized GameObject data in both cases allows verification of the final state.

Server/src/services/tools/refresh_unity.py (1)

29-34: LGTM! Parameter formatting improves readability.

The multiline formatting of type annotations makes the function signature more readable without changing functionality.

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

🧹 Nitpick comments (3)
Server/src/services/resources/editor_state.py (3)

123-125: Consider consolidating duplicate _in_pytest() helper.

This function duplicates the implementation in Server/src/services/tools/preflight.py (lines 10-13). Consider importing from a shared utility module to maintain a single source of truth.

♻️ Proposed refactor
+from services.tools.preflight import _in_pytest
+
-def _in_pytest() -> bool:
-    # Avoid instance-discovery side effects during the Python integration test suite.
-    return bool(os.environ.get("PYTEST_CURRENT_TEST"))

285-286: Consider logging exceptions in external changes integration.

The bare try-except-pass silently swallows all exceptions during external changes detection. While graceful degradation is appropriate here, logging the exception would aid debugging when external change tracking fails unexpectedly.

♻️ Proposed improvement
     except Exception:
-        pass
+        ctx.error("Failed to integrate external changes detection", exc_info=True)

256-269: Optional: Cache project root for performance.

The function calls get_project_info() on every get_editor_state() invocation to retrieve the project root. Since the project root is unlikely to change during an editor session, consider caching it per instance to reduce overhead if this resource is polled frequently.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9122e1e and 98a261a.

📒 Files selected for processing (1)
  • Server/src/services/resources/editor_state.py
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 316
File: TestProjects/UnityMCPTests/Assets/Tests/EditMode/Resources.meta:1-8
Timestamp: 2025-10-13T13:27:23.040Z
Learning: UnityMcpBridge is a legacy project kept for backwards compatibility; MCPForUnity is the only active Unity plugin project. GUID collisions between UnityMcpBridge and MCPForUnity are acceptable.
🧬 Code graph analysis (1)
Server/src/services/resources/editor_state.py (6)
Server/src/services/registry/resource_registry.py (1)
  • mcp_for_unity_resource (10-43)
Server/src/transport/legacy/unity_connection.py (2)
  • async_send_command_with_retry (838-870)
  • get_unity_connection_pool (652-665)
Server/src/services/tools/preflight.py (1)
  • _in_pytest (11-14)
Server/src/transport/unity_transport.py (2)
  • _current_transport (23-25)
  • send_with_unity_instance (79-114)
Server/src/transport/plugin_hub.py (2)
  • PluginHub (41-558)
  • get_sessions (220-234)
Server/src/services/state/external_changes_scanner.py (2)
  • set_project_root (51-54)
  • update_and_get (173-241)
🪛 Ruff (0.14.10)
Server/src/services/resources/editor_state.py

139-139: Do not catch blind exception: Exception

(BLE001)


156-156: Do not catch blind exception: Exception

(BLE001)


170-170: Do not catch blind exception: Exception

(BLE001)


180-180: Do not catch blind exception: Exception

(BLE001)


285-286: try-except-pass detected, consider logging the exception

(S110)


285-285: Do not catch blind exception: Exception

(BLE001)


298-298: Do not catch blind exception: Exception

(BLE001)

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

🧹 Nitpick comments (3)
MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs (1)

94-107: Optional: Remove redundant condition at line 102.

The check layerId != -1 at line 102 is redundant since lines 98-101 already return early if layerId == -1. The condition can be simplified to just if (targetGo.layer != layerId).

♻️ Simplified condition
                 int layerId = LayerMask.NameToLayer(layerName);
                 if (layerId == -1)
                 {
                     return new ErrorResponse($"Invalid layer specified: '{layerName}'. Use a valid layer name.");
                 }
-                if (layerId != -1 && targetGo.layer != layerId)
+                if (targetGo.layer != layerId)
                 {
                     targetGo.layer = layerId;
                     modified = true;
                 }
Server/tests/integration/test_run_tests_async.py (1)

6-34: Consider renaming test function for clarity.

The test function is named test_run_tests_async_forwards_params but now tests the run_tests function (the "_async" suffix is no longer part of the function name being tested). While the test logic is correct and properly validates parameter forwarding and the typed response objects, renaming to test_run_tests_forwards_params would improve clarity.

♻️ Suggested rename
-async def test_run_tests_async_forwards_params(monkeypatch):
+async def test_run_tests_forwards_params(monkeypatch):
Server/src/services/resources/editor_state.py (1)

254-284: Consider logging exceptions from external changes scanner.

The external changes scanner integration (lines 254-284) is wrapped in try/except: pass, which silently suppresses all errors. While it's appropriate that external enrichment failures shouldn't break the main response, the complete silence makes debugging difficult.

💡 Optional: Add debug logging
     except Exception:
-        pass
+        await ctx.debug("External changes scanner enrichment failed; continuing without external change data")
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98a261a and f270bee.

📒 Files selected for processing (7)
  • MCPForUnity/Editor/Services/EditorStateCache.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs
  • Server/src/services/resources/editor_state.py
  • Server/src/services/tools/refresh_unity.py
  • Server/src/services/tools/run_tests.py
  • Server/tests/integration/test_run_tests_async.py
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 316
File: TestProjects/UnityMCPTests/Assets/Tests/EditMode/Resources.meta:1-8
Timestamp: 2025-10-13T13:27:23.040Z
Learning: UnityMcpBridge is a legacy project kept for backwards compatibility; MCPForUnity is the only active Unity plugin project. GUID collisions between UnityMcpBridge and MCPForUnity are acceptable.
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:17.871Z
Learning: In the MCPForUnity project, the team prefers to rely on their testing process to catch UI initialization issues rather than adding defensive null checks for UI elements in editor windows.
📚 Learning: 2025-11-05T18:23:12.349Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 368
File: MCPForUnity/UnityMcpServer~/src/resources/menu_items.py:15-15
Timestamp: 2025-11-05T18:23:12.349Z
Learning: In Unity MCP, the `name` parameter in the `mcp_for_unity_resource` decorator is the external API name exposed to MCP clients (LLMs, AI agents). The command string passed to `async_send_command_with_retry` or `async_send_with_unity_instance` (e.g., "get_menu_items") is the internal command identifier that must match the C# side. These are decoupled, allowing external API naming to evolve independently of internal command routing.

Applied to files:

  • Server/src/services/tools/refresh_unity.py
📚 Learning: 2025-10-13T13:41:00.086Z
Learnt from: JohanHoltby
Repo: CoplayDev/unity-mcp PR: 309
File: MCPForUnity/Editor/Helpers/ServerInstaller.cs:478-508
Timestamp: 2025-10-13T13:41:00.086Z
Learning: In the MCPForUnityTools feature (MCPForUnity/Editor/Helpers/ServerInstaller.cs), the design intentionally forces users to have only one .py file per MCPForUnityTools folder to keep file tracking simple. Package-style tools (subdirectories with __init__.py) are not supported.

Applied to files:

  • Server/src/services/tools/refresh_unity.py
📚 Learning: 2025-09-03T16:00:55.839Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 0
File: :0-0
Timestamp: 2025-09-03T16:00:55.839Z
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:

  • MCPForUnity/Editor/Services/EditorStateCache.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs
📚 Learning: 2025-10-13T13:27:23.040Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 316
File: TestProjects/UnityMCPTests/Assets/Tests/EditMode/Resources.meta:1-8
Timestamp: 2025-10-13T13:27:23.040Z
Learning: UnityMcpBridge is a legacy project kept for backwards compatibility; MCPForUnity is the only active Unity plugin project. GUID collisions between UnityMcpBridge and MCPForUnity are acceptable.

Applied to files:

  • MCPForUnity/Editor/Services/EditorStateCache.cs
📚 Learning: 2025-12-29T15:23:11.613Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:11.613Z
Learning: In MCPForUnity, prefer relying on the established testing process to catch UI initialization issues instead of adding defensive null checks for UI elements in editor windows. This means during reviews, verify that tests cover UI initialization paths and that code avoids repetitive null-check boilerplate in Editor Windows. If a UI element can be null, ensure there is a well-tested fallback or that its initialization is guaranteed by design, rather than sprinkling null checks throughout editor code.

Applied to files:

  • MCPForUnity/Editor/Services/EditorStateCache.cs
  • MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs
  • MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs
🧬 Code graph analysis (4)
Server/src/services/tools/refresh_unity.py (2)
Server/src/services/resources/editor_state.py (2)
  • get_editor_state (219-304)
  • infer_single_instance_id (128-170)
Server/tests/integration/test_helpers.py (1)
  • model_dump (10-13)
Server/src/services/tools/run_tests.py (5)
Server/src/services/tools/preflight.py (1)
  • preflight (27-110)
Server/src/transport/legacy/unity_connection.py (1)
  • async_send_command_with_retry (838-870)
MCPForUnity/Editor/Services/TestJobManager.cs (1)
  • TestJobFailure (20-24)
Server/tests/integration/test_helpers.py (1)
  • error (46-47)
Server/src/transport/unity_transport.py (1)
  • send_with_unity_instance (79-114)
Server/tests/integration/test_run_tests_async.py (2)
Server/src/services/tools/run_tests.py (2)
  • run_tests (97-155)
  • get_test_job (165-191)
Server/tests/integration/test_helpers.py (1)
  • DummyContext (16-55)
MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs (1)
MCPForUnity/Editor/Tools/Vfx/ParticleCommon.cs (4)
  • ParticleSystem (8-12)
  • ParticleSystem (14-50)
  • ParticleSystem (52-85)
  • ParticleCommon (6-86)
🪛 Ruff (0.14.10)
Server/src/services/resources/editor_state.py

137-137: Do not catch blind exception: Exception

(BLE001)


154-154: Do not catch blind exception: Exception

(BLE001)


168-168: Do not catch blind exception: Exception

(BLE001)


178-178: Do not catch blind exception: Exception

(BLE001)


283-284: try-except-pass detected, consider logging the exception

(S110)


283-283: Do not catch blind exception: Exception

(BLE001)


296-296: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (22)
MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs (4)

10-38: LGTM! Module toggle implementation is solid.

The method correctly handles module enabling/disabling with proper null checks, undo support, and the default case for unknown modules. The underscore removal normalization (line 22) is a nice touch for input flexibility.


40-58: Default case properly handles unknown actions.

The Control method now includes a default case (line 54) that returns an appropriate error for unrecognized actions. The implementation correctly handles all common playback controls with proper parameter handling.


60-87: Integer overflow safely handled with Math.Clamp.

The AddBurst method now correctly prevents integer overflow (lines 71-72) by parsing to int, clamping to the valid short range, then casting. This addresses the previous concern about values exceeding short.MaxValue. The burst array manipulation and parameter handling are implemented correctly.


89-101: LGTM! Burst clearing is correctly implemented.

The method properly clears all bursts with undo support and dirty marking. Storing the count before clearing (line 96) allows for an informative success message.

MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs (3)

35-56: LGTM: Excellent defensive checks for parent modification.

The parent modification logic properly handles null/empty values for unparenting, and the loop detection using IsChildOf at lines 47-49 prevents hierarchy cycles. The use of SetParent with worldPositionStays=true is appropriate for preserving world-space positioning.


190-223: LGTM: Improved error aggregation with exception logging.

The error aggregation logic now properly logs exceptions during reflection-based error extraction (lines 213-216), ensuring that failures in the aggregation process are visible while allowing the method to continue processing remaining errors. This provides good visibility into any issues with the error handling logic itself.


15-238: LGTM: Well-structured GameObject modification handler.

The overall implementation demonstrates solid design:

  • Comprehensive parameter handling with appropriate validation
  • Proper Undo recording before modifications
  • Defensive checks (null handling, hierarchy loop detection, tag/layer validation)
  • Contextual error messages
  • Distinction between early-return errors (add/remove components) and collected errors (property setting) allows for appropriate failure modes
  • Clean separation of concerns using helper classes
Server/src/services/tools/run_tests.py (4)

1-16: LGTM! Clear documentation and well-organized imports.

The module docstring accurately describes the new job-based workflow, and all imports are appropriate for the async test runner implementation.


43-88: LGTM! Well-structured data models for the job-based workflow.

The Pydantic models properly define the async test job lifecycle with appropriate typing and optional fields. The snake_case naming follows Python conventions and will be correctly serialized/deserialized.


97-155: LGTM! Job-start workflow correctly implemented.

The function properly:

  • Validates preconditions via preflight (no tests running, no compilation, assets refreshed if dirty)
  • Normalizes filter parameters with the _coerce_string_list helper
  • Sends the command asynchronously via unified transport
  • Returns typed RunTestsStartResponse objects (correctly addresses previous type annotation concerns)

The camelCase parameter keys (testNames, groupNames, etc.) appropriately match Unity/C# naming conventions.


158-191: LGTM! Polling endpoint properly implemented.

The function correctly:

  • Omits preflight checks (appropriate for read-only polling)
  • Forwards job_id and detail flags to Unity
  • Returns typed GetTestJobResponse objects (correctly addresses previous type annotation concerns)

The consistent response-handling pattern across both endpoints improves maintainability.

Server/tests/integration/test_run_tests_async.py (1)

36-56: LGTM! Test correctly validates the polling endpoint.

The test properly verifies that get_test_job forwards the job_id parameter and returns typed response objects with the expected structure. The attribute-based assertions correctly match the new API surface.

MCPForUnity/Editor/Services/EditorStateCache.cs (2)

36-230: LGTM! Well-structured typed snapshot model.

The nested class hierarchy provides a clear, type-safe representation of the editor state schema. The JsonProperty attributes correctly map C# PascalCase properties to snake_case JSON keys, ensuring compatibility with the Python side.

The inclusion of ExternalChangesDirtySinceUnixMs and ExternalChangesLastClearedUnixMs fields (lines 167-171) addresses the schema mismatch flagged in previous reviews.


334-415: LGTM! Clean transition to typed snapshot construction.

The BuildSnapshot method now constructs a strongly-typed EditorStateSnapshot graph, which improves type safety and makes the serialization contract explicit. The initialization logic correctly populates all fields, including the newly added ExternalChangesDirtySinceUnixMs and ExternalChangesLastClearedUnixMs (lines 383-384).

Note: A previous review flagged that Activity.SinceUnixMs (line 366) always equals the current observation time rather than tracking when the activity phase started. This is marked as addressed in past comments, so no further action is needed here unless the team decides to implement phase-transition tracking.

Server/src/services/resources/editor_state.py (5)

16-117: LGTM! Comprehensive and well-structured Pydantic models.

The model hierarchy (EditorStateUnity, EditorStateEditor, EditorStateActivity, etc.) provides clear schema definitions that align with the C# counterparts in EditorStateCache.cs. The use of optional fields (| None = None) and proper type hints ensures flexibility and type safety.

The counts: Any field (line 75) in EditorStateLastRun is appropriate for handling variable test result structures.


119-126: LGTM! Clean utility helpers.

_now_unix_ms() provides consistent timestamp generation, and _in_pytest() prevents unintended side effects during test execution. Both are well-implemented and appropriately scoped.


128-170: LGTM! Public API correctly addresses past review concerns.

The function is now public (line 128, no underscore prefix), which properly addresses previous feedback about exposing this functionality. The implementation handles both transport types with appropriate fallback behavior.

The broad except Exception handlers (lines 137, 154, 168) are flagged by static analysis but are appropriate here—this is a best-effort inference helper that should never crash the caller. Each transport path is isolated to prevent one failure mode from affecting the other.


173-211: LGTM! Clear and well-documented enrichment logic.

The function correctly computes staleness and readiness advice based on editor state. The 2-second staleness threshold (line 183) is conservative and appropriate for detecting unfocused editors, as noted in the comment.

The blocking reasons logic (lines 190-200) comprehensively covers compilation, domain reload, test execution, asset refresh, and staleness—providing useful guidance for retry behavior.


288-302: LGTM! Robust validation with clear error reporting.

The validation logic (lines 288-302) properly handles both Pydantic v1 (parse_obj) and v2 (model_validate) APIs, and returns a detailed error response when validation fails. This ensures schema mismatches are caught and reported clearly to callers.

Server/src/services/tools/refresh_unity.py (3)

16-16: LGTM! Clean migration to the new editor_state module.

The import (line 16) and parameter formatting (lines 29-34) correctly reflect the transition from editor_state_v2 to the unified editor_state module. The multi-line parameter annotations improve readability.

Also applies to: 29-34


74-83: LGTM! Robust readiness polling implementation.

The polling logic correctly calls the new editor_state.get_editor_state(ctx) API and defensively extracts the advice.ready_for_tools flag through multiple levels of dictionary access. The model_dump() fallback to direct dict access handles both Pydantic versions gracefully.


87-91: LGTM! Properly uses public API.

Line 87 correctly calls editor_state.infer_single_instance_id(ctx), which is now a public function (as confirmed in editor_state.py line 128). This addresses previous review feedback about accessing private functions.

The broad exception handler (lines 90-91) is appropriate here—external changes cleanup is a best-effort operation that should never break the main flow.

msanatan and others added 2 commits January 7, 2026 18:18
#8)

* Add local test harness for fast developer iteration

Scripts for running the NL/T/GO test suites locally against a GUI Unity
Editor, complementing the CI workflows in .github/workflows/.

Benefits:
- 10-100x faster than CI (no Docker startup)
- Real-time Unity console debugging
- Single test execution for rapid iteration
- Auto-detects HTTP vs stdio transport

Usage:
  ./scripts/local-test/setup.sh           # One-time setup
  ./scripts/local-test/quick-test.sh NL-0 # Run single test
  ./scripts/local-test/run-nl-suite-local.sh  # Full suite

See scripts/local-test/README.md for details.

Also updated .gitignore to:
- Allow scripts/local-test/ to be tracked
- Ignore generated artifacts (reports/*.xml, .claude/local/, .unity-mcp/)

* Fix issue CoplayDev#525: Save dirty scenes for all test modes

Move SaveDirtyScenesIfNeeded() call outside the PlayMode conditional
so EditMode tests don't get blocked by Unity's "Save Scene" modal dialog.

This prevents MCP from timing out when running EditMode tests with unsaved
scene changes.

* fix: add missing FAST_FAIL_TIMEOUT constant in PluginHub

The FAST_FAIL_TIMEOUT class attribute was referenced on line 149 but never
defined, causing AttributeError on every ping attempt. This error was silently
caught by the broad 'except Exception' handler, causing all fast-fail commands
(read_console, get_editor_state, ping) to fail after 6 seconds of retries with
'ping not answered' error.

Added FAST_FAIL_TIMEOUT = 10 to define a 10-second timeout for fast-fail
commands, matching the intent of the existing fast-fail infrastructure.

* feat(ScriptableObject): enhance dry-run validation for AnimationCurve and Quaternion

Dry-run validation now validates value formats, not just property existence:

- AnimationCurve: Validates structure ({keys:[...]} or direct array), checks
  each keyframe is an object, validates numeric fields (time, value, inSlope,
  outSlope, inWeight, outWeight) and integer fields (weightedMode)
- Quaternion: Validates array length (3 for Euler, 4 for raw) or object
  structure ({x,y,z,w} or {euler:[x,y,z]}), ensures all components are numeric

Refactored shared validation helpers into appropriate locations:
- ParamCoercion: IsNumericToken, ValidateNumericField, ValidateIntegerField
- VectorParsing: ValidateAnimationCurveFormat, ValidateQuaternionFormat

Added comprehensive XML documentation clarifying keyframe field defaults
(all default to 0 except as noted).

Added 5 new dry-run validation tests covering valid and invalid formats
for both AnimationCurve and Quaternion properties.

* test: fix integration tests after merge

- test_refresh_unity_retry_recovery: Mock now handles both refresh_unity and
  get_editor_state commands (refresh_unity internally calls get_editor_state
  when wait_for_ready=True)
- test_run_tests_async_forwards_params: Mock response now includes required
  'mode' field for RunTestsStartResponse Pydantic validation
- test_get_test_job_forwards_job_id: Updated to handle GetTestJobResponse as
  Pydantic model instead of dict (use model_dump() for assertions)

* Update warning message to apply to all test modes

Follow-up to PR CoplayDev#527: Since SaveDirtyScenesIfNeeded() now runs for all test modes, update the warning message to say 'tests' instead of 'PlayMode tests'.

* feat(run_tests): add wait_timeout to get_test_job to avoid client loop detection

When polling for test completion, MCP clients like Cursor can detect the
repeated get_test_job calls as 'looping' and terminate the agent.

Added wait_timeout parameter that makes the server wait internally for tests
to complete (polling Unity every 2s) before returning. This dramatically
reduces client-side tool calls from 10-20 down to 1-2, avoiding loop detection.

Usage: get_test_job(job_id='xxx', wait_timeout=30)
- Returns immediately if tests complete within timeout
- Returns current status if timeout expires (client can call again)
- Recommended: 30-60 seconds

* fix: use Pydantic attribute access in test_run_tests_async for merge compatibility

* revert: remove local test harness - will be submitted in separate PR

---------

Co-authored-by: Scott Jennings <[email protected]>
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

🧹 Nitpick comments (1)
Server/src/services/tools/run_tests.py (1)

195-221: Server-side polling implementation is correct.

The wait_timeout logic properly polls Unity at 2-second intervals, respects the deadline, and returns immediately on terminal states ("succeeded", "failed", "cancelled"). The use of asyncio.get_event_loop().time() for deadline calculations is acceptable.

💡 Optional: Make poll interval configurable

The poll_interval = 2.0 is hardcoded (line 197). If future use cases require different polling frequencies, consider making it a function parameter with a sensible default.

     wait_timeout: Annotated[int | None,
                             "If set, wait up to this many seconds for tests to complete before returning. "
                             "Reduces polling frequency and avoids client-side loop detection. "
                             "Recommended: 30-60 seconds. Returns immediately if tests complete sooner."] = None,
+    poll_interval: Annotated[float,
+                             "Polling interval in seconds when using wait_timeout"] = 2.0,
 ) -> GetTestJobResponse | MCPResponse:
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f270bee and ce1fbc6.

📒 Files selected for processing (16)
  • MCPForUnity/Editor/Dependencies/DependencyManager.cs.meta
  • MCPForUnity/Editor/Dependencies/Models/DependencyCheckResult.cs.meta
  • MCPForUnity/Editor/Dependencies/Models/DependencyStatus.cs.meta
  • MCPForUnity/Editor/Dependencies/PlatformDetectors/IPlatformDetector.cs.meta
  • MCPForUnity/Editor/Dependencies/PlatformDetectors/LinuxPlatformDetector.cs.meta
  • MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs.meta
  • MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs.meta
  • MCPForUnity/Editor/Helpers/ParamCoercion.cs
  • MCPForUnity/Editor/Helpers/VectorParsing.cs
  • MCPForUnity/Editor/Services/TestRunnerService.cs
  • MCPForUnity/Editor/Setup/SetupWindowService.cs.meta
  • MCPForUnity/Editor/Tools/ManageScriptableObject.cs
  • Server/src/services/tools/run_tests.py
  • Server/tests/integration/test_refresh_unity_retry_recovery.py
  • Server/tests/integration/test_run_tests_async.py
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptableObjectStressTests.cs
✅ Files skipped from review due to trivial changes (6)
  • MCPForUnity/Editor/Dependencies/Models/DependencyStatus.cs.meta
  • MCPForUnity/Editor/Dependencies/PlatformDetectors/LinuxPlatformDetector.cs.meta
  • MCPForUnity/Editor/Dependencies/DependencyManager.cs.meta
  • MCPForUnity/Editor/Setup/SetupWindowService.cs.meta
  • MCPForUnity/Editor/Dependencies/Models/DependencyCheckResult.cs.meta
  • MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs.meta
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 316
File: TestProjects/UnityMCPTests/Assets/Tests/EditMode/Resources.meta:1-8
Timestamp: 2025-10-13T13:27:23.040Z
Learning: UnityMcpBridge is a legacy project kept for backwards compatibility; MCPForUnity is the only active Unity plugin project. GUID collisions between UnityMcpBridge and MCPForUnity are acceptable.
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:17.871Z
Learning: In the MCPForUnity project, the team prefers to rely on their testing process to catch UI initialization issues rather than adding defensive null checks for UI elements in editor windows.
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 0
File: :0-0
Timestamp: 2025-09-03T16:00:55.839Z
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-10-13T13:27:23.040Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 316
File: TestProjects/UnityMCPTests/Assets/Tests/EditMode/Resources.meta:1-8
Timestamp: 2025-10-13T13:27:23.040Z
Learning: UnityMcpBridge is a legacy project kept for backwards compatibility; MCPForUnity is the only active Unity plugin project. GUID collisions between UnityMcpBridge and MCPForUnity are acceptable.

Applied to files:

  • MCPForUnity/Editor/Dependencies/PlatformDetectors/IPlatformDetector.cs.meta
  • MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs.meta
📚 Learning: 2025-09-04T01:01:11.927Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 260
File: UnityMcpBridge/UnityMcpServer~/src/server_version.txt:1-1
Timestamp: 2025-09-04T01:01:11.927Z
Learning: The UnityMcpBridge project is not maintaining changelogs yet, so don't suggest adding changelog entries for version bumps.

Applied to files:

  • MCPForUnity/Editor/Dependencies/PlatformDetectors/IPlatformDetector.cs.meta
📚 Learning: 2025-12-29T15:23:11.613Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:11.613Z
Learning: In MCPForUnity, prefer relying on the established testing process to catch UI initialization issues instead of adding defensive null checks for UI elements in editor windows. This means during reviews, verify that tests cover UI initialization paths and that code avoids repetitive null-check boilerplate in Editor Windows. If a UI element can be null, ensure there is a well-tested fallback or that its initialization is guaranteed by design, rather than sprinkling null checks throughout editor code.

Applied to files:

  • MCPForUnity/Editor/Helpers/ParamCoercion.cs
  • MCPForUnity/Editor/Tools/ManageScriptableObject.cs
  • MCPForUnity/Editor/Services/TestRunnerService.cs
  • MCPForUnity/Editor/Helpers/VectorParsing.cs
🧬 Code graph analysis (6)
MCPForUnity/Editor/Tools/ManageScriptableObject.cs (1)
MCPForUnity/Editor/Helpers/VectorParsing.cs (6)
  • AnimationCurve (415-463)
  • AnimationCurve (471-474)
  • VectorParsing (12-729)
  • ValidateAnimationCurveFormat (490-553)
  • Quaternion (152-213)
  • ValidateQuaternionFormat (571-660)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptableObjectStressTests.cs (3)
MCPForUnity/Editor/Tools/ManageScriptableObject.cs (1)
  • HandleCommand (40-75)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/TestUtilities.cs (1)
  • JObject (21-25)
MCPForUnity/Editor/Helpers/McpLog.cs (1)
  • Debug (31-35)
MCPForUnity/Editor/Services/TestRunnerService.cs (1)
MCPForUnity/Editor/Helpers/McpLog.cs (2)
  • McpLog (7-52)
  • Warn (43-46)
Server/src/services/tools/run_tests.py (2)
Server/src/services/tools/preflight.py (1)
  • preflight (27-110)
Server/src/transport/unity_transport.py (1)
  • send_with_unity_instance (79-114)
MCPForUnity/Editor/Helpers/VectorParsing.cs (1)
MCPForUnity/Editor/Helpers/ParamCoercion.cs (4)
  • ParamCoercion (11-259)
  • ValidateNumericField (178-192)
  • ValidateIntegerField (202-216)
  • IsNumericToken (165-168)
Server/tests/integration/test_run_tests_async.py (2)
Server/src/services/tools/run_tests.py (2)
  • run_tests (98-156)
  • get_test_job (166-229)
Server/tests/integration/test_helpers.py (1)
  • DummyContext (16-55)
🪛 Ruff (0.14.10)
Server/tests/integration/test_refresh_unity_retry_recovery.py

30-30: Avoid specifying long messages outside the exception class

(TRY003)

Server/tests/integration/test_run_tests_async.py

12-12: Unused function argument: send_fn

(ARG001)


12-12: Unused function argument: unity_instance

(ARG001)


12-12: Unused function argument: kwargs

(ARG001)


42-42: Unused function argument: send_fn

(ARG001)


42-42: Unused function argument: unity_instance

(ARG001)


42-42: Unused function argument: kwargs

(ARG001)

🔇 Additional comments (23)
MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs.meta (1)

2-2: LGTM! GUID regeneration addresses the reported conflict.

The GUID change resolves Issue #524 as stated in the PR objectives. The Unity-generated GUID replaces the previous value, ensuring unique asset identification across the project.

MCPForUnity/Editor/Dependencies/PlatformDetectors/IPlatformDetector.cs.meta (1)

2-2: GUID regeneration is correct and safe for this interface.

The GUID change correctly addresses the conflict mentioned in issue #524. Since IPlatformDetector is a pure C# interface with no serialized asset references (only used in code like DependencyManager.cs), changing the GUID poses no risk of breaking asset references. No further verification needed.

Server/tests/integration/test_refresh_unity_retry_recovery.py (1)

25-30: LGTM! Clean refactoring of the mock strategy.

The updated mock correctly simulates the retry-disconnect scenario and handles both refresh_unity and get_editor_state commands with appropriate responses. The branching logic is clear and the test validation at lines 35-41 confirms the expected behavior.

The static analysis hint about the error message on line 30 is a nitpick—in test code, descriptive error messages are valuable for debugging when unexpected commands are encountered.

MCPForUnity/Editor/Services/TestRunnerService.cs (2)

109-111: LGTM! Fixes Issue #525.

Calling SaveDirtyScenesIfNeeded() for all test modes (not just PlayMode) correctly prevents modal save dialogs from blocking EditMode tests, as referenced in the comment.


333-333: LGTM! Consistent with broader scope.

The warning message correctly uses "tests" (generic) instead of "PlayMode tests" (specific), since the function now handles both EditMode and PlayMode.

Server/src/services/tools/run_tests.py (3)

44-88: LGTM! Well-structured data models.

The new Pydantic models for the async job lifecycle (RunTestsStartData, GetTestJobData, TestJobProgress, TestJobFailure) are cleanly defined with appropriate Optional fields and proper inheritance from MCPResponse.


152-156: LGTM! Type safety issue resolved.

The response handling correctly returns typed RunTestsStartResponse or MCPResponse objects instead of raw dicts, addressing the type annotation inconsistency flagged in previous reviews.


224-229: LGTM! Consistent typed response handling.

The immediate return path (no wait_timeout) correctly returns typed GetTestJobResponse or MCPResponse, consistent with the run_tests implementation.

Server/tests/integration/test_run_tests_async.py (2)

7-33: LGTM! Test correctly updated for new module structure.

The test properly reflects the migration from services.tools.test_jobs to services.tools.run_tests, with correct command type ("run_tests"), updated monkeypatch target, and proper attribute-based access to the typed response object.


37-56: LGTM! Test correctly updated for new module structure.

The test properly reflects the migration to services.tools.run_tests with correct monkeypatch target and attribute-based response access.

MCPForUnity/Editor/Tools/ManageScriptableObject.cs (3)

391-434: Enhanced dry-run validation for complex types looks good.

The validation logic correctly delegates to VectorParsing.ValidateAnimationCurveFormat and ValidateQuaternionFormat for early format feedback during dry-run. The implementation properly handles:

  • Null/non-null value tokens
  • Success/failure result branching with appropriate messages
  • Property type and array info in the response

Consider extending dry-run validation to other complex types (Vector2/3/4, Color) in a follow-up if clients would benefit from early format feedback for those as well.


1094-1119: Comprehensive XML documentation for AnimationCurve parsing.

The updated documentation clearly describes:

  • Supported input formats (wrapped, direct array, null)
  • All keyframe fields with their defaults
  • The defaulting behavior for missing fields

This will be valuable for consumers of the API.


1204-1225: Clear documentation for Quaternion input formats.

The documentation comprehensively covers:

  • All supported input formats (Euler array, raw quaternion array, object formats)
  • Format detection rules based on array length
  • Null handling (identity quaternion)
MCPForUnity/Editor/Helpers/ParamCoercion.cs (1)

159-216: Well-designed validation helpers for dry-run support.

The new validation methods are clean and follow good patterns:

  • IsNumericToken is a simple, reusable predicate
  • ValidateNumericField and ValidateIntegerField correctly treat absent/null fields as valid (will use defaults)
  • Error messages include the actual token type for debugging

These integrate well with the VectorParsing validators.

MCPForUnity/Editor/Helpers/VectorParsing.cs (4)

272-279: Good API design for ParseColorOrDefault overloads.

Splitting into two overloads provides both convenience (parameterless returns Color.white) and flexibility (explicit default). This is a clean pattern.


394-412: Documentation correctly notes default value inconsistency.

The note at lines 405 and 410-411 appropriately calls out that value defaults to 1 here (for ManageVFX) but to 0 in ManageScriptableObject. This transparency is helpful.


475-553: Thorough AnimationCurve format validation.

The validation logic correctly:

  • Handles wrapped { keys: [...] } and direct array formats
  • Validates each keyframe has numeric fields using ParamCoercion.ValidateNumericField
  • Validates weightedMode as integer
  • Provides informative success/error messages

The success message correctly states defaults for the ManageScriptableObject context where this validator is used.


555-660: Comprehensive Quaternion format validation.

The validator covers all supported formats:

  • Euler angles array [x, y, z]
  • Raw quaternion array [x, y, z, w]
  • Object with euler array
  • Object with x, y, z, w fields

Each branch properly validates numeric types and provides clear error messages when validation fails.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptableObjectStressTests.cs (5)

734-793: Good test coverage for valid AnimationCurve dry-run validation.

The test correctly validates that:

  • A properly formatted AnimationCurve passes dry-run validation
  • The success message includes descriptive content about the curve format

The test pattern (create, dry-run, assert) is consistent with other tests in the file.


795-852: Effective negative test for AnimationCurve format validation.

The test verifies that invalid input (non-numeric time field) is caught during dry-run with a clear, actionable error message. The assertion checks for key terms in the error message without being overly brittle.


854-905: Good test for valid Quaternion Euler format.

The test confirms that a 3-element Euler angles array passes dry-run validation with an appropriate descriptive message.


907-958: Appropriate negative test for Quaternion array length.

The test verifies that an incorrect array length (2 elements) is caught with a clear error message explaining the valid options.


960-1011: Good test for non-numeric Quaternion value detection.

The test confirms that string values in numeric positions (e.g., "ninety" instead of 90) are caught during dry-run validation with a helpful error message.

@msanatan msanatan merged commit c0fd7d5 into CoplayDev:main Jan 7, 2026
1 check passed
@msanatan msanatan deleted the pre-release-tidying branch January 7, 2026 22:51
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.

GUID confict

2 participants