Skip to content

Conversation

@dsarno
Copy link
Owner

@dsarno dsarno commented Sep 3, 2025

Summary by CodeRabbit

  • New Features

    • Enhanced GameObject and asset operations: nested property paths, private-field support, aggregated error reporting, and AI-style property suggestions.
  • Improvements

    • Cleaner logging, more robust framed I/O with timeouts, safer client error handling, synchronous menu execution, Windows Store Python probe, improved shader/type resolution, safer asset preview generation, and sanitized asset path handling.
  • Bug Fixes

    • Better null/invalid input handling, prevention of unnamed-primitive leaks, improved anchor/regex matching and edit safety.
  • Documentation

    • Added additive Unity NL/T CI test design and README wording update.
  • Tests

    • Many new Editor test suites added; one obsolete test removed.
  • Chores

    • Version bumps and .gitignore updated.

dsarno and others added 23 commits September 2, 2025 09:36
…dits and New Script Creation (CoplayDev#248)

* Unity MCP: reliable auto-reload via Unity-side sentinel flip; remove Python writes

- Add MCP/Flip Reload Sentinel editor menu and flip package sentinel synchronously
- Trigger sentinel flip after Create/Update/ApplyTextEdits (sync) in ManageScript
- Instrument flip path with Debug.Log for traceability
- Remove Python reload_sentinel writes; tools now execute Unity menu instead
- Harden reload_sentinel path resolution to project/package
- ExecuteMenuItem runs synchronously for deterministic results
- Verified MCP edits trigger compile/reload without focus; no Python permission errors

* Getting double flips

* Fix double reload and ensure accurate batch edits; immediate structured reloads

* Remove MCP/Flip Reload Sentinel menu; rely on synchronous import/compile for reloads

* Route bridge/editor logs through McpLog and gate behind debug; create path now reloads synchronously

* chore: ignore backup artifacts; remove stray ManageScript.cs.backup files

* fix span logic

* fix: honor UNITY_MCP_STATUS_DIR for sentinel status file lookup (fallback to ~/.unity-mcp)

* test: add sentinel test honoring UNITY_MCP_STATUS_DIR; chore: ManageScript overlap check simplification and log consistency

* Harden environment path, remove extraneous flip menu test

* refactor: centralize import/compile via ManageScriptRefreshHelpers.ImportAndRequestCompile; replace duplicated sequences

* feat: add scheduledRefresh flag; standardize logs; gate info and DRY immediate import/compile

* chore: remove execute_menu_item sentinel flip from manage_script_edits; rely on import/compile

* chore: remove unused MCP reload sentinel mechanism

* fix: honor ignore_case for anchor_insert in text conversion path
…ous ImportAsset and RequestScriptCompilation\n- Debounced refresh calls ImportAsset instead of Refresh\n- Path sanitation for Assets/ and slashes\n- Atomic writes with Windows-safe fallbacks\n- Nudge Editor tick for debounce reliability
…managescript

Fix/windows auto reload managescript
- Replace Assembly.LoadFrom with already-loaded assembly search
- Prioritize Player assemblies over Editor assemblies using CompilationPipeline
- Support both short names and fully-qualified component names
- Add comprehensive caching and error handling with disambiguation
- Use Undo.AddComponent for proper editor integration
- Handle ReflectionTypeLoadException safely during type enumeration
- Add fallback to TypeCache for comprehensive type discovery in Editor

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude <noreply@anthropic.com>
Critical Bug Fixes:
- Fix operator precedence bug in ManageAsset.cs that could cause null reference exceptions
- Fix GameObject memory leak in primitive creation when name validation fails
- Add proper cleanup with DestroyImmediate when primitive creation fails

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

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude <noreply@anthropic.com>
…perations

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

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

Co-Authored-By: Claude <noreply@anthropic.com>
…ponentResolver test

The TryResolve_PrefersPlayerAssemblies test was failing in CI because it referenced
TicTacToe3D.cs which exists only locally and is not committed to the repository.

- Replace test reference from "TicTacToe3D" to "CustomComponent"
- CustomComponent exists in Assets/Scripts/TestAsmdef/ and is tracked in git
- CustomComponent is properly compiled into the TestAsmdef Player assembly
- Add explicit assertion to verify resolution from correct TestAsmdef assembly
- Test maintains same functionality: verifying ComponentResolver finds user scripts from Player assemblies

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

Co-Authored-By: Claude <noreply@anthropic.com>
fix: Replace missing TicTacToe3D with existing CustomComponent in Com…
- Add nl-unity-suite-full-additive.md: new additive test design that builds state progressively instead of requiring resets
- Update claude-nl-suite.yml workflow to use additive test suite
- Fix validation scoping bugs in ManageScript.cs:
  - Correct scoped validation scope calculation (was using newText.Length instead of originalLength)
  - Enable always-on final structural validation regardless of relaxed mode
- Unify regex_replace and anchor_insert to use same smart matching logic in manage_script_edits.py
- Additive tests demonstrate better real-world workflow testing and expose interaction bugs between operations
- Self-healing capability: tools can recover from and fix broken file states

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

Co-Authored-By: Claude <noreply@anthropic.com>
- Add ManageScriptValidationTests.cs: Unity-based validation tests for structural balance checking
- Add test_improved_anchor_matching.py: Python tests for enhanced anchor pattern matching
- Remove obsolete test_regex_delete_guard.py (functionality moved to C# validation)
- Tests validate the scoped validation fixes and anchor matching improvements

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

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

coderabbitai bot commented Sep 3, 2025

Note

Other AI code review bot(s) detected

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

Walkthrough

Adds an additive NL/T CI prompt and switches the Claude workflow to it; implements extensive Unity Editor and Python server changes (framed I/O, centralized McpLog, command routing, robust type/property resolution, improved anchor-matching heuristics, async sentinel flip), many new editor tests/asmdefs/meta files, and version/ignore hygiene updates.

Changes

Cohort / File(s) Summary of changes
CI Prompt & Workflow
\.claude/prompts/nl-unity-suite-full-additive.md, \.github/workflows/claude-nl-suite.yml
Adds an additive NL/T test-design prompt and updates the GitHub Actions Claude NL workflow to reference it.
Repo hygiene & docs
.gitignore, README.md
Adds backup ignore patterns and tweaks README sponsor wording.
Unity test project assets & asmdef
TestAsmdef & scripts
TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/TestAsmdef.asmdef, .../TestAsmdef.asmdef.meta, .../CustomComponent.cs, .../CustomComponent.cs.meta, .../Hello.cs, .../Hello.cs.meta, TestProjects/UnityMCPTests/Assets/Editor.meta
Adds TestAsmdef assembly and meta files, new CustomComponent MonoBehaviour, minor Hello.cs formatting edits, and folder Editor.meta.
Test assembly references
TestProjects/UnityMCPTests/Assets/Tests/EditMode/MCPForUnityTests.Editor.asmdef
Inserts "TestAsmdef" into editor test asmdef references.
New Editor tests (+ metas)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/*.cs, .../*.meta
Adds NUnit test suites and metas: AIPropertyMatchingTests, ComponentResolverTests, ManageGameObjectTests, ManageScriptValidationTests.
Editor internals & metadata
UnityMcpBridge/Editor/AssemblyInfo.cs, .../AssemblyInfo.cs.meta
Adds [assembly: InternalsVisibleTo("MCPForUnityTests.EditMode")] and meta file.
Server installer probe
UnityMcpBridge/Editor/Helpers/ServerInstaller.cs
Adds Windows Store Python (LOCALAPPDATA\Packages) probe for uv.exe with LINQ candidate ordering, wrapped in try/catch.
Bridge core: logging, framing, command routing
UnityMcpBridge/Editor/MCPForUnityBridge.cs
Centralizes logging via McpLog gated by IsDebugEnabled, adds framed I/O utilities with timeouts, JSON validation, command parsing, ExecuteCommand routing, and standardized error responses.
Execute menu item
UnityMcpBridge/Editor/Tools/ExecuteMenuItem.cs
Switches to synchronous EditorApplication.ExecuteMenuItem, updates tracing/logging and response payloads.
ManageGameObject / ComponentResolver / serializer refactor
UnityMcpBridge/Editor/Tools/ManageGameObject.cs
Adds shared InputSerializer, nested property setting, aggregated error collection, ComponentResolver (resolution/caching/GetAllComponentProperties/GetAIPropertySuggestions), AI-style suggestions, and routes serialization to Helpers.GameObjectSerializer.
ManageAsset updates
UnityMcpBridge/Editor/Tools/ManageAsset.cs
Replaces FindType with ComponentResolver.TryResolve, improves shader/material handling, component resolution warnings, and robust RenderTexture preview cleanup.
ManageScript & refresh helpers
UnityMcpBridge/Editor/Tools/ManageScript.cs
Adds null-param handling, SanitizeAssetsPath and ImportAndRequestCompile helpers, refines balance-check semantics and refresh scheduling (immediate vs debounced), and updates logging.
Editor window logging gate
UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs
Replaces two Debug.Log calls with gated McpLog.Info calls.
Python server metadata bumps
UnityMcpBridge/UnityMcpServer~/src/pyproject.toml, .../server_version.txt, UnityMcpBridge/package.json
Bumps versions to 3.3.0 (metadata-only) and updates server_version.txt.
Python manage_script: sentinel & refresh
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py
Triggers async sentinel flip after successful edits when options.force_sentinel_reload is set; adds non-blocking latest-status helper; changes legacy update path to debounced refresh.
Python edit heuristics & anchor matching
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py
Introduces _find_best_anchor_match and _find_best_closing_brace_match, unified anchor heuristics (prefer_last support), safer backref expansion, removes internal structural validator, and refines default refresh behavior.
Python compatibility shim
UnityMcpBridge/UnityMcpServer~/src/reload_sentinel.py
Adds deprecated no-op flip_reload_sentinel(*args, **kwargs) returning a deprecation message.
Python tests added
tests/test_improved_anchor_matching.py
Adds tests validating improved anchor-matching heuristics and applying edits using new matching.
Python tests removed
tests/test_regex_delete_guard.py
Deletes previous regex-delete-guard test module that validated structural delete protections.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Bridge as MCPForUnityBridge (C#)
  participant Router as Command Router
  participant Tools as Tools (ManageScript/ManageGameObject/ManageAsset/ExecuteMenuItem)
  note over Bridge: Framed I/O (ReadExactAsync) with timeouts\nJSON validation (IsValidJson)
  Client->>Bridge: framed command JSON
  Bridge->>Bridge: Validate & Deserialize
  Bridge->>Router: ExecuteCommand(type, params)
  Router->>Tools: Dispatch to specific handler
  Tools-->>Router: Response object (success/error + payload)
  Router-->>Bridge: Structured response
  Bridge-->>Client: framed JSON response
  alt Debug enabled
    Bridge->>Bridge: McpLog debug breadcrumbs
  end
Loading
sequenceDiagram
  autonumber
  participant Py as manage_script(_edits).py
  participant Unity as ManageScript (C#)
  participant AssetDB as AssetDatabase
  participant Editor as Unity Editor / Compiler
  rect rgba(220,235,255,0.2)
    Py->>Unity: apply_text_edits (debounced by default)
    Unity->>Unity: Scoped + final balance validation (C#)
    Unity->>AssetDB: Import asset (SanitizeAssetsPath)
    AssetDB-->>Unity: Import done
    Unity->>Editor: Optional compile (ImportAndRequestCompile)
    Unity-->>Py: Success response
  end
  alt force_sentinel_reload
    Py-->>Editor: Async "MCP/Flip Reload Sentinel" (no-retry)
    note over Editor: Non-blocking sentinel flip triggered
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

codex

Poem

I tap my paws on keys so bright,
Anchors hop and find the right light.
Edits land near class-end spots,
Sentinels flip—no blocking lots.
Types resolved, tests hum—v3.3.0 hops! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/brace-validation-improvements

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Status, Documentation and Community

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

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Summary

This PR implements comprehensive improvements to brace validation and script editing functionality across the Unity MCP Bridge system. The changes focus on enhancing the reliability of C# code parsing, validation, and modification operations within Unity projects.

The core improvements center around the ManageScript.cs tool, which now features enhanced null parameter validation, improved scoped balance checking with better tolerance for context outside editing regions, and refined refresh/compile behavior with both immediate and debounced modes. The validation logic has been strengthened with relaxed brace tolerance (-3 instead of -1) to handle legitimate edits within larger code contexts while maintaining final structural validation.

A new ComponentResolver system has been introduced in ManageGameObject.cs that provides robust component type resolution with assembly prioritization, caching, and AI-powered property suggestions using Levenshtein distance calculations. This system handles Unity's complex assembly loading patterns and provides helpful error messages with suggestions when property names don't match exactly.

The script editing infrastructure has been enhanced with improved anchor matching heuristics in manage_script_edits.py. New functions like _find_best_anchor_match() and _find_best_closing_brace_match() use scoring algorithms to intelligently select the best insertion points for code modifications, particularly for class-level changes where multiple closing braces exist.

Logging has been standardized throughout the codebase by replacing direct Debug.Log calls with McpLog helper methods, providing consistent formatting and better control over debug output. The system now distinguishes between benign network errors and actual problems requiring developer attention.

The testing infrastructure has been significantly expanded with comprehensive test suites for component resolution, property matching, script validation, and GameObject management. New test assemblies and components have been added to validate the improved functionality across various scenarios.

Version numbers have been coordinated across the system, bumping from 3.1.0/3.1.1 to 3.2.0/3.3.0 to reflect these substantial improvements. The changes also include architectural improvements like moving reload sentinel functionality from external Python scripts to Unity menu items, and switching from immediate to debounced refresh patterns for better editor performance.

Important Files Changed

Changed Files
Filename Score Overview
UnityMcpBridge/Editor/Tools/ManageScript.cs 4/5 Enhanced brace validation with improved scoped balance checking, null validation, and relaxed tolerance
UnityMcpBridge/Editor/Tools/ManageGameObject.cs 4/5 Added comprehensive ComponentResolver system with AI property suggestions and error collection
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py 4/5 Implemented improved anchor matching heuristics for better C# code insertion accuracy
tests/test_improved_anchor_matching.py 1/5 Non-functional tests referencing missing implementation with fragile module loading
tests/test_regex_delete_guard.py 2/5 Critical safety validation tests completely removed without clear replacement
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs 3/5 Comprehensive tests with potential reliability issues from performance timing assertions
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptValidationTests.cs 3/5 Script validation tests using reflection with incomplete error handling
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ComponentResolverTests.cs 4/5 Well-structured component resolution tests with minor brittleness in assembly assertions
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/AIPropertyMatchingTests.cs 4/5 Comprehensive AI property matching tests with good coverage of edge cases
UnityMcpBridge/Editor/Tools/ExecuteMenuItem.cs 4/5 Improved synchronous execution with better error handling and accurate feedback
UnityMcpBridge/Editor/Tools/ManageAsset.cs 4/5 Consolidated type resolution using ComponentResolver with improved error reporting
UnityMcpBridge/Editor/MCPForUnityBridge.cs 4/5 Standardized logging and enhanced network error handling with benign error detection
UnityMcpBridge/Editor/Helpers/ServerInstaller.cs 4/5 Added Windows Store Python UV detection with proper error handling
.claude/prompts/nl-unity-suite-full-additive.md 4/5 New additive test design with complex state management and potential debugging challenges
.github/workflows/claude-nl-suite.yml 4/5 Changed testing methodology from reset-based to additive approach
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py 4/5 Added force sentinel reload and changed default refresh to debounced
UnityMcpBridge/UnityMcpServer~/src/reload_sentinel.py 5/5 Deprecated external sentinel management in favor of Unity menu integration
TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs 4/5 Modified for testing with formatting inconsistencies and missing EOF newline
TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/CustomComponent.cs 5/5 Well-structured test component for validating script parsing and validation
UnityMcpBridge/Editor/AssemblyInfo.cs 5/5 Standard InternalsVisibleTo attribute for test assembly access
UnityMcpBridge/package.json 5/5 Version bump from 3.1.0 to 3.3.0 for release coordination
UnityMcpBridge/UnityMcpServer~/src/pyproject.toml 4/5 Version bump reflecting anchor matching and validation improvements
UnityMcpBridge/UnityMcpServer~/src/server_version.txt 5/5 Server version update for installation management
README.md 5/5 Minor marketing copy improvement for sponsor positioning
UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs 5/5 Standardized logging with McpLog.Info calls
All .meta files 5/5 Standard Unity asset metadata files with proper GUID assignment
.gitignore 5/5 Added backup artifact patterns to exclude temporary files

Confidence score: 3/5

  • This PR contains significant improvements but has several concerning issues that require careful review
  • Score reflects complex validation logic changes with potential edge cases and some non-functional tests
  • Pay close attention to test files with reflection usage, relaxed validation tolerance, and missing test implementations

Sequence Diagram

sequenceDiagram
    participant User
    participant MCP_Client as MCP Client<br/>(Claude/Cursor)
    participant Python_Server as Python MCP Server
    participant Unity_Bridge as Unity Bridge
    participant ManageScript as ManageScript Tool
    participant Validator as Script Validator
    participant FileSystem as File System

    User->>MCP_Client: "Apply text edits to script with better validation"
    MCP_Client->>Python_Server: apply_text_edits(uri, edits, precondition_sha256)
    
    Note over Python_Server: Normalize edits and validate structure
    Python_Server->>Python_Server: _unwrap_and_alias(edits)
    Python_Server->>Python_Server: Check for overlapping ranges
    
    Python_Server->>Unity_Bridge: send_command_with_retry("manage_script", params)
    Unity_Bridge->>ManageScript: HandleCommand(action: "apply_text_edits")
    
    Note over ManageScript: Enhanced validation with brace checking
    ManageScript->>FileSystem: Read current file contents
    FileSystem-->>ManageScript: Return file contents
    
    ManageScript->>ManageScript: ComputeSha256(contents)
    ManageScript->>ManageScript: Validate precondition SHA matches
    
    alt SHA mismatch
        ManageScript-->>Unity_Bridge: Error: "stale_file"
        Unity_Bridge-->>Python_Server: Return stale file error
        Python_Server-->>MCP_Client: {"success": false, "code": "stale_file"}
        MCP_Client-->>User: "File was modified, please refresh"
    else SHA matches
        ManageScript->>ManageScript: Apply header guards (using_guard)
        
        Note over ManageScript: New brace validation improvements
        ManageScript->>Validator: CheckBalancedDelimiters(working_text)
        Validator->>Validator: Parse strings, comments, delimiters
        Validator->>Validator: Track brace/paren/bracket balance
        
        alt Relaxed validation mode
            ManageScript->>Validator: CheckScopedBalance(text, start, end)
            Note over Validator: Tolerates outer context imbalance
            Validator-->>ManageScript: Return scoped balance result
        else Standard validation
            Validator-->>ManageScript: Return full balance result
        end
        
        alt Validation fails
            ManageScript-->>Unity_Bridge: Error: "unbalanced_braces"
            Unity_Bridge-->>Python_Server: Return validation error
            Python_Server-->>MCP_Client: {"success": false, "status": "unbalanced_braces"}
            MCP_Client-->>User: "Syntax error: unbalanced braces at line X"
        else Validation passes
            ManageScript->>FileSystem: Write changes atomically
            FileSystem-->>ManageScript: Confirm write successful
            
            ManageScript->>ManageScript: ComputeSha256(new_contents)
            ManageScript->>ManageScript: Schedule refresh/compile
            
            ManageScript-->>Unity_Bridge: Success with new SHA
            Unity_Bridge-->>Python_Server: {"success": true, "sha256": "new_hash"}
            Python_Server-->>MCP_Client: {"success": true, "editsApplied": count}
            MCP_Client-->>User: "Successfully applied edits with validation"
        end
    end
Loading

37 files reviewed, 23 comments

Edit Code Review Bot Settings | Greptile

Copy link

@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 (3)
UnityMcpBridge/Editor/Tools/ManageAsset.cs (2)

180-188: Fix: material creation can throw when “Standard” shader is unavailable (URP/HDRP).

Shader.Find("Standard") may return null; new Material(null) throws. Respect an optional properties.shader and add safe fallbacks.

-                else if (lowerAssetType == "material")
-                {
-                    Material mat = new Material(Shader.Find("Standard")); // Default shader
-                    // TODO: Apply properties from JObject (e.g., shader name, color, texture assignments)
-                    if (properties != null)
-                        ApplyMaterialProperties(mat, properties);
-                    AssetDatabase.CreateAsset(mat, fullPath);
-                    newAsset = mat;
-                }
+                else if (lowerAssetType == "material")
+                {
+                    // Prefer provided shader; fall back to common pipelines
+                    var requested = properties?["shader"]?.ToString();
+                    Shader shader =
+                        (!string.IsNullOrEmpty(requested) ? Shader.Find(requested) : null)
+                        ?? Shader.Find("Universal Render Pipeline/Lit")
+                        ?? Shader.Find("HDRP/Lit")
+                        ?? Shader.Find("Standard")
+                        ?? Shader.Find("Unlit/Color");
+                    if (shader == null)
+                        return Response.Error($"Could not find a suitable shader (requested: '{requested ?? "none"}').");
+
+                    var mat = new Material(shader);
+                    if (properties != null)
+                        ApplyMaterialProperties(mat, properties);
+                    AssetDatabase.CreateAsset(mat, fullPath);
+                    newAsset = mat;
+                }

1262-1276: Ensure RenderTexture/Texture2D are always released to avoid editor leaks.

If an exception occurs before releasing, rt is leaked. Use try/finally.

-                        RenderTexture rt = RenderTexture.GetTemporary(
-                            preview.width,
-                            preview.height
-                        );
-                        Graphics.Blit(preview, rt);
-                        RenderTexture previous = RenderTexture.active;
-                        RenderTexture.active = rt;
-                        Texture2D readablePreview = new Texture2D(preview.width, preview.height);
-                        readablePreview.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
-                        readablePreview.Apply();
-                        RenderTexture.active = previous;
-                        RenderTexture.ReleaseTemporary(rt);
-
-                        byte[] pngData = readablePreview.EncodeToPNG();
-                        previewBase64 = Convert.ToBase64String(pngData);
-                        previewWidth = readablePreview.width;
-                        previewHeight = readablePreview.height;
-                        UnityEngine.Object.DestroyImmediate(readablePreview); // Clean up temp texture
+                        RenderTexture rt = null;
+                        Texture2D readablePreview = null;
+                        RenderTexture previous = RenderTexture.active;
+                        try
+                        {
+                            rt = RenderTexture.GetTemporary(preview.width, preview.height);
+                            Graphics.Blit(preview, rt);
+                            RenderTexture.active = rt;
+                            readablePreview = new Texture2D(preview.width, preview.height);
+                            readablePreview.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
+                            readablePreview.Apply();
+                        }
+                        finally
+                        {
+                            RenderTexture.active = previous;
+                            if (rt != null) RenderTexture.ReleaseTemporary(rt);
+                        }
+
+                        var pngData = readablePreview.EncodeToPNG();
+                        previewBase64 = Convert.ToBase64String(pngData);
+                        previewWidth = readablePreview.width;
+                        previewHeight = readablePreview.height;
+                        UnityEngine.Object.DestroyImmediate(readablePreview);
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (1)

669-674: Fix closure over m in nested helpers (ruff B023).

Bind the match object via default args to avoid late binding pitfalls.

-                        def _expand_dollars(rep: str) -> str:
-                            return _re.sub(r"\$(\d+)", lambda g: m.group(int(g.group(1))) or "", rep)
+                        def _expand_dollars(rep: str, _m=m) -> str:
+                            return _re.sub(r"\$(\d+)", lambda g: _m.group(int(g.group(1))) or "", rep)
-                        def _expand_dollars(rep: str) -> str:
-                            return _re.sub(r"\$(\d+)", lambda g: m.group(int(g.group(1))) or "", rep)
+                        def _expand_dollars(rep: str, _m=m) -> str:
+                            return _re.sub(r"\$(\d+)", lambda g: _m.group(int(g.group(1))) or "", rep)

Also applies to: 815-818

🧹 Nitpick comments (66)
.gitignore (1)

40-42: LGTM: ignoring backup artifacts is a good safeguard.

This will prevent noisy editor/tooling backups from entering VCS.

You might also consider adding other common backup/temp patterns to future-proof:

  • *.bak
  • *.orig
  • *~
  • *.tmp
UnityMcpBridge/Editor/AssemblyInfo.cs.meta (1)

1-2: Meta file appears minimal; ensure Unity won’t regenerate importer fields.

Typical .cs.meta files include a MonoImporter block. If Unity regenerates this on import, it’ll cause churn. Recommend committing the full importer block.

Suggested content:

 fileFormatVersion: 2
 guid: be61633e00d934610ac1ff8192ffbe3d
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/AIPropertyMatchingTests.cs.meta (1)

1-2: Meta file likely missing MonoImporter block; avoid future re-gen diffs.

Same note as the other .cs.meta: consider checking this into source to prevent Unity from rewriting it locally.

Suggested content:

 fileFormatVersion: 2
 guid: 9e4468da1a15349029e52570b84ec4b0
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ComponentResolverTests.cs.meta (1)

1-2: Add trailing newline to the .meta file
Meta file is valid; adding a trailing newline prevents noisy diffs. No duplicate GUIDs found across .meta files.

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

1-2: OK; stable GUID committed.

No issues spotted. Optional: add trailing newline for consistency.

README.md (1)

3-3: Fix heading level to satisfy markdownlint MD001.

Change H4 to H2 so levels don’t skip from H1 to H4.

-#### Proudly sponsored and maintained by [Coplay](https://www.coplay.dev/?ref=unity-mcp) -- the best AI assistant for Unity. [Read the backstory here.](https://www.coplay.dev/blog/coplay-and-open-source-unity-mcp-join-forces)
+## Proudly sponsored and maintained by [Coplay](https://www.coplay.dev/?ref=unity-mcp) — the best AI assistant for Unity. [Read the backstory here.](https://www.coplay.dev/blog/coplay-and-open-source-unity-mcp-join-forces)
UnityMcpBridge/UnityMcpServer~/src/pyproject.toml (1)

7-7: Consider adding upper bounds for reproducible installs.
Open-ended ranges can pull breaking majors; cap to known-good series.

Apply:

-dependencies = ["httpx>=0.27.2", "mcp[cli]>=1.4.1"]
+dependencies = ["httpx>=0.27.2,<0.29", "mcp[cli]>=1.4.1,<2.0"]
TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs.meta (1)

2-2: Meta missing MonoImporter block — Unity may rewrite on import.
Keep default fields to avoid churn and ensure deterministic meta.

Suggested content:

 fileFormatVersion: 2
 guid: bebdf68a6876b425494ee770d20f70ef
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

Then reimport in a clean clone to verify Unity doesn’t rewrite.

TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs (2)

2-2: Remove unused using.
System.Collections isn’t used.

-using System.Collections;

6-15: Trim extra blank lines.
Keeps sample script tidy.

 public class Hello : MonoBehaviour
 {
-
     // Use this for initialization
     void Start()
     {
         Debug.Log("Hello World");
     }
-
-
-
 }
UnityMcpBridge/UnityMcpServer~/src/reload_sentinel.py (2)

1-5: Docstring shim looks good; keep it side‑effect free.

No functional concerns. Consider ASCII-only text in user‑visible strings to avoid odd console encodings in some environments. See next comment for a concrete tweak.


7-8: Silence Ruff ARG001 and prefer ASCII arrow in message.

Current signature trips ARG001 for unused args. Also swap the Unicode arrow for ASCII to be log/console safe.

-def flip_reload_sentinel(*args, **kwargs) -> str:
-    return "reload_sentinel.py is deprecated; use execute_menu_item → 'MCP/Flip Reload Sentinel'"
+def flip_reload_sentinel(*_args: object, **_kwargs: object) -> str:
+    return "reload_sentinel.py is deprecated; use execute_menu_item -> 'MCP/Flip Reload Sentinel'"
UnityMcpBridge/Editor/Helpers/ServerInstaller.cs (1)

573-597: Nice Windows Store uv.exe probe; refine ordering and enumeration for robustness.

  • Directory.GetDirectories eagerly allocates; prefer EnumerateDirectories.
  • Ordering by path string is a weak proxy for version recency. Sort by last write time (or parse version suffix) for better “latest” selection.
  • Keep ValidateUvBinary as the decisive check.
-                try
-                {
-                    string pkgsRoot = Path.Combine(localAppData, "Packages");
-                    if (Directory.Exists(pkgsRoot))
-                    {
-                        var pythonPkgs = Directory.GetDirectories(pkgsRoot, "PythonSoftwareFoundation.Python.*", SearchOption.TopDirectoryOnly)
-                                                 .OrderByDescending(p => p, StringComparer.OrdinalIgnoreCase);
-                        foreach (var pkg in pythonPkgs)
-                        {
-                            string localCache = Path.Combine(pkg, "LocalCache", "local-packages");
-                            if (!Directory.Exists(localCache)) continue;
-                            var pyRoots = Directory.GetDirectories(localCache, "Python*", SearchOption.TopDirectoryOnly)
-                                                   .OrderByDescending(d => d, StringComparer.OrdinalIgnoreCase);
-                            foreach (var pyRoot in pyRoots)
-                            {
-                                string uvExe = Path.Combine(pyRoot, "Scripts", "uv.exe");
-                                if (File.Exists(uvExe) && ValidateUvBinary(uvExe)) return uvExe;
-                            }
-                        }
-                    }
-                }
+                try
+                {
+                    string pkgsRoot = Path.Combine(localAppData, "Packages");
+                    if (Directory.Exists(pkgsRoot))
+                    {
+                        var pythonPkgs = Directory.EnumerateDirectories(pkgsRoot, "PythonSoftwareFoundation.Python.*", SearchOption.TopDirectoryOnly)
+                                                  .OrderByDescending(p => {
+                                                      try { return Directory.GetLastWriteTimeUtc(p); } catch { return DateTime.MinValue; }
+                                                  });
+                        foreach (var pkg in pythonPkgs)
+                        {
+                            string localCache = Path.Combine(pkg, "LocalCache", "local-packages");
+                            if (!Directory.Exists(localCache)) continue;
+                            var pyRoots = Directory.EnumerateDirectories(localCache, "Python*", SearchOption.TopDirectoryOnly)
+                                                   .OrderByDescending(d => {
+                                                       try { return Directory.GetLastWriteTimeUtc(d); } catch { return DateTime.MinValue; }
+                                                   });
+                            foreach (var pyRoot in pyRoots)
+                            {
+                                string uvExe = Path.Combine(pyRoot, "Scripts", "uv.exe");
+                                if (File.Exists(uvExe) && ValidateUvBinary(uvExe)) return uvExe;
+                            }
+                        }
+                    }
+                }
                 catch { }
.claude/prompts/nl-unity-suite-full-additive.md (2)

5-7: Align the printed AllowedTools with the workflow.

The workflow allows Bash(scripts/nlt-revert.sh:*). Include it in the one‑time banner to avoid confusion.

-AllowedTools: Write,mcp__unity__manage_editor,mcp__unity__list_resources,mcp__unity__read_resource,mcp__unity__apply_text_edits,mcp__unity__script_apply_edits,mcp__unity__validate_script,mcp__unity__find_in_file,mcp__unity__read_console,mcp__unity__get_sha
+AllowedTools: Write,Bash(scripts/nlt-revert.sh:*),mcp__unity__manage_editor,mcp__unity__list_resources,mcp__unity__read_resource,mcp__unity__apply_text_edits,mcp__unity__script_apply_edits,mcp__unity__validate_script,mcp__unity__find_in_file,mcp__unity__read_console,mcp__unity__get_sha

90-95: Add a language to this fenced block to satisfy markdownlint (MD040).

It’s comment text; “text” works.

-  ```
+  ```text
   // Tail test A
   // Tail test B  
   // Tail test C

</blockquote></details>
<details>
<summary>UnityMcpBridge/Editor/Windows/MCPForUnityEditorWindow.cs (5)</summary><blockquote>

`1819-1833`: **Harden CLI arg construction to avoid quoting/injection and path edge cases.**

Building a single string with interpolated paths risks quoting bugs (paths with spaces/quotes) and accidental injection. Prefer tokenized args in ExecPath or sanitize inputs before composing.

Apply minimal sanitization if adding a tokenized ExecPath overload isn’t feasible now:

```diff
-            string args = $"mcp add UnityMCP -- \"{uvPath}\" run --directory \"{srcDir}\" server.py";
+            // Basic guard against problematic characters in paths
+            if (uvPath?.IndexOfAny(new[] {'\"', '\n', '\r'}) >= 0 || srcDir?.IndexOfAny(new[] {'\"', '\n', '\r'}) >= 0)
+            {
+                UnityEngine.Debug.LogError("MCP for Unity: Invalid characters in paths for uv or server directory.");
+                return;
+            }
+            string args = $"mcp add UnityMCP -- \"{uvPath}\" run --directory \"{srcDir}\" server.py";

Longer-term: add an ExecPath.TryRun overload that accepts ArgumentList tokens and avoids string parsing entirely.


2067-2089: Avoid potential Process I/O deadlocks when probing for Python.

Read both stdout and stderr after WaitForExit, or at least WaitForExit before ReadToEnd to reduce the risk of blocking.

-                    using var p = Process.Start(psi);
-                    string outp = p.StandardOutput.ReadToEnd().Trim();
-                    p.WaitForExit(2000);
+                    using var p = Process.Start(psi);
+                    p.WaitForExit(2000);
+                    string outp = (p.StandardOutput.ReadToEnd() + "\n" + p.StandardError.ReadToEnd()).Trim();

And similarly for the non-Windows which-call block.

Also applies to: 2109-2123


968-1027: Simplify IMGUI layout: too many Begin/End Horizontal pairs.

The nested Begin/End churn is error-prone. Consider splitting rows with explicit EditorGUILayout.EndHorizontal() and immediate returns, or use vertical stacks for the extra “path” and “picker” rows to keep one active Horizontal at a time.


1445-1471: Unify debug logging here as well.

Switch this dev-mode log to McpLog.Info for consistency and to honor the debug toggle.

-                                UnityEngine.Debug.Log($"Currently in development mode. Package: {devPath}");
+                                MCPForUnity.Editor.Helpers.McpLog.Info($"Currently in development mode. Package: {devPath}", always: false);

1839-1853: Standardize user-facing logs through McpLog.

Several logs still use UnityEngine.Debug.*. Route through McpLog (Info/Warn/Error) for consistent prefixing and filtering.

-                    UnityEngine.Debug.Log("<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>: MCP for Unity already registered with Claude Code.");
+                    MCPForUnity.Editor.Helpers.McpLog.Info("MCP for Unity already registered with Claude Code.");
...
-                    UnityEngine.Debug.LogError($"MCP for Unity: Failed to start Claude CLI.\n{stderr}\n{stdout}");
+                    MCPForUnity.Editor.Helpers.McpLog.Error($"Failed to start Claude CLI.\n{stderr}\n{stdout}");
...
-                    UnityEngine.Debug.Log("Claude CLI reports no MCP for Unity server via 'mcp get' - setting status to NotConfigured and aborting unregister.");
+                    MCPForUnity.Editor.Helpers.McpLog.Info("Claude CLI reports no MCP for Unity server via 'mcp get' - setting status to NotConfigured and aborting unregister.", always: false);
...
-                    UnityEngine.Debug.LogWarning($"Error removing {serverName}: {stderr}");
+                    MCPForUnity.Editor.Helpers.McpLog.Warn($"Error removing {serverName}: {stderr}");
...
-                UnityEngine.Debug.Log("MCP for Unity: MCP server successfully unregistered from Claude Code.");
+                MCPForUnity.Editor.Helpers.McpLog.Info("MCP for Unity: MCP server successfully unregistered from Claude Code.");
...
-                UnityEngine.Debug.Log("No MCP servers found to unregister - already unregistered.");
+                MCPForUnity.Editor.Helpers.McpLog.Info("No MCP servers found to unregister - already unregistered.", always: false);

Also applies to: 1887-1891, 1903-1912, 1925-1932

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

13-16: Nit: explicitly mark Start as private.

Unity allows private Start; being explicit improves readability.

-        void Start()
+        private void Start()
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ComponentResolverTests.cs (3)

104-121: Assembly preference assertion is good but brittle to rename.

If the test asmdef name changes, this will fail. Consider asserting it’s not an Editor assembly and that it contains “TestAsmdef” via Contains/StartsWith.

-            Assert.AreEqual("TestAsmdef", assemblyName, 
-                "CustomComponent should be resolved from TestAsmdef assembly");
+            StringAssert.Contains("TestAsmdef", assemblyName, "Expected resolution from TestAsmdef (player) assembly");

88-102: Cache behavior test may depend on prior runs. Add a cache reset hook.

If ComponentResolver caches survive domain reloads, test order could affect results. Expose a test-only ClearCaches() or friend API and call it in SetUp/TearDown.

I can draft a minimal internal ClearCaches() in ManageGameObject.ComponentResolver (UNITY_EDITOR, InternalsVisibleTo) and a SetUp method here that calls it.


135-144: Redundant assertion.

The second check is implied by the first; keep one.

-            Assert.IsTrue(typeof(Component).IsAssignableFrom(type), "Resolved type should be assignable from Component");
-            Assert.IsTrue(typeof(MonoBehaviour).IsAssignableFrom(type) || 
-                         typeof(Component).IsAssignableFrom(type), "Should be a valid Unity component");
+            Assert.IsTrue(typeof(Component).IsAssignableFrom(type), "Resolved type should be assignable from Component");
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py (2)

309-340: Flip-sentinel: keep it, but narrow exceptions and add debug logs (and send explicit action).

Catching bare Exception twice hides actionable failures (BLE001/S110). Log at debug level and include an explicit "action" for future-proofing.

-            if resp.get("success") and (options or {}).get("force_sentinel_reload"):
+            if resp.get("success") and (options or {}).get("force_sentinel_reload"):
                 # Optional: flip sentinel via menu if explicitly requested
                 try:
-                    import threading, time, json, glob, os
+                    import threading, time, json, glob, os
+                    import logging as _log
                     def _latest_status() -> dict | None:
                         try:
                             files = sorted(glob.glob(os.path.expanduser("~/.unity-mcp/unity-mcp-status-*.json")), key=os.path.getmtime, reverse=True)
                             if not files:
                                 return None
                             with open(files[0], "r") as f:
                                 return json.loads(f.read())
-                        except Exception:
-                            return None
+                        except Exception as e:
+                            _log.debug("unity-mcp: _latest_status failed: %s", e)
+                            return None

                     def _flip_async():
                         try:
                             time.sleep(0.1)
                             st = _latest_status()
                             if st and st.get("reloading"):
                                 return
                             send_command_with_retry(
                                 "execute_menu_item",
-                                {"menuPath": "MCP/Flip Reload Sentinel"},
+                                {"action": "execute", "menuPath": "MCP/Flip Reload Sentinel"},
                                 max_retries=0,
                                 retry_ms=0,
                             )
-                        except Exception:
-                            pass
+                        except Exception as e:
+                            _log.debug("unity-mcp: flip_sentinel failed: %s", e)
                     threading.Thread(target=_flip_async, daemon=True).start()
-                except Exception:
-                    pass
+                except Exception as e:
+                    _log.debug("unity-mcp: scheduling flip_sentinel failed: %s", e)
                 return resp

487-494: Legacy 'update' path now uses refresh='debounced' — confirm Unity-side support.

If the Editor handler doesn’t recognize 'debounced', this may no-op the refresh. Please verify ManageScript supports it; if unsure, fall back to 'immediate' per capability.

tests/test_improved_anchor_matching.py (1)

206-224: Main block is not used by pytest; consider invoking pytest when run as a script.

Optional: replace manual runner with pytest.main to unify behavior locally and in CI.

-if __name__ == "__main__":
-    print("Testing improved anchor matching...")
-    print("="*60)
-    
-    success1 = test_improved_anchor_matching()
-    
-    print("\n" + "="*60)
-    print("Comparing old vs new behavior...")
-    success2 = test_old_vs_new_matching()
-    
-    print("\n" + "="*60)
-    print("Testing _apply_edits_locally with improved matching...")
-    success3 = test_apply_edits_with_improved_matching()
-    
-    print("\n" + "="*60)
-    if success1 and success2 and success3:
-        print("🎉 ALL TESTS PASSED! Improved anchor matching is working!")
-    else:
-        print("💥 Some tests failed. Need more work on anchor matching.")
+if __name__ == "__main__":
+    import pytest, sys
+    raise SystemExit(pytest.main([__file__] + sys.argv[1:]))
UnityMcpBridge/Editor/Tools/ExecuteMenuItem.cs (2)

99-116: Switch to synchronous ExecuteMenuItem looks good; consider machine-readable error codes in Response.Error.

Success path is clear and payload is useful. For failures, returning a stable code (e.g., "not_found_or_disabled") helps callers branch reliably without parsing strings.

-                Debug.LogWarning($"[ExecuteMenuItem] Failed (not found/disabled): '{menuPath}'");
-                return Response.Error(
-                    $"Failed to execute menu item (not found or disabled): '{menuPath}'",
-                    new { executed = false, menuPath }
-                );
+                Debug.LogWarning($"[ExecuteMenuItem] Failed (not found/disabled): '{menuPath}'");
+                return Response.Error(
+                    "not_found_or_disabled",
+                    new { executed = false, menuPath, reason = $"Failed to execute menu item (not found or disabled): '{menuPath}'" }
+                );

120-122: Validate main-thread assumption (and consider minimal allowlisting).

Unity requires menu execution on the main thread; please confirm the bridge dispatch guarantees this. Also consider an allowlist or safe-prefix policy in addition to the blacklist for defense-in-depth.

UnityMcpBridge/Editor/Tools/ManageAsset.cs (3)

10-10: Remove unused static import.

using static MCPForUnity.Editor.Tools.ManageGameObject; isn’t used here and can confuse readers about member origins.

-using static MCPForUnity.Editor.Tools.ManageGameObject;

295-305: Create parent folders before CreateFolder for reliability.

AssetDatabase.CreateFolder won’t create missing parents. Either error early or ensure parents exist.

-                if (!string.IsNullOrEmpty(parentDir) && !AssetDatabase.IsValidFolder(parentDir))
-                {
-                    // Recursively create parent folders if needed (AssetDatabase handles this internally)
-                    // Or we can do it manually: Directory.CreateDirectory(Path.Combine(Directory.GetCurrentDirectory(), parentDir)); AssetDatabase.Refresh();
-                }
+                if (!string.IsNullOrEmpty(parentDir) && !AssetDatabase.IsValidFolder(parentDir))
+                {
+                    Directory.CreateDirectory(Path.Combine(Directory.GetCurrentDirectory(), parentDir));
+                    AssetDatabase.Refresh();
+                }

358-389: Avoid duplicate warnings; separate “resolve failed” vs “component missing on GO”.

Currently unresolved types trigger two warnings (resolve failure + generic “not found”). Streamline flow to log once and continue.

-                            Component targetComponent = null;
-                            bool resolved = ComponentResolver.TryResolve(componentName, out var compType, out var compError);
-                            if (resolved)
-                            {
-                                targetComponent = gameObject.GetComponent(compType);
-                            }
-                            
-                            // Only warn about resolution failure if component also not found
-                            if (targetComponent == null && !resolved)
-                            {
-                                Debug.LogWarning(
-                                    $"[ManageAsset.ModifyAsset] Failed to resolve component '{componentName}' on '{gameObject.name}': {compError}"
-                                );
-                            }
+                            Component targetComponent = null;
+                            bool resolved = ComponentResolver.TryResolve(componentName, out var compType, out var compError);
+                            if (!resolved)
+                            {
+                                Debug.LogWarning($"[ManageAsset.ModifyAsset] Failed to resolve component '{componentName}' on '{gameObject.name}': {compError}");
+                                continue;
+                            }
+                            targetComponent = gameObject.GetComponent(compType);
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/AIPropertyMatchingTests.cs (1)

6-6: Remove unused static import.

using static MCPForUnity.Editor.Tools.ManageGameObject; isn’t needed in this test.

-using static MCPForUnity.Editor.Tools.ManageGameObject;
UnityMcpBridge/Editor/MCPForUnityBridge.cs (5)

233-236: Route “already running” notice through McpLog for consistency.

Keeps all informational logs using the same formatter and gating.

-                    if (IsDebugEnabled())
-                    {
-                        Debug.Log($"<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>: MCPForUnityBridge already running on port {currentUnityPort}");
-                    }
+                    McpLog.Info($"MCPForUnityBridge already running on port {currentUnityPort}", always: false);

355-355: Use McpLog for stop notice.

Small consistency improvement.

-                    if (IsDebugEnabled()) Debug.Log("<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>: MCPForUnityBridge stopped.");
+                    McpLog.Info("MCPForUnityBridge stopped.", always: false);

395-395: Don’t hide listener errors behind debug flag.

Consider logging as Warn always; intermittent Accept failures are rare and actionable.

-                        if (IsDebugEnabled()) Debug.LogError($"Listener error: {ex.Message}");
+                        McpLog.Warn($"Listener error: {ex.Message}");

409-413: Unify connection log with McpLog.

Minor formatting/gating consistency.

-                        var ep = client.Client?.RemoteEndPoint?.ToString() ?? "unknown";
-                        Debug.Log($"<b><color=#2EA3FF>UNITY-MCP</color></b>: Client connected {ep}");
+                        var ep = client.Client?.RemoteEndPoint?.ToString() ?? "unknown";
+                        McpLog.Info($"Client connected {ep}", always: false);

483-496: Narrow “benign” error classification.

Treating all IOException as benign can mask protocol bugs (e.g., oversized frames). Limit to timeouts/clean disconnects.

-                        bool isBenign =
-                            msg.IndexOf("Connection closed before reading expected bytes", StringComparison.OrdinalIgnoreCase) >= 0
-                            || msg.IndexOf("Read timed out", StringComparison.OrdinalIgnoreCase) >= 0
-                            || ex is System.IO.IOException;
+                        bool isClosed = msg.IndexOf("Connection closed before reading expected bytes", StringComparison.OrdinalIgnoreCase) >= 0;
+                        bool isTimeout = msg.IndexOf("Read timed out", StringComparison.OrdinalIgnoreCase) >= 0;
+                        bool isBenign = isClosed || isTimeout;
UnityMcpBridge/Editor/Tools/ManageScript.cs (8)

113-118: Return a structured error payload for null params (avoid raw string as data).

Prefer a small object to keep error shapes machine‑parsable and consistent.

-                return Response.Error("invalid_params", "Parameters cannot be null.");
+                return Response.Error("invalid_params", new { detail = "Parameters cannot be null." });

120-120: Use culture-invariant lowercasing for action parsing.

ToLowerInvariant() avoids locale surprises.

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

253-256: Unify deprecation logs on McpLog for consistency.

Other branches use McpLog.Warn; align edit case as well.

-                    Debug.LogWarning("manage_script.edit is deprecated; prefer apply_text_edits. Serving structured edit for backward compatibility.");
+                    McpLog.Warn("manage_script.edit is deprecated; prefer apply_text_edits. Serving structured edit for backward compatibility.");

340-344: Use McpLog for warnings to keep a consistent prefix/filtering.

Creation path still logs via Debug.LogWarning.

-                // Log warnings but don't block creation
-                Debug.LogWarning($"Script validation warnings for {name}:\n" + string.Join("\n", validationErrors));
+                // Log warnings but don't block creation
+                McpLog.Warn($"Script validation warnings for {name}:\n" + string.Join("\n", validationErrors));

774-784: Deduplicate refresh logic: reuse ImportAndRequestCompile and make Info logs debug-gated.

One helper keeps path sanitization and the compile request in sync with other code paths; also reduce log noise unless debug is on.

-                if (immediate)
-                {
-                    McpLog.Info($"[ManageScript] ApplyTextEdits: immediate refresh for '{relativePath}'");
-                    AssetDatabase.ImportAsset(
-                        relativePath,
-                        ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate
-                    );
-#if UNITY_EDITOR
-                    UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation();
-#endif
-                }
+                if (immediate)
+                {
+                    McpLog.Info($"[ManageScript] ApplyTextEdits: immediate refresh for '{relativePath}'", always: false);
+                    ManageScriptRefreshHelpers.ImportAndRequestCompile(relativePath);
+                }

787-788: Gate informational logs behind the debug flag.

Keep console quieter by default.

-                    McpLog.Info($"[ManageScript] ApplyTextEdits: debounced refresh scheduled for '{relativePath}'");
+                    McpLog.Info($"[ManageScript] ApplyTextEdits: debounced refresh scheduled for '{relativePath}'", always: false);

675-679: Remove unused locals in the scoped-balance block.

originalLength isn’t used; simplifies and avoids warnings.

-                    int originalLength = sp.end - sp.start;
-                    int newLength = sp.text?.Length ?? 0;
-                    int endPos = sp.start + newLength;
+                    int endPos = sp.start + (sp.text?.Length ?? 0);

2640-2642: Defend against null/empty paths in refresh helpers.

Prevent passing null into Editor APIs in edge cases.

     public static void ScheduleScriptRefresh(string relPath)
     {
-        var sp = SanitizeAssetsPath(relPath);
+        var sp = SanitizeAssetsPath(relPath);
+        if (string.IsNullOrEmpty(sp)) return;
         RefreshDebounce.Schedule(sp, TimeSpan.FromMilliseconds(200));
     }

     public static void ImportAndRequestCompile(string relPath, bool synchronous = true)
     {
-        var sp = SanitizeAssetsPath(relPath);
+        var sp = SanitizeAssetsPath(relPath);
+        if (string.IsNullOrEmpty(sp)) return;
         var opts = ImportAssetOptions.ForceUpdate;
         if (synchronous) opts |= ImportAssetOptions.ForceSynchronousImport;
         AssetDatabase.ImportAsset(sp, opts);
 #if UNITY_EDITOR
         UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation();
 #endif
     }

Also applies to: 2646-2653

UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (2)

106-106: Drop redundant local import.

re is already imported at module scope; avoid function‑local import.

-    import re

581-587: Don’t swallow exceptions silently; log at debug level.

Keep failures diagnosable without spamming users.

+import logging
-                    try:
-                        _trigger_sentinel_async()
-                    except Exception:
-                        pass
+                    try:
+                        _trigger_sentinel_async()
+                    except Exception as ex:
+                        logging.debug("Sentinel flip suppressed: %s", ex, exc_info=True)

(Apply the same change at the other try/except sentinel sites.)

Also applies to: 709-712, 733-735, 943-944

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

16-21: Assert structured error shape, not just non-null.

Strengthen the check by asserting success == false.

-            var result = ManageScript.HandleCommand(null);
-            Assert.IsNotNull(result, "Should handle null parameters gracefully");
+            var result = ManageScript.HandleCommand(null);
+            var jo = JObject.FromObject(result);
+            Assert.IsFalse((bool)jo["success"], "Should return an error for null parameters");

26-35: Also assert error for invalid action.

Make the test verify the failure contract.

-            var result = ManageScript.HandleCommand(paramsObj);
-            Assert.IsNotNull(result, "Should return error result for invalid action");
+            var result = ManageScript.HandleCommand(paramsObj);
+            var jo = JObject.FromObject(result);
+            Assert.IsFalse((bool)jo["success"], "Should return error for invalid action");

49-53: Optionally assert expected token on imbalance.

Small improvement: assert the reported expected delimiter to catch regressions.

-            bool result = CallCheckBalancedDelimiters(unbalancedCode, out int line, out char expected);
-            Assert.IsFalse(result, "Unbalanced code should fail balance check");
+            bool result = CallCheckBalancedDelimiters(unbalancedCode, out int line, out char expected);
+            Assert.IsFalse(result, "Unbalanced code should fail balance check");
+            Assert.AreEqual('}', expected, "Expected closing brace should be reported");
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (10)

24-37: Shared JsonSerializer: add array fallbacks + note non-thread-safe usage

  • Json.NET JsonSerializer isn’t thread-safe. If any callers hit this from background tasks, guard with a lock or use per-call instances.
  • Your converters mostly expect object shapes (x/y/z), but several call sites pass JArray inputs (e.g., material vectors/colors). Add light-weight array fallbacks in ConvertJTokenToType to avoid noisy exceptions.

Apply array fallbacks in ConvertJTokenToType:

@@
 private static object ConvertJTokenToType(JToken token, Type targetType, JsonSerializer inputSerializer)
 {
     if (token == null || token.Type == JTokenType.Null)
@@
-    try
+    // Fast-path array fallbacks for common Unity types when input is [x,y,z,...]
+    if (token is JArray arr)
+    {
+        try
+        {
+            if (targetType == typeof(Vector2) && arr.Count == 2) return new Vector2(arr[0].ToObject<float>(), arr[1].ToObject<float>());
+            if (targetType == typeof(Vector3) && arr.Count == 3) return new Vector3(arr[0].ToObject<float>(), arr[1].ToObject<float>(), arr[2].ToObject<float>());
+            if (targetType == typeof(Vector4) && arr.Count == 4) return new Vector4(arr[0].ToObject<float>(), arr[1].ToObject<float>(), arr[2].ToObject<float>(), arr[3].ToObject<float>());
+            if (targetType == typeof(Quaternion) && arr.Count == 4) return new Quaternion(arr[0].ToObject<float>(), arr[1].ToObject<float>(), arr[2].ToObject<float>(), arr[3].ToObject<float>());
+            if (targetType == typeof(Color) && arr.Count >= 3) return new Color(arr[0].ToObject<float>(), arr[1].ToObject<float>(), arr[2].ToObject<float>(), arr.Count > 3 ? arr[3].ToObject<float>() : 1f);
+        }
+        catch { /* fall through to serializer */ }
+    }
+    try
     {
         // Use the provided serializer instance which includes our custom converters
         return token.ToObject(targetType, inputSerializer);
     }

386-426: Creating missing tags at runtime — add safety note

InternalEditorUtility.AddTag mutates TagManager. Consider guarding against play mode, and optionally AssetDatabase.SaveAssets after tag creation.


704-719: Align invalid layer handling with 'create' path (warn vs. error)

Create warns on unknown layer; modify errors out. Recommend consistency to avoid surprising failures.

-            if (layerId == -1 && layerName != "Default")
-            {
-                return Response.Error(
-                    $"Invalid layer specified: '{layerName}'. Use a valid layer name."
-                );
-            }
+            if (layerId == -1 && layerName != "Default")
+            {
+                Debug.LogWarning($"[ManageGameObject.Modify] Layer '{layerName}' not found. Layer unchanged.");
+            }
             if (layerId != -1 && targetGo.layer != layerId)
             {
                 targetGo.layer = layerId;
                 modified = true;
             }

787-822: Partial failures currently return overall error — consider partial-success contract

You apply valid properties and still return Response.Error if any fail. Consider adding a partialSuccess flag or success=true with warnings to better reflect state.


913-984: Simplify component enumeration; avoid reverse + Insert(0)

Reverse iteration with Insert(0) is O(n^2). Iterate forward and Add to preserve order.

-            for (int i = componentCount - 1; i >= 0; i--)
+            for (int i = 0; i < componentCount; i++)
             {
                 Component c = componentsToIterate[i];
                 if (c == null) 
                 {
                     continue;
                 }
                 try 
                 {
                     var data = Helpers.GameObjectSerializer.GetComponentData(c, includeNonPublicSerialized);
                     if (data != null)
                     {
-                        componentData.Insert(0, data);
+                        componentData.Add(data);
                     }
                 }
                 catch (Exception ex)
                 {
                     Debug.LogError($"[GetComponentsFromTarget REVERSE for] Error processing component {c.GetType().FullName} (ID: {c.GetInstanceID()}) on {targetGo.name}: {ex.Message}\n{ex.StackTrace}");
-                    componentData.Insert(0, new JObject(
+                    componentData.Add(new JObject(
                         new JProperty("typeName", c.GetType().FullName + " (Serialization Error)"),
                         new JProperty("instanceID", c.GetInstanceID()),
                         new JProperty("error", ex.Message)
                     ));
                 }
             }

1141-1162: De-duplicate Vector3 parsing by reusing Vector3Helper

Use existing helper to avoid skew in parsing behavior and error messages.

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

1767-1816: Support 3-float arrays for material vectors

Common inputs are [x,y,z]; set Vector4(x,y,z,0) when Color conversion fails.

-                    else if (value.Type == JTokenType.Float || value.Type == JTokenType.Integer)
+                    else if (value.Type == JTokenType.Float || value.Type == JTokenType.Integer)
                     {
                         try { material.SetFloat(finalPart, value.ToObject<float>(inputSerializer)); return true; } catch { }
                     }
                     else if (value.Type == JTokenType.Boolean)
                     {
                         try { material.SetFloat(finalPart, value.ToObject<bool>(inputSerializer) ? 1f : 0f); return true; } catch { }
                     }
-                    else if (value.Type == JTokenType.String)
+                    else if (value.Type == JTokenType.String)
                     {
                         // Try converting to Texture using the serializer/converter
                         try {
                             Texture texture = value.ToObject<Texture>(inputSerializer);
                             if (texture != null) {
                                 material.SetTexture(finalPart, texture);
                                 return true;
                             }
                         } catch { }
                     }
+                    // Handle [x,y,z] → Vector4(x,y,z,0)
+                    if (value is JArray v3 && v3.Count == 3)
+                    {
+                        try {
+                            var vec = new Vector4(v3[0].ToObject<float>(), v3[1].ToObject<float>(), v3[2].ToObject<float>(), 0f);
+                            material.SetVector(finalPart, vec);
+                            return true;
+                        } catch { }
+                    }

1524-1584: Differentiate “not found” vs. “conversion failed”

SetProperty returns false for both missing members and bad conversions, which results in misleading “Property … not found” warnings. Return a richer result (enum) so callers can log accurate diagnostics and better suggestions.


2316-2355: Stabilize AI suggestions cache key

Order of availableProperties affects the cache key. Sort to increase hit rate.

-    var cacheKey = $"{userInput.ToLowerInvariant()}:{string.Join(",", availableProperties)}";
+    var ordered = availableProperties.OrderBy(p => p, StringComparer.Ordinal).ToArray();
+    var cacheKey = $"{userInput.ToLowerInvariant()}:{string.Join(",", ordered)}";

2234-2271: Cache player assembly names

CompilationPipeline.GetAssemblies is relatively heavy. Cache Player assembly names once per domain to reduce cost in FindCandidates.

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

33-41: Assert the error payload, not just non-null

Strengthen the contract: verify success=false and error message using JObject.FromObject(result).

-            var result = ManageGameObject.HandleCommand(null);
-            
-            Assert.IsNotNull(result, "Should return a result object");
-            // Note: Actual error checking would need access to Response structure
+            var result = ManageGameObject.HandleCommand(null);
+            Assert.IsNotNull(result);
+            var jo = JObject.FromObject(result);
+            Assert.IsFalse((bool)jo["success"]);
+            StringAssert.Contains("cannot be null", (string)jo["error"]);

42-50: Same: verify error shape for empty params

Mirror the stronger assertions here too.

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

199-227: Use correct parameter name: searchMethod (camelCase)

Prod code reads searchMethod; tests send search_method. They pass only due to defaulting, reducing coverage of the param path.

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

258-264: Assert partial-failure envelope is returned

You collect componentErrors and return Response.Error. Validate presence to lock the contract.

-            // The collect-and-continue behavior means we should get an error response 
-            // that contains info about the failed properties, but valid ones were still applied
-            // This proves the collect-and-continue behavior is working
+            var jo = JObject.FromObject(result);
+            Assert.IsFalse((bool)jo["success"], "Partial failures should return error");
+            Assert.IsNotNull(jo["data"]?["componentErrors"], "componentErrors should be present");

276-290: Same searchMethod naming nit

Align with prod field name to exercise that path deterministically.

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

CodeRabbit fixes:
- Fix Python test assertions to use assert instead of print/return
- Update version consistency: server_version.txt from 3.2.0 to 3.3.0
- Assembly definition references already correctly configured

Greptile style fixes:
- Add missing newlines at end of Unity meta files and source files
- Fix test logic assumptions: use GreaterOrEqual instead of exact counts
- Make test assertions more robust for fuzzy matching algorithms

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

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

@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 (4)
tests/test_improved_anchor_matching.py (4)

10-14: Avoid leaking sys.path mutations across the test session

Modifying sys.path at module import time can affect other tests. Prefer a pytest fixture that inserts SRC and restores sys.path after the module completes.

Example fixture (place in tests/conftest.py or at top of this file and import/use):

import sys, pathlib, pytest

@pytest.fixture(scope="module", autouse=True)
def _patch_sys_path():
    root = pathlib.Path(__file__).resolve().parents[1]
    src = root / "UnityMcpBridge" / "UnityMcpServer~" / "src"
    old = list(sys.path)
    sys.path.insert(0, str(src))
    try:
        yield
    finally:
        sys.path[:] = old

31-35: Guard against None spec/loader in load_module

importlib.util.spec_from_file_location can return None (or a spec with loader=None). Add a defensive check to fail fast with a clear error.

 def load_module(path, name):
-    spec = importlib.util.spec_from_file_location(name, path)
-    module = importlib.util.module_from_spec(spec)
-    spec.loader.exec_module(module)
+    spec = importlib.util.spec_from_file_location(name, path)
+    if spec is None or spec.loader is None:
+        raise ImportError(f"cannot load {name} from {path}")
+    module = importlib.util.module_from_spec(spec)
+    spec.loader.exec_module(module)
     return module

144-150: Replace assert False and make line-splitting platform-safe

  • Avoid assert False (ruff B011) and use a None default with next().
  • Use splitlines() for CRLF/CR handling.
-    lines = result.split('\n')
-    try:
-        idx = next(i for i, line in enumerate(lines) if "NewMethod" in line)
-    except StopIteration:
-        assert False, "NewMethod not found in result"
-    total_lines = len(lines)
+    lines = result.splitlines()
+    idx = next((i for i, line in enumerate(lines) if "NewMethod" in line), None)
+    assert idx is not None, "NewMethod not found in result"
+    total_lines = len(lines)
     assert idx >= total_lines - 5, f"method inserted too early (idx={idx}, total_lines={total_lines})"

152-170: Remove the main runner or delegate to pytest

The test functions don’t return booleans; this block always prints failure when run directly and is redundant under pytest.

-if __name__ == "__main__":
-    print("Testing improved anchor matching...")
-    print("="*60)
-    
-    success1 = test_improved_anchor_matching()
-    
-    print("\n" + "="*60)
-    print("Comparing old vs new behavior...")
-    success2 = test_old_vs_new_matching()
-    
-    print("\n" + "="*60)
-    print("Testing _apply_edits_locally with improved matching...")
-    success3 = test_apply_edits_with_improved_matching()
-    
-    print("\n" + "="*60)
-    if success1 and success2 and success3:
-        print("🎉 ALL TESTS PASSED! Improved anchor matching is working!")
-    else:
-        print("💥 Some tests failed. Need more work on anchor matching.")
+if __name__ == "__main__":
+    import pytest, sys
+    sys.exit(pytest.main([__file__]))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 28a9bc6 and 9697653.

📒 Files selected for processing (8)
  • TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs.meta (0 hunks)
  • TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/CustomComponent.cs.meta (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/TestAsmdef.asmdef (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/AIPropertyMatchingTests.cs (1 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptValidationTests.cs.meta (1 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/server_version.txt (1 hunks)
  • tests/test_improved_anchor_matching.py (1 hunks)
💤 Files with no reviewable changes (1)
  • TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs.meta
✅ Files skipped from review due to trivial changes (2)
  • TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/CustomComponent.cs.meta
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptValidationTests.cs.meta
🚧 Files skipped from review as they are similar to previous changes (4)
  • TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/TestAsmdef.asmdef
  • UnityMcpBridge/UnityMcpServer~/src/server_version.txt
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/AIPropertyMatchingTests.cs
  • TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs
🧰 Additional context used
🧬 Code graph analysis (1)
tests/test_improved_anchor_matching.py (1)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (2)
  • _find_best_anchor_match (86-125)
  • _apply_edits_locally (9-83)
🪛 Ruff (0.12.2)
tests/test_improved_anchor_matching.py

68-68: Use of assert detected

(S101)


72-72: Use of assert detected

(S101)


115-115: Use of assert detected

(S101)


116-116: Use of assert detected

(S101)


118-118: Use of assert detected

(S101)


148-148: Use of assert detected

(S101)


148-148: Do not assert False (python -O removes these calls), raise AssertionError()

Replace assert False

(B011)


150-150: Use of assert detected

(S101)

🔇 Additional comments (2)
tests/test_improved_anchor_matching.py (2)

63-72: LGTM: assertions ensure the anchor is near the class end

Clear, strict checks replace prints/returns; this will fail on regressions.


105-118: LGTM: old vs new behavior is asserted, not printed

Asserting new_line > old_line and proximity to end makes the test robust.

- Remove deprecated _trigger_sentinel_async function and all call sites
- Clean up force_sentinel_reload option handling (replaced with Unity's synchronous system)
- Addresses repeated CodeRabbit flagging of unused vestigial code

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

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

@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

♻️ Duplicate comments (2)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (2)

118-118: Pattern detection logic is acceptable for its intended use.

The pattern '}' in pattern check provides a reasonable heuristic for identifying closing brace patterns. While it could theoretically match unrelated patterns, it's appropriately scoped for Unity C# script editing where this pattern is meaningful.


180-180: Method signature detection is appropriately scoped.

The regex pattern r'\b(void|public|private|protected)\s+\w+\s*\(' is designed for C# method signatures in Unity scripts. While it could match properties or events, the penalty application is conservative and appropriate for the heuristic scoring context.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9697653 and 290c913.

📒 Files selected for processing (1)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (14 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (1)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (388-407)
🪛 Ruff (0.12.2)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py

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

(TRY003)


672-672: Do not catch blind exception: Exception

(BLE001)


771-771: Function definition does not bind loop variable m

(B023)

🔇 Additional comments (15)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (15)

5-5: LGTM!

The import addition is appropriate for the file operations used later in the code.


43-51: Improved anchor matching with robust heuristics.

The implementation correctly replaces direct regex usage with the new _find_best_anchor_match function that provides better heuristics for finding appropriate matches, especially for class-ending braces.


86-125: Well-designed anchor matching with semantic awareness.

The _find_best_anchor_match function provides intelligent heuristics for choosing the best match when multiple candidates exist. The special handling for closing brace patterns and the prefer_last parameter make the anchor resolution more reliable for Unity C# scripts.


128-193: Sophisticated scoring algorithm for C# structure awareness.

The _find_best_closing_brace_match function implements comprehensive heuristics that consider indentation levels, proximity to EOF, and method signatures. This should significantly improve the accuracy of class-ending brace detection.


207-207: LGTM! Validation delegation to Unity compiler services.

Removing the internal structural balance validation and delegating this responsibility to Unity's compiler services is a sound architectural decision that leverages Unity's built-in capabilities.


537-551: Improved refresh strategy for structured edits.

The preference for immediate refresh on structured edits makes sense to avoid missed reloads when the Unity Editor is unfocused. The removal of optional sentinel reload logic simplifies the flow.


596-597: Consistent use of improved anchor matching.

Good application of the new _find_best_anchor_match function in the mixed batch processing path, maintaining consistency across different code paths.


666-666: Smart default for mixed edit operations.

The debounced refresh default for mixed operations balances performance with reliability, while allowing explicit override when needed.


671-671: LGTM! Sentinel reload cleanup.

The removal of optional sentinel reload logic simplifies the codebase while maintaining the core functionality. The deprecated behavior is properly cleaned up.

Also applies to: 689-691


677-678: Appropriate refresh strategy for subsequent structured edits.

Using debounced refresh for structured edits in mixed batches is sensible to avoid overwhelming Unity with rapid refresh requests.


720-724: Enhanced anchor matching with improved error handling.

The consistent application of _find_best_anchor_match with proper flag handling and clear error messages improves the reliability of anchor-based operations.


765-766: Consistent anchor matching across all paths.

Good use of the unified _find_best_anchor_match function in the regex_replace path, ensuring consistent behavior across different operation types.


773-773: LGTM! Proper delegation to Unity's validation.

The comment correctly indicates that validation is now handled by Unity's built-in compiler services, which is more reliable than custom validation logic.


787-788: Clear error handling for empty edit operations.

The no_spans check provides a helpful error message when no applicable edits are computed, improving the user experience when anchors aren't found.


802-802: Consistent debounced refresh strategy.

The default to debounced refresh across different text edit paths provides a good balance between responsiveness and performance, while still allowing explicit override when needed.

Also applies to: 808-810, 861-861, 886-886, 890-892

- Fix material creation to handle missing shaders (URP/HDRP fallbacks)
- Add try/finally blocks for RenderTexture resource management
- Fix Python closure variable binding (ruff B023)

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

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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (2)

39-41: Align default anchor_insert position: currently inconsistent and likely wrong for class-end inserts.

_apply_edits_locally defaults to "before", but the mixed/text conversion paths default to "after". For class-closing-brace anchors, "after" places text outside the class. Unify to "before".

- position = (e.get("position") or "after").lower()
+ position = (e.get("position") or "before").lower()

Apply in both places:

  • mixed path anchor_insert (around building at_edits)
  • text path anchor_insert (conversion to apply_text_edits)

Also applies to: 591-598, 718-724


527-533: anchor_replace/anchor_delete are misrouted to the text path and then rejected.

STRUCT includes anchor_replace/anchor_delete, but structured_kinds omits them, causing pure anchor_replace batches to hit the text conversion and fail with “unsupported_op”.

- structured_kinds = {"replace_class","delete_class","replace_method","delete_method","insert_method","anchor_insert"}
+ structured_kinds = {
+   "replace_class","delete_class","replace_method","delete_method",
+   "insert_method","anchor_insert","anchor_replace","anchor_delete"
+ }

Also applies to: 699-701

🧹 Nitpick comments (7)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (4)

117-125: Make closing-brace pattern detection more robust.

'}' in pattern and '$' in pattern are brittle and can misclassify unrelated patterns. Normalize and check for a line that’s just a closing brace.

- is_closing_brace_pattern = '}' in pattern and ('$' in pattern or pattern.endswith(r'\s*'))
+ # Treat anchors intended for a line that is just a closing brace
+ norm = pattern.strip().replace('(?m)', '')
+ is_closing_brace_pattern = bool(re.fullmatch(r'(?:\^)?\s*\\?\}\s*(?:\$)?', norm))

167-172: Tighten heuristics: handle tabs and broaden method-signature hints.

  • Indentation: tabs aren’t accounted for; normalize to spaces before scoring.
  • Method proximity: include internal|static|virtual|override|async and generic return types.
- indentation = len(line_content) - len(line_content.lstrip())
+ indent_ws = re.match(r'[ \t]*', line_content).group(0)
+ indentation = len(indent_ws.replace('\t', '    '))
@@
- for context_line in context_lines:
-     if re.search(r'\b(void|public|private|protected)\s+\w+\s*\(', context_line):
+ for context_line in context_lines:
+     if re.search(
+         r'\b(public|private|protected|internal)\b'
+         r'(?:\s+(?:static|virtual|override|async))*\s+'
+         r'[\w<>\[\],\s]+\s+\w+\s*\(',
+         context_line
+     ):
          score -= 5  # Penalty for being near method signatures

Also applies to: 178-186


672-673: Log broad exceptions rather than swallowing silently.

Catching Exception is fine here, but at least log the error for diagnosis.

-            except Exception as e:
-                return _with_norm({"success": False, "message": f"Text edit conversion failed: {e}"}, normalized_for_echo, routing="mixed/text-first")
+            except Exception as e:
+                import logging; logging.getLogger(__name__).exception("Text edit conversion failed")
+                return _with_norm({"success": False, "message": f"Text edit conversion failed: {e}"}, normalized_for_echo, routing="mixed/text-first")

815-816: Also log the exception in text-path conversion.

-        except Exception as e:
-            return _with_norm({"success": False, "code": "conversion_failed", "message": f"Edit conversion failed: {e}"}, normalized_for_echo, routing="text")
+        except Exception as e:
+            import logging; logging.getLogger(__name__).exception("Edit conversion failed (text path)")
+            return _with_norm({"success": False, "code": "conversion_failed", "message": f"Edit conversion failed: {e}"}, normalized_for_echo, routing="text")
UnityMcpBridge/Editor/Tools/ManageAsset.cs (3)

10-10: Remove unused static import to avoid symbol bleed.
Not needed for ComponentResolver; it risks accidental name collisions.

-using static MCPForUnity.Editor.Tools.ManageGameObject;

182-193: Make shader fallback pipeline-aware.
Prefer the active RP’s default shader; keep Unlit/Color as last resort.

-// Prefer provided shader; fall back to common pipelines
-var requested = properties?["shader"]?.ToString();
-Shader shader =
-    (!string.IsNullOrEmpty(requested) ? Shader.Find(requested) : null)
-    ?? Shader.Find("Universal Render Pipeline/Lit")
-    ?? Shader.Find("HDRP/Lit")
-    ?? Shader.Find("Standard")
-    ?? Shader.Find("Unlit/Color");
+// Prefer provided shader; otherwise choose a sensible default for the active render pipeline
+var requested = properties?["shader"]?.ToString();
+Shader shader = !string.IsNullOrEmpty(requested) ? Shader.Find(requested) : null;
+if (shader == null)
+{
+    var rp = UnityEngine.Rendering.RenderPipelineManager.currentPipeline;
+    var defaultPath = rp == null
+        ? "Standard" // Built-in RP
+        : (rp.GetType().Name.IndexOf("HD", StringComparison.OrdinalIgnoreCase) >= 0
+            ? "HDRP/Lit"
+            : "Universal Render Pipeline/Lit");
+    shader = Shader.Find(defaultPath) ?? Shader.Find("Unlit/Color");
+}

368-383: Augment resolution warnings with available component context.
Include attached components to help users correct names quickly.

-    Debug.LogWarning(
-        $"[ManageAsset.ModifyAsset] Failed to resolve component '{componentName}' on '{gameObject.name}': {compError}"
-    );
+    var available = string.Join(", ", gameObject.GetComponents<Component>().Select(c => c.GetType().FullName));
+    Debug.LogWarning(
+        $"[ManageAsset.ModifyAsset] Failed to resolve component '{componentName}' on '{gameObject.name}': {compError}. Available on GameObject: [{available}]"
+    );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 290c913 and 264b585.

📒 Files selected for processing (2)
  • UnityMcpBridge/Editor/Tools/ManageAsset.cs (6 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (15 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
UnityMcpBridge/Editor/Tools/ManageAsset.cs (2)
UnityMcpBridge/Editor/Tools/ManageGameObject.cs (4)
  • ManageGameObject (22-2167)
  • Type (2152-2166)
  • ComponentResolver (2173-2433)
  • TryResolve (2183-2219)
UnityMcpBridge/Editor/Helpers/Response.cs (2)
  • Response (10-61)
  • Error (41-60)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (1)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (388-407)
🪛 Ruff (0.12.2)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py

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

(TRY003)


672-672: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (3)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (2)

565-568: Confirm payload shape for encoded contents.

You gate on contentsEncoded but read encodedContents. Verify the manage_script read response uses both keys as intended (flag + payload) to avoid silent None. If shapes vary across Unity versions, normalize here.


633-635: Good fix: freeze the regex match for $-backref expansion.

Capturing m via default arg avoids late-binding bugs and matches Ruff B023 guidance.

Also applies to: 770-772

UnityMcpBridge/Editor/Tools/ManageAsset.cs (1)

963-965: LGTM: safer float property handling.
Requiring a non-empty name and numeric value prevents accidental misuse.

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

Choose a reason for hiding this comment

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

⚠️ Potential issue

Bug: Using ComponentResolver for ScriptableObject types always fails.
ComponentResolver filters to Component/MonoBehaviour; SO creation will never resolve.

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

Add this helper (place near other helpers):

private static bool TryResolveScriptableObjectType(string nameOrFullName, out Type type, out string error)
{
    error = string.Empty;
    type = null;
    if (string.IsNullOrWhiteSpace(nameOrFullName))
    {
        error = "Type name cannot be null or empty.";
        return false;
    }

    // Direct load (FQN)
    var t = Type.GetType(nameOrFullName, throwOnError: false);
    if (t != null)
    {
        if (typeof(ScriptableObject).IsAssignableFrom(t)) { type = t; return true; }
        error = $"Type '{nameOrFullName}' found but does not inherit from ScriptableObject.";
        return false;
    }

#if UNITY_EDITOR
    // Fast editor index
    var matches = TypeCache.GetTypesDerivedFrom<ScriptableObject>()
        .Where(x => x.Name.Equals(nameOrFullName, StringComparison.Ordinal) ||
                    (x.FullName?.Equals(nameOrFullName, StringComparison.Ordinal) ?? false))
        .ToList();
    if (matches.Count == 1) { type = matches[0]; return true; }
    if (matches.Count > 1)
    {
        error = $"Multiple ScriptableObject types matched '{nameOrFullName}': {string.Join(", ", matches.Select(m => m.FullName))}. Provide a fully-qualified name.";
        return false;
    }
#endif

    // Fallback: scan loaded assemblies
    var loaded = AppDomain.CurrentDomain.GetAssemblies();
    static IEnumerable<Type> SafeGetTypes(System.Reflection.Assembly a)
    {
        try { return a.GetTypes(); }
        catch (System.Reflection.ReflectionTypeLoadException rtle) { return rtle.Types.Where(tt => tt != null)!; }
    }
    var cands = loaded.SelectMany(SafeGetTypes)
                      .Where(tt => typeof(ScriptableObject).IsAssignableFrom(tt))
                      .Where(tt => tt.Name.Equals(nameOrFullName, StringComparison.Ordinal) ||
                                   (tt.FullName?.Equals(nameOrFullName, StringComparison.Ordinal) ?? false))
                      .ToList();
    if (cands.Count == 1) { type = cands[0]; return true; }
    if (cands.Count > 1)
    {
        error = $"Multiple ScriptableObject types matched '{nameOrFullName}': {string.Join(", ", cands.Select(m => m.FullName))}. Provide a fully-qualified name.";
        return false;
    }

    error = $"Type '{nameOrFullName}' not found.";
    return false;
}
🤖 Prompt for AI Agents
In UnityMcpBridge/Editor/Tools/ManageAsset.cs around lines 215-225, the code
uses ComponentResolver which only resolves Component/MonoBehaviour types so
ScriptableObject types never resolve; add a new helper
TryResolveScriptableObjectType (place it near the other helper methods in the
file) that attempts direct Type.GetType, uses UnityEditor.TypeCache for editor
builds, and falls back to scanning loaded assemblies while handling
ReflectionTypeLoadException, and then replace the ComponentResolver.TryResolve
call with TryResolveScriptableObjectType to set scriptType/out error and keep
the existing validation and error-return logic.

…ments

- Move RenderTexture cleanup to finally block to ensure proper disposal
- Add PNG data validation before Base64 conversion
- Add explicit TextureFormat.RGB24 specification
- Add bool() coercion for prefer_last parameter to handle non-boolean JSON values

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

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

@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

Caution

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

⚠️ Outside diff range comments (1)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (1)

633-639: Harden $n backref expansion against out-of-range groups.

_m.group(int(...)) raises IndexError when $n > last group. Return empty string instead.

Apply:

@@
-                        def _expand_dollars(rep: str, _m=m) -> str:
-                            return _re.sub(r"\$(\d+)", lambda g: _m.group(int(g.group(1))) or "", rep)
+                        def _expand_dollars(rep: str, _m=m) -> str:
+                            def _safe(g: _re.Match) -> str:
+                                try:
+                                    idx = int(g.group(1))
+                                    val = _m.group(idx)
+                                    return val or ""
+                                except (IndexError, ValueError):
+                                    return ""
+                            return _re.sub(r"\$(\d+)", _safe, rep)
@@
-                        def _expand_dollars(rep: str, _m=m) -> str:
-                            return _re.sub(r"\$(\d+)", lambda g: _m.group(int(g.group(1))) or "", rep)
+                        def _expand_dollars(rep: str, _m=m) -> str:
+                            def _safe(g: _re.Match) -> str:
+                                try:
+                                    idx = int(g.group(1))
+                                    val = _m.group(idx)
+                                    return val or ""
+                                except (IndexError, ValueError):
+                                    return ""
+                            return _re.sub(r"\$(\d+)", _safe, rep)

Also applies to: 770-773

♻️ Duplicate comments (2)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (2)

86-126: Brace-pattern detection is fragile; decide by matches, not by pattern text.

'}' in pattern and $ heuristics can misclassify unrelated patterns that contain braces.

Apply:

@@
-    is_closing_brace_pattern = '}' in pattern and ('$' in pattern or pattern.endswith(r'\s*'))
-
-    if is_closing_brace_pattern and prefer_last:
-        # Use heuristics to find the best closing brace match
-        return _find_best_closing_brace_match(matches, text)
+    def _ends_at_eol(m: re.Match) -> bool:
+        end = m.end()
+        return end == len(text) or text[end:end+1] in ("\n", "\r")
+    closing_matches = [m for m in matches if m.group(0).strip().endswith("}") and _ends_at_eol(m)]
+    if prefer_last and closing_matches:
+        return _find_best_closing_brace_match(closing_matches, text)

128-194: Tighten “method-like” context regex to avoid false penalties.

Current pattern may match classes/properties/events. Adjust to better identify method signatures and ignore type declarations.

Apply:

@@
-            for context_line in context_lines:
-                if re.search(r'\b(void|public|private|protected)\s+\w+\s*\(', context_line):
-                    score -= 5  # Penalty for being near method signatures
+            for context_line in context_lines:
+                is_type_decl = re.search(r'\b(class|struct|interface|enum)\b', context_line)
+                is_method_sig = re.search(r'^\s*(public|private|protected|internal)\s+(static\s+)?[\w<>,\[\]\s\?]+\s+\w+\s*\(', context_line)
+                if is_method_sig and not is_type_decl:
+                    score -= 5  # Penalty for being near method signatures
🧹 Nitpick comments (3)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (3)

596-603: Respect per-edit prefer_last consistently.

Hard-coding prefer_last=True ignores caller intent. Use e.get("prefer_last", True) as shown in the earlier diff.

Also applies to: 721-724


711-789: De-dup shared utilities to reduce drift.

line_col_from_index appears in multiple branches. Consider a single helper (module-level) to avoid subtle off-by-one inconsistencies.


70-80: Narrow exception handling around regex operations.

Catching Exception masks unrelated issues. Prefer re.error for compile failures; use explicit guards elsewhere. Optional given structured error shaping.

Also applies to: 620-629, 757-765

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

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

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 264b585 and 064dc29.

📒 Files selected for processing (2)
  • UnityMcpBridge/Editor/Tools/ManageAsset.cs (6 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (15 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • UnityMcpBridge/Editor/Tools/ManageAsset.cs
🧰 Additional context used
🧬 Code graph analysis (1)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (1)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (388-407)
🪛 Ruff (0.12.2)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py

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

(TRY003)


672-672: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (6)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (6)

5-5: No action — benign import.

Leaving import os is harmless if you plan to use it soon. If not, consider removing in a later cleanup.


207-207: Confirm structural validation handoff.

With _is_structurally_balanced removed, ensure Unity’s manage_script validators run for both structured and text paths (including atomic text batches) under options.validate='standard'.


537-539: Good default: immediate refresh for structured edits.

This reduces missed reloads when the Editor is unfocused.


666-667: Routing/options hygiene looks solid.

Atomic apply for multi-span text edits, debounced refresh on text paths, and precondition SHA usage are all appropriate.

Also applies to: 802-805, 861-862, 886-887


549-551: Replace deprecated sentinel with direct compilation trigger – verify reload reliability
Structured/text edits now invoke UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation() instead of flipping the sentinel. Confirm that this consistently forces Unity to recompile/reload in the Editor, and consider preserving the execute_menu_item("MCP/Flip Reload Sentinel") path behind a feature flag as a fallback if needed.


38-52: Ignore incorrect default-position inconsistency suggestion.
All anchor_insert calls use the same code path in manage_script_edits.py, where the default position is "before". There is no alternate “after” default in any server or resource‐tools implementation.

Likely an incorrect or invalid review comment.

@dsarno dsarno closed this Sep 5, 2025
@dsarno dsarno deleted the fix/brace-validation-improvements branch September 5, 2025 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants