Skip to content

Conversation

@dsarno
Copy link
Collaborator

@dsarno dsarno commented Dec 29, 2025

What changed

This PR hardens MCP-for-Unity against large-response crashes/timeouts by making “paged + summary-first” the default pattern and by guiding (and correcting) common LLM usage.

  • manage_scene(get_hierarchy): default page_size lowered to 50 (items per page), keeping payloads bounded when callers omit paging params.
  • Server handshake instructions: added explicit payload sizing & paging guidance for:
    • manage_scene(get_hierarchy)
    • manage_gameobject(get_components) (metadata-first; small pages for include_properties=true)
    • manage_asset(search) (page results; avoid previews)
  • manage_asset(search) tool schema: updated parameter docs to steer models toward search_pattern + paging and away from thumbnails.
  • Guardrail for common LLM mistake: server now normalizes manage_asset(search) calls like path="t:MonoScript" into a safer server-side filtered search (search_pattern="t:MonoScript", path="Assets"), preventing “invalid folder ⇒ search entire project” behavior.
  • Test stability: CodexConfigHelperTests now force DevModeForceServerRefresh=false in setup to avoid arg-order failures when dev mode is enabled in the editor.

Why

We’ve seen Unity freeze/crash risk from single-shot, high-payload responses (deep hierarchies, component properties), and token blowups from broad asset searches. The goal is to reduce peak single-response size and make safe behavior the default for naive agents.

A/B validation (same MCPStressTest scene)

Using the same scene asset (Assets/Scenes/MCPStressTest.unity/MCPStressTest.unity in UnityMCPTests), we captured raw JSON responses and measured bytes (wc -c):

A (old, single-shot)

  • get_hierarchy: 30,715 bytes
  • get_components(Heavy_00): 14,139 bytes
  • Peak A: 30,715 bytes

B (new, paged + summary-first)

  • get_hierarchy(page_size=50): 3,825 bytes
  • children paging spot-check (StressRoot_00, page_size=10): 2,530 bytes
  • get_components metadata-only pages (page_size=10): 767 / 769 / 596 bytes
  • get_components properties-on-demand pages (include_properties=true, page_size=3): 1,876 / 1,998 bytes
  • Peak B (captured): 3,825 bytes

Result: peak single-response size dropped from 30,715 → 3,825 bytes (~8× smaller), with include_properties=true staying bounded under ~2KB per page in the test.

Testing

  • Unity EditMode tests: fixed 4 failing CodexConfigHelperTests cases; rerun via MCP: 4/4 passed.
  • Server sanity: python3 -m compileall ok; server unit tests (non-integration) passed (83 passed, 2 skipped, 7 xpassed).

Notes / Follow-ups

  • manage_asset(search) is now more forgiving for LLMs, but callers should still prefer:
    • path="Assets", search_pattern="t:MonoScript", page_size=25, page_number=1, generate_preview=false
  • Untracked stress assets used during local testing were intentionally not committed.

Should fix #455 #488 #377

Summary by Sourcery

Introduce payload-safe paging defaults and dev-mode refresh controls across Unity MCP hierarchy, component, and asset tooling to reduce large-response risks and improve diagnostics.

New Features:

  • Add paged, summary-first hierarchy retrieval for manage_scene(get_hierarchy) with optional parent and transform controls.
  • Add paging and metadata-first responses for manage_gameobject(get_components), with optional properties-on-demand support.
  • Expose dev-mode 'force fresh server install' toggle that injects --no-cache/--refresh into uvx- and CLI-based server launch commands.
  • Extend debug_request_context to return server diagnostics including version, working directory, and argv.

Bug Fixes:

  • Normalize unsafe manage_asset(search) usages by converting path-based AssetDatabase queries into bounded search_pattern+path combinations and mapping asset_type into filter_type to avoid project-wide scans.
  • Harden local HTTP server shutdown to avoid killing the Unity Editor or unrelated processes by filtering candidate PIDs and using graceful termination first.
  • Stabilize CodexConfigHelper tests by forcing deterministic dev-mode prefs and restoring original EditorPrefs, and reduce flakiness in scriptable object tests by waiting for Unity to finish compiling/updating before assertions.

Enhancements:

  • Document payload sizing, paging defaults, and recommended usage patterns for hierarchy and component tools in developer docs and server startup guidance.
  • Refine manage_asset, manage_scene, manage_gameobject, read_console, and run_tests tool schemas to share common coercion helpers and provide clearer parameter guidance for paging and payload safety.
  • Improve manage_scene and manage_gameobject server-side parameter coercion and add tests to ensure paging-related params pass through correctly.
  • Enhance process discovery for local HTTP server shutdown using lsof/ps/tasklist with safer filtering of listening processes.
  • Include server version pin to fastmcp 2.14.1 in the Python package configuration.

Documentation:

  • Update developer documentation and settings UI text to describe dev-mode refresh behavior and recommended paging strategies for large Unity queries.

Tests:

  • Add EditMode tests for scene hierarchy paging, component paging/metadata behavior, and Unity readiness waits, plus integration tests covering paging params pass-through, tool signatures, and debug_request_context diagnostics.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Dev Mode toggle to force fresh server installations for reliable iterative development
    • Implemented pagination for scene hierarchies and component retrieval with safety limits
    • Added optional component property inclusion with configurable boundaries
  • Bug Fixes

    • Improved server process termination to safely handle multiple listening processes
  • Documentation

    • Added payload sizing and pagination strategy guidance with recommended defaults

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

CodexConfigHelperTests was calling MCPServiceLocator.Reset() in TearDown, which disposes the active bridge/transport during MCP-driven test runs. Replace with restoring only the mutated service (IPlatformService).
Capture the original IPlatformService before this fixture runs and restore it in TearDown. This preserves the MCP connection safety fix (no MCPServiceLocator.Reset()) while avoiding global state leakage to subsequent tests.
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 29, 2025

Reviewer's Guide

Implements payload-safe paging and metadata-first responses for hierarchy and component queries, hardens asset search and process management, adds dev-mode cache-busting controls, centralizes Python-side coercion helpers, and updates tests/docs accordingly.

Sequence diagram for paged manage_scene get_hierarchy

sequenceDiagram
    actor LLMClient
    participant MCPServerPython as MCPServerPython
    participant ManageScenePy as manage_scene_py
    participant UnityTransport as UnityTransport
    participant ManageSceneCs as ManageScene_cs

    LLMClient->>MCPServerPython: manage_scene(action=get_hierarchy, parent, page_size, cursor, ...)
    MCPServerPython->>ManageScenePy: manage_scene(action, name, build_index, screenshot_file_name,
    MCPServerPython-->>ManageScenePy: screenshot_super_size, parent, page_size, cursor,
    MCPServerPython-->>ManageScenePy: max_nodes, max_depth, max_children_per_node, include_transform)

    ManageScenePy->>ManageScenePy: coerce_int/coerce_bool on paging params
    ManageScenePy->>UnityTransport: send_with_unity_instance(async_send_command_with_retry,
    ManageScenePy-->>UnityTransport: "manage_scene", params{action,get_hierarchy+paging}

    UnityTransport->>ManageSceneCs: HandleCommand(params)
    ManageSceneCs->>ManageSceneCs: ToSceneCommand(JObject)
    ManageSceneCs->>ManageSceneCs: GetSceneHierarchyPaged(SceneCommand)
    ManageSceneCs->>ManageSceneCs: ResolveGameObject(parent)
    alt parent is null
        ManageSceneCs->>ManageSceneCs: nodes = activeScene.GetRootGameObjects()
    else parent found
        ManageSceneCs->>ManageSceneCs: nodes = parent.children
    else parent not found
        ManageSceneCs-->>UnityTransport: ErrorResponse("Parent GameObject not found")
        UnityTransport-->>ManageScenePy: error
        ManageScenePy-->>MCPServerPython: error
        MCPServerPython-->>LLMClient: error
        ManageSceneCs->>ManageSceneCs: return
    end

    ManageSceneCs->>ManageSceneCs: clamp pageSize,maxNodes,maxChildrenPerNode
    ManageSceneCs->>ManageSceneCs: slice nodes[cursor:cursor+effectiveTake]
    loop for each node in page
        ManageSceneCs->>ManageSceneCs: BuildGameObjectSummary(go, includeTransform,...)
    end
    ManageSceneCs->>ManageSceneCs: compute next_cursor or null
    ManageSceneCs-->>UnityTransport: SuccessResponse{scope,cursor,pageSize,next_cursor,truncated,total,items}

    UnityTransport-->>ManageScenePy: response
    ManageScenePy-->>MCPServerPython: response
    MCPServerPython-->>LLMClient: paged hierarchy summary
    LLMClient->>MCPServerPython: repeat with cursor=next_cursor until null
Loading

Sequence diagram for paged manage_gameobject get_components with metadata-first behavior

sequenceDiagram
    actor LLMClient
    participant MCPServerPython as MCPServerPython
    participant ManageGameObjectPy as manage_gameobject_py
    participant UnityTransport as UnityTransport
    participant ManageGameObjectCs as ManageGameObject_cs

    LLMClient->>MCPServerPython: manage_gameobject(action=get_components,
    LLMClient-->>MCPServerPython: target,page_size,cursor,max_components,
    LLMClient-->>MCPServerPython: include_properties=false)

    MCPServerPython->>ManageGameObjectPy: manage_gameobject(...)
    ManageGameObjectPy->>ManageGameObjectPy: coerce_int page_size,cursor,max_components
    ManageGameObjectPy->>ManageGameObjectPy: coerce_bool include_properties
    ManageGameObjectPy->>UnityTransport: send_with_unity_instance(async_send_command_with_retry,
    ManageGameObjectPy-->>UnityTransport: "manage_gameobject", params{action=get_components+paging}

    UnityTransport->>ManageGameObjectCs: HandleCommand(params)
    ManageGameObjectCs->>ManageGameObjectCs: GetComponentsFromTarget(target,
    ManageGameObjectCs-->>ManageGameObjectCs: searchMethod,includeNonPublicSerialized,
    ManageGameObjectCs-->>ManageGameObjectCs: pageSize,cursor,maxComponents,includeProperties=false)

    ManageGameObjectCs->>ManageGameObjectCs: clamp pageSize,maxComponents
    ManageGameObjectCs->>ManageGameObjectCs: components = targetGo.GetComponents()
    ManageGameObjectCs->>ManageGameObjectCs: slice components[cursor:cursor+effectiveTake]

    loop for each component in page
        alt include_properties == false
            ManageGameObjectCs->>ManageGameObjectCs: BuildComponentMetadata(Component)
        else include_properties == true
            ManageGameObjectCs->>ManageGameObjectCs: serialize component via GameObjectSerializer
            ManageGameObjectCs->>ManageGameObjectCs: enforce maxPayloadChars budget
        end
    end

    ManageGameObjectCs->>ManageGameObjectCs: compute next_cursor or null
    ManageGameObjectCs-->>UnityTransport: SuccessResponse{cursor,pageSize,next_cursor,truncated,total,includeProperties,items}

    UnityTransport-->>ManageGameObjectPy: response
    ManageGameObjectPy-->>MCPServerPython: response
    MCPServerPython-->>LLMClient: paged component metadata
    LLMClient->>MCPServerPython: repeat with cursor=next_cursor
    Note over LLMClient,MCPServerPython: When full properties needed, set include_properties=true with smaller page_size
Loading

Updated class diagram for hierarchy, component paging, and dev-mode controls

classDiagram
    class SceneCommand {
        string action
        string name
        string path
        int? buildIndex
        string fileName
        int? superSize
        JToken parent
        int? pageSize
        int? cursor
        int? maxNodes
        int? maxDepth
        int? maxChildrenPerNode
        bool? includeTransform
    }

    class ManageScene_cs {
        +object HandleCommand(JObject params)
        -object GetSceneHierarchyPaged(SceneCommand cmd)
        -GameObject ResolveGameObject(JToken targetToken, Scene activeScene)
        -object BuildGameObjectSummary(GameObject go, bool includeTransform, int maxChildrenPerNode)
        -string GetGameObjectPath(GameObject go)
    }

    class ManageGameObject_cs {
        +object HandleCommand(JObject params)
        -object GetComponentsFromTarget(string target, string searchMethod, bool includeNonPublicSerialized, int pageSize, int cursor, int maxComponents, bool includeProperties)
        -object BuildComponentMetadata(Component c)
    }

    class ServerManagementService {
        +bool StopLocalHttpServer()
        -List~int~ GetListeningProcessIdsForPort(int port)
        -static int GetCurrentProcessIdSafe()
        -bool LooksLikeMcpServerProcess(int pid)
        -bool TerminateProcess(int pid)
        +bool TryGetLocalHttpServerCommand(out string command, out string error)
    }

    class CodexConfigHelper {
        +string BuildCodexServerBlock(string uvPath)
        +TomlTable CreateUnityMcpTable(string uvPath)
        -static bool GetDevModeForceRefresh()
        -static void AddDevModeArgs(TomlArray args, bool devForceRefresh)
    }

    class ConfigJsonBuilder {
        +void PopulateUnityNode(JObject unity, string uvPath, McpClient client)
        -static IList~string~ BuildUvxArgs(string fromUrl, string packageName, bool devForceRefresh)
    }

    class McpClientConfiguratorBase {
        -void Register()
        +string GetManualSnippet()
        -static bool GetDevModeForceRefresh()
    }

    class McpSettingsSection {
        -Toggle devModeForceRefreshToggle
        -void CacheUIElements()
        -void InitializeUI()
        -void RegisterCallbacks()
        +void UpdatePathOverrides()
    }

    class EditorPrefKeys {
        <<static>>
        +string DevModeForceServerRefresh
    }

    class manage_scene_py {
        +async_manage_scene(ctx, action, name, build_index, screenshot_file_name, screenshot_super_size, parent, page_size, cursor, max_nodes, max_depth, max_children_per_node, include_transform) dict
    }

    class manage_gameobject_py {
        +async_manage_gameobject(ctx, action, target, search_in_children, search_inactive, includeNonPublicSerialized, page_size, cursor, max_components, include_properties, new_name, offset, component_name, component_properties) dict
    }

    class manage_asset_py {
        +async_manage_asset(ctx, action, path, asset_type, properties, destination, generate_preview, search_pattern, filter_type, filter_date_after, page_size, page_number) dict
    }

    class utils_py {
        +Any parse_json_payload(Any value)
        +int~nullable~ coerce_int(Any value, int~nullable~ default)
    }

    SceneCommand <.. ManageScene_cs
    ManageScene_cs <.. manage_scene_py
    ManageGameObject_cs <.. manage_gameobject_py
    manage_asset_py ..> utils_py
    manage_scene_py ..> utils_py
    manage_gameobject_py ..> utils_py

    CodexConfigHelper ..> EditorPrefKeys
    ConfigJsonBuilder ..> EditorPrefKeys
    McpClientConfiguratorBase ..> EditorPrefKeys
    McpSettingsSection ..> EditorPrefKeys
    McpSettingsSection ..> CodexConfigHelper

    ServerManagementService ..> EditorPrefKeys
Loading

Flow diagram for manage_asset search normalization and paging safety

flowchart TD
    A_start[Start manage_asset
    action=search] --> B_coerce[Coerce page_size,page_number
    using coerce_int]
    B_coerce --> C_pathRaw[Read raw path string]

    C_pathRaw --> D_checkQuery{search_pattern is empty
    AND path starts with t:}
    D_checkQuery -->|Yes| E_normalizePath[Set search_pattern = path
    Set path = Assets
    Emit ctx.info normalization]
    D_checkQuery -->|No| F_skipPath[Keep original path/search_pattern]

    E_normalizePath --> G_filterType
    F_skipPath --> G_filterType

    G_filterType{filter_type is empty
    AND asset_type is non-empty string} -->|Yes| H_mapFilter[Set filter_type = asset_type
    Emit ctx.info mapping]
    G_filterType -->|No| I_keepFilter[Keep filter_type]

    H_mapFilter --> J_buildParams
    I_keepFilter --> J_buildParams

    J_buildParams[Build params_dict with
    action,path,search_pattern,
    filter_type,page_size,page_number,
    generate_preview, etc.] --> K_sendUnity[send_with_unity_instance
    async_send_command_with_retry
    manage_asset C# handler]
    K_sendUnity --> L_end[End]
Loading

File-Level Changes

Change Details Files
Add paged, summary-first scene hierarchy API with parent/child paging and safety bounds.
  • Extend scene command parameters to accept paging and safety fields (parent, page_size, cursor, max_nodes, max_depth, max_children_per_node, include_transform).
  • Replace recursive GetSceneHierarchy with GetSceneHierarchyPaged that pages roots or children, enforces conservative limits, and returns a cursor-based payload.
  • Add helpers to resolve GameObjects from flexible identifiers and to build lightweight GameObject summaries with optional transform data.
  • Introduce EditMode tests to validate root and children paging behavior and cursors.
MCPForUnity/Editor/Tools/ManageScene.cs
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageSceneHierarchyPagingTests.cs
Make get_components paged and metadata-first with optional bounded property serialization.
  • Update manage_gameobject C# handler to parse paging and includeProperties parameters with defensive coercion.
  • Refactor GetComponentsFromTarget to page over components, cap max components and payload size, and return a structured paging envelope with optional serialized properties.
  • Add BuildComponentMetadata helper for lightweight component summaries and new tests covering default metadata paging and includeProperties=true behavior.
  • Plumb paging/include_properties parameters through Python manage_gameobject tool and verify pass-through via integration tests and signature checks.
MCPForUnity/Editor/Tools/ManageGameObject.cs
Server/src/services/tools/manage_gameobject.py
Server/tests/integration/test_manage_gameobject_param_coercion.py
Server/tests/integration/test_tool_signatures_paging.py
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs
Harden asset search against broad-scoped queries and improve tool schema guidance.
  • Update manage_asset tool description and parameter docs to recommend paging, discourage previews, and steer queries toward search_pattern+Assets.
  • Normalize common LLM mistakes by mapping path values like 't:MonoScript' into search_pattern with path='Assets' and mapping asset_type into filter_type for searches.
  • Use shared coerce_int helper for pagination parameters.
Server/src/services/tools/manage_asset.py
Server/src/services/tools/utils.py
Add explicit paging/safety parameters to manage_scene Python tool and centralize coercion utilities.
  • Extend manage_scene tool signature with parent/page_size/cursor/max_nodes/max_depth/max_children_per_node/include_transform and pass them through to C#.
  • Replace local int/bool coercion logic with shared coerce_int/coerce_bool helpers.
  • Add integration tests to ensure paging params are in signatures and passed through correctly.
Server/src/services/tools/manage_scene.py
Server/src/services/tools/utils.py
Server/tests/integration/test_manage_scene_paging_params.py
Server/tests/integration/test_tool_signatures_paging.py
Improve server management safety and dev-mode cache-busting behavior in the Unity editor and client config.
  • Change port-stopping logic to enumerate all listeners, avoid killing the Unity editor, and only terminate processes that look like uv/uvx/python-based MCP servers using lsof/netstat/ps/tasklist heuristics.
  • Replace KillProcess with TerminateProcess that prefers graceful termination before force-kill and works cross-platform.
  • Honor a new DevModeForceServerRefresh EditorPref when constructing uvx commands for HTTP server start, Codex config, generic config JSON, and Claude MCP client registration, inserting --no-cache --refresh flags when enabled.
  • Expose a Dev Mode toggle in the MCP settings UI and persist it via EditorPrefs, and document the behavior in README-DEV along with paging defaults.
  • Stabilize CodexConfigHelper tests by forcing DevModeForceServerRefresh=false in setup/teardown and restoring original prefs, and ensure platform service is restored correctly.
MCPForUnity/Editor/Services/ServerManagementService.cs
MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs
MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
MCPForUnity/Editor/Constants/EditorPrefKeys.cs
MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.cs
MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.uxml
docs/README-DEV.md
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/CodexConfigHelperTests.cs
Stabilize tests and utilities and enrich diagnostics on the Python server side.
  • Update ManageScriptableObject EditMode tests to wait for Unity to finish compiling/updating before running assertions to avoid transient 'compiling_or_reloading' states.
  • Add debug_request_context server diagnostics (version, cwd, argv) and corresponding tests.
  • Refactor read_console and run_tests tools to use shared coerce_bool/coerce_int helpers instead of local implementations.
  • Bump fastmcp dependency to 2.14.1 and refresh uv.lock.
  • Add tests ensuring debug_request_context includes server diagnostics.
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptableObjectTests.cs
Server/src/services/tools/debug_request_context.py
Server/tests/integration/test_debug_request_context_diagnostics.py
Server/src/services/tools/read_console.py
Server/src/services/tools/run_tests.py
Server/src/services/tools/utils.py
Server/pyproject.toml
Server/uv.lock

Assessment against linked issues

Issue Objective Addressed Explanation
#455 Prevent Unity 6.3 LTS from crashing when calling manage_gameobject with action="get_components" (by making the response size/pattern safe).

Possibly linked issues

  • #: PR centralizes int/bool coercion and updates manage_gameobject, manage_asset, read_console, and manage_scene to fix string-typed parameters.

Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 29, 2025

📝 Walkthrough

Walkthrough

This PR implements three major features: a dev-mode force-refresh mechanism for MCP server installation caching to enable faster iteration, safer multi-process termination logic to avoid killing the Unity Editor, and pagination support for scene hierarchy and component retrieval with configurable safety limits and optional property inclusion.

Changes

Cohort / File(s) Summary
Dev Mode Force Refresh Configuration
MCPForUnity/Editor/Constants/EditorPrefKeys.cs,
MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.cs,
MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.uxml
Added new EditorPrefs key DevModeForceServerRefresh and wired a toggle UI control (dev-mode-force-refresh-toggle) in Advanced Settings that persists state and triggers server refresh on change.
Dev Mode Command Building
MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs,
MCPForUnity/Editor/Helpers/CodexConfigHelper.cs,
MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs,
MCPForUnity/Editor/Services/ServerManagementService.cs
Introduced GetDevModeForceRefresh() helper and threaded devFlags (--no-cache --refresh) into stdio transport commands via register/configure methods, affecting manual snippet generation and uvx argument construction.
Multi-PID Server Termination
MCPForUnity/Editor/Services/ServerManagementService.cs
Replaced single-PID stop logic with GetListeningProcessIdsForPort() returning multiple PIDs, added LooksLikeMcpServerProcess() heuristic filtering to skip non-MCP processes, GetCurrentProcessIdSafe() to avoid killing the editor, and TerminateProcess() with graceful then forceful termination paths.
Scene Pagination (Editor)
MCPForUnity/Editor/Tools/ManageScene.cs
Added GetSceneHierarchyPaged() with configurable pageSize, cursor, maxNodes, maxChildrenPerNode bounds; introduced ResolveGameObject(), BuildGameObjectSummary(), and GetGameObjectPath() helpers; returns structured payload with scope, next_cursor, truncated, and items.
Component Pagination (Editor)
MCPForUnity/Editor/Tools/ManageGameObject.cs
Extended GetComponentsFromTarget() with pageSize, cursor, maxComponents, includeProperties parameters; added BuildComponentMetadata() for lightweight metadata emission; returns paginated response with paging metadata and items list.
Server Tool Paging Parameters
Server/src/services/tools/manage_scene.py,
Server/src/services/tools/manage_gameobject.py
Added paging/safety parameters (parent, page_size, cursor, max_nodes, max_depth, max_children_per_node, include_transform for manage_scene; page_size, cursor, max_components, include_properties for manage_gameobject) with coercion and conditional payload inclusion.
Utility Functions & Coercion
Server/src/services/tools/utils.py,
Server/src/services/tools/read_console.py,
Server/src/services/tools/run_tests.py
Added public coerce_int() helper for safe numeric parsing with defaults; refactored existing tools to use centralized coerce_int and coerce_bool instead of local implementations.
Asset Tool Enhancements
Server/src/services/tools/manage_asset.py
Enhanced descriptions with payload-safety guidance; normalized search actions (t: query → search_pattern); mapped asset_type to filter_type; added coercion of page_size and page_number to integers.
Server Initialization & Diagnostics
Server/src/main.py,
Server/src/services/tools/debug_request_context.py
Added startup workaround exposing typing names to builtins to prevent NameError during dynamic annotation evaluation; extended debug_request_context with server field containing version, cwd, and argv.
Test Coverage
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs,
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageSceneHierarchyPagingTests.cs,
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptableObjectTests.cs,
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/CodexConfigHelperTests.cs,
Server/tests/integration/test_*_paging*.py
Added new tests validating paging parameter pass-through, parameter coercion, debug context diagnostics, scene hierarchy pagination with cursor flow, and component retrieval with/without properties.
Server Dependency & Docs
Server/pyproject.toml,
docs/README-DEV.md
Pinned fastmcp to exact version ==2.14.1; documented dev-mode toggle, paging defaults, safety parameters, and recommended pagination strategies with examples.

Sequence Diagrams

sequenceDiagram
    participant Client as MCP Client
    participant Server as MCP Server<br/>(Python)
    participant EditorService as Editor Service<br/>(Unity)
    participant Database as Scene/Assets<br/>Database
    
    rect rgba(100, 150, 200, 0.2)
    note over Client,Server: Get Hierarchy with Paging
    Client->>Server: manage_scene(action: get_hierarchy,<br/>pageSize: 10, cursor: 0)
    Server->>EditorService: HandleCommand(get_hierarchy,<br/>pageSize, cursor, max_nodes)
    EditorService->>Database: GetSceneHierarchyPaged()
    Database->>EditorService: [Page of GameObjects]
    EditorService->>EditorService: BuildGameObjectSummary<br/>for each item
    EditorService->>Server: {items: [...], next_cursor, truncated}
    Server->>Client: ✓ {items, next_cursor, truncated,<br/>pageSize, total}
    end
    
    rect rgba(150, 100, 200, 0.2)
    note over Client,Server: Retrieve Next Page via Cursor
    Client->>Server: manage_scene(action: get_hierarchy,<br/>pageSize: 10, cursor: <next_cursor>)
    Server->>EditorService: HandleCommand(get_hierarchy,<br/>pageSize, cursor)
    EditorService->>Database: GetSceneHierarchyPaged()
    Database->>EditorService: [Next Page of GameObjects]
    EditorService->>Server: {items: [...], next_cursor, truncated}
    Server->>Client: ✓ {items, next_cursor, truncated}
    end
    
    rect rgba(200, 150, 100, 0.2)
    note over Client,Server: Get Components with Properties
    Client->>Server: manage_gameobject(action: get_components,<br/>target: "Player", includeProperties: true)
    Server->>EditorService: HandleCommand(get_components,<br/>pageSize, includeProperties)
    EditorService->>Database: GetComponentsFromTarget()
    Database->>EditorService: [Components]
    EditorService->>EditorService: BuildComponentMetadata<br/>+ properties (if enabled)
    EditorService->>Server: {items: [...], truncated}
    Server->>Client: ✓ {items, pageSize, total, truncated}
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • justinpbarnett

Poem

🐰 Hops excitedly while organizing papers

Fresh server installs now dash with delight,
Pagination keeps payloads lean and light,
Multiple PIDs no longer confuse,
The editor stays safe—no processes to lose!
With cursors and bounds, the data flows right. 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.43% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: payload-safe paging for hierarchy/components, safer asset search, and documentation updates.
Linked Issues check ✅ Passed The PR implements the core requirements from #455: prevents get_components crashes through pagination with safe defaults, disambiguates tool usage with clear schema guidance, and makes server behavior forgiving of problematic parameters.
Out of Scope Changes check ✅ Passed All changes align with stated objectives: paging implementation, asset search normalization, payload sizing defaults, and supporting infrastructure (EditorPrefs keys, utility functions, tests). No unrelated changes detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❤️ Share

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

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • Several Python tools (manage_scene, manage_gameobject, read_console, run_tests) now call coerce_int/coerce_bool; double‑check that each module imports these from services.tools.utils to avoid runtime NameErrors.
  • In GetComponentsFromTarget, the payload budgeting for includeProperties=true calls JToken.FromObject(...).ToString(...) for every component, which double‑serializes each item; consider a cheaper heuristic (e.g., capping item count or using a rough size estimate) to reduce per‑call overhead.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Several Python tools (`manage_scene`, `manage_gameobject`, `read_console`, `run_tests`) now call `coerce_int`/`coerce_bool`; double‑check that each module imports these from `services.tools.utils` to avoid runtime `NameError`s.
- In `GetComponentsFromTarget`, the payload budgeting for `includeProperties=true` calls `JToken.FromObject(...).ToString(...)` for every component, which double‑serializes each item; consider a cheaper heuristic (e.g., capping item count or using a rough size estimate) to reduce per‑call overhead.

## Individual Comments

### Comment 1
<location> `MCPForUnity/Editor/Tools/ManageScene.cs:577-586` </location>
<code_context>
+        private static GameObject ResolveGameObject(JToken targetToken, Scene activeScene)
</code_context>

<issue_to_address>
**suggestion (bug_risk):** GameObject.Find is not scoped to the provided Scene and may return objects from other loaded scenes.

In the path-based branch, `GameObject.Find(s)` ignores `activeScene` and searches all loaded scenes, so a path meant for `activeScene` could resolve to a same-path object in another scene. If you want to keep lookups scoped, consider traversing `activeScene.GetRootGameObjects()` (as in your name-based search) and only use `GameObject.Find` when cross-scene lookup is explicitly desired.

Suggested implementation:

```csharp
        private static GameObject FindInSceneByPath(Scene scene, string path)
        {
            if (!scene.IsValid() || !scene.isLoaded || string.IsNullOrEmpty(path))
                return null;

            // Normalize to avoid leading/trailing slashes interfering with segment names
            var trimmed = path.Trim().Trim('/');
            if (string.IsNullOrEmpty(trimmed))
                return null;

            var segments = trimmed.Split('/');
            if (segments.Length == 0)
                return null;

            // First segment must match a root GameObject in this scene
            var roots = scene.GetRootGameObjects();
            GameObject current = null;

            foreach (var root in roots)
            {
                if (root != null && root.name == segments[0])
                {
                    current = root;
                    break;
                }
            }

            if (current == null)
                return null;

            // Walk down the hierarchy using Transform.Find scoped under the matched root
            for (var i = 1; i < segments.Length && current != null; i++)
            {
                var childTransform = current.transform.Find(segments[i]);
                current = childTransform != null ? childTransform.gameObject : null;
            }

            return current;
        }

        private static GameObject ResolveGameObject(JToken targetToken, Scene activeScene)

```

Inside `ResolveGameObject`, locate the branch that handles string/“path-based” targets (the one currently using `GameObject.Find(...)` to resolve a path string). Replace those `GameObject.Find(pathString)` calls with `FindInSceneByPath(activeScene, pathString)` to keep lookups scoped to `activeScene`. If you truly need cross-scene lookup in some cases, keep `GameObject.Find` only in those explicit branches (e.g., guarded by a flag or a specific token marker indicating cross-scene resolution).
</issue_to_address>

### Comment 2
<location> `MCPForUnity/Editor/Services/ServerManagementService.cs:282-287` </location>
<code_context>
         }

-        private int GetProcessIdForPort(int port)
+        private List<int> GetListeningProcessIdsForPort(int port)
         {
+            var results = new List<int>();
</code_context>

<issue_to_address>
**suggestion (bug_risk):** The netstat-based PID parsing on Windows doesn’t distinguish LISTENING entries from client connections.

On Windows, `netstat -ano | findstr :{port}` returns both LISTENING and ESTABLISHED (and other) entries. The current logic takes the PID from any matching line, so it can capture client processes instead of the actual listener, even though `LooksLikeMcpServerProcess` reduces this somewhat. Please restrict PID collection to LISTENING lines only (e.g., filter by state in code or add `findstr LISTENING`), so you only return PIDs for listening sockets on that port.

Suggested implementation:

```csharp
                                var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                                // On Windows, netstat output includes multiple states (LISTENING, ESTABLISHED, etc.).
                                // We only want PIDs for LISTENING sockets on the given port.
                                if (parts.Length > 3
                                    && parts[3].Equals("LISTENING", StringComparison.OrdinalIgnoreCase)
                                    && int.TryParse(parts[parts.Length - 1], out int pid))
                                {
                                    results.Add(pid);
                                }
                            }

```

If the netstat invocation is constructed elsewhere (e.g., as `"netstat -ano | findstr :{port}"`), you could further harden this by adding a `LISTENING` filter at the shell level, such as `"netstat -ano | findstr :{port} | findstr LISTENING"`. That change would need to be applied where the process start info/command line for netstat is built.
</issue_to_address>

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

Comment on lines +282 to 287
private List<int> GetListeningProcessIdsForPort(int port)
{
var results = new List<int>();
try
{
string stdout, stderr;
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): The netstat-based PID parsing on Windows doesn’t distinguish LISTENING entries from client connections.

On Windows, netstat -ano | findstr :{port} returns both LISTENING and ESTABLISHED (and other) entries. The current logic takes the PID from any matching line, so it can capture client processes instead of the actual listener, even though LooksLikeMcpServerProcess reduces this somewhat. Please restrict PID collection to LISTENING lines only (e.g., filter by state in code or add findstr LISTENING), so you only return PIDs for listening sockets on that port.

Suggested implementation:

                                var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                                // On Windows, netstat output includes multiple states (LISTENING, ESTABLISHED, etc.).
                                // We only want PIDs for LISTENING sockets on the given port.
                                if (parts.Length > 3
                                    && parts[3].Equals("LISTENING", StringComparison.OrdinalIgnoreCase)
                                    && int.TryParse(parts[parts.Length - 1], out int pid))
                                {
                                    results.Add(pid);
                                }
                            }

If the netstat invocation is constructed elsewhere (e.g., as "netstat -ano | findstr :{port}"), you could further harden this by adding a LISTENING filter at the shell level, such as "netstat -ano | findstr :{port} | findstr LISTENING". That change would need to be applied where the process start info/command line for netstat is built.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (10)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptableObjectTests.cs (1)

311-325: Consider Unity-idiomatic alternatives to Thread.Sleep polling.

The busy-wait pattern with Thread.Sleep(50) is functional and appropriate for test code, but Unity Editor callbacks might be more idiomatic. The current implementation successfully addresses the test determinism issues mentioned in the PR objectives.

Alternative approach using EditorApplication.update

If you want to avoid blocking the main thread with Thread.Sleep, consider using EditorApplication.update callbacks:

private static void WaitForUnityReady(double timeoutSeconds = 30.0)
{
    double start = EditorApplication.timeSinceStartup;
    bool ready = false;
    
    System.Action checkReady = null;
    checkReady = () =>
    {
        if (!EditorApplication.isCompiling && !EditorApplication.isUpdating)
        {
            ready = true;
            EditorApplication.update -= checkReady;
        }
        else if (EditorApplication.timeSinceStartup - start > timeoutSeconds)
        {
            EditorApplication.update -= checkReady;
            Assert.Fail($"Timed out waiting for Unity to finish compiling/updating (>{timeoutSeconds:0.0}s).");
        }
    };
    
    EditorApplication.update += checkReady;
    
    // Wait for callback to complete
    while (!ready && EditorApplication.timeSinceStartup - start <= timeoutSeconds)
    {
        Thread.Sleep(10); // Brief sleep to yield
    }
}

However, the current polling approach is simpler and works well for test scenarios.

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

72-106: Test logic is correct, but consider adding a clarity comment.

The test adds 2 components (Rigidbody, BoxCollider) to a GameObject that already has a Transform component (3 total). With pageSize=2, the assertion at line 99 expects exactly 2 items, which correctly validates paging behavior. However, this implicit assumption about the Transform component could be made more explicit with a comment.

Optional: Add comment clarifying total component count
         // Arrange
         testGameObject.AddComponent<Rigidbody>();
         testGameObject.AddComponent<BoxCollider>();
+        // Note: GameObject already has Transform, so total is 3 components

108-145: Consider strengthening the properties assertion.

Line 144 uses a heuristic (Properties().Count() > 2) to verify that properties are included. This could be fragile if component serialization changes. A more robust approach would be to directly check for a "properties" key in the first item.

Recommended: Check for properties key directly
-            // Heuristic: property-including payload should have more than just typeName/instanceID.
-            Assert.Greater(first.Properties().Count(), 2, "Expected richer component payload when includeProperties=true.");
+            // When includeProperties=true, the component should include a "properties" field.
+            Assert.IsNotNull(first["properties"], "Expected 'properties' field when includeProperties=true.");
docs/README-DEV.md (1)

172-206: LGTM! Comprehensive paging documentation.

The payload sizing and paging defaults are well-documented with clear practical recommendations. The documentation accurately reflects the implementation (page_size defaults, max limits, include_properties behavior).

Optional: Style improvement for "very large"

Line 174: Static analysis suggests replacing "very large" with a more specific term like "unbounded" or "extremely large" for stronger technical writing. However, "very large" is perfectly clear in this context.

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

1-32: LGTM! Good signature validation tests.

These tests effectively prevent regression by ensuring paging parameters remain in the tool signatures. The approach using inspect.signature is appropriate for integration testing.

Optional: Consider validating parameter types

The current tests only verify parameter presence. For stronger validation, you could also check parameter types and defaults:

def test_manage_scene_signature_includes_paging_params():
    import services.tools.manage_scene as mod
    
    sig = inspect.signature(mod.manage_scene)
    params = sig.parameters
    
    # Check presence and types
    assert "page_size" in params
    assert params["page_size"].annotation in (int | str, "int | str")  # depending on Python version
    assert params["page_size"].default is None

However, this adds maintenance overhead and may be overkill for integration tests.

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

63-77: LGTM! Solid defensive coercion utility.

The coerce_int function correctly handles edge cases (None, booleans, empty strings) and provides sensible fallback behavior. The implementation mirrors the existing coerce_bool pattern, which is good for consistency.

Optional: Narrow exception handling

Line 76: Static analysis suggests avoiding bare Exception. While the defensive catch-all is acceptable here (and consistent with the tolerant design), you could narrow it to specific exceptions for slightly better error visibility:

-    except Exception:
+    except (ValueError, TypeError, AttributeError):
         return default

However, given this is a defensive utility meant to never raise, the current approach is also reasonable.

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

9-9: LGTM! Good refactoring to shared utilities.

Moving from local coercion helpers to centralized utilities in utils.py reduces code duplication and improves maintainability. The explicit default=True for include_stacktrace makes the intent clear.

Also applies to: 42-42, 49-49

MCPForUnity/Editor/Helpers/CodexConfigHelper.cs (1)

9-9: Verify UnityEngine usage.

The using UnityEngine; directive was added, but it's not clearly used in the changed code. Consider removing if unused, or keep if required by UnityEditor dependencies.

MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs (1)

425-429: Dev-mode CLI flags are composed correctly; consider centralizing the prefs helper

The devFlags string ("--no-cache --refresh ") is correctly inserted before --from in both the runtime args and manual snippet, and the GetDevModeForceRefresh helper is defensively implemented around EditorPrefs.

If you find yourself using this flag in more places (Codex helpers already have a similar helper), consider centralizing GetDevModeForceRefresh in a shared utility to avoid drift in behavior, but the current implementation is functionally sound.

Also applies to: 520-551, 553-557

MCPForUnity/Editor/Services/ServerManagementService.cs (1)

345-394: Solid heuristic with good safety defaults.

The explicit rejection of Unity/UnityHub processes (lines 372-375) and the fail-safe return false default are good defensive measures.

Minor: Line 357 has slight redundancy—combined.Contains("uvx") already covers uvx.exe. Consider simplifying:

-return combined.Contains("python") || combined.Contains("uvx") || combined.Contains("uv.exe") || combined.Contains("uvx.exe");
+return combined.Contains("python") || combined.Contains("uvx") || combined.Contains("uv.exe");
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28f60b4 and 9aa02d2.

⛔ Files ignored due to path filters (1)
  • Server/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
  • MCPForUnity/Editor/Constants/EditorPrefKeys.cs
  • MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
  • MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs
  • MCPForUnity/Editor/Services/ServerManagementService.cs
  • MCPForUnity/Editor/Tools/ManageGameObject.cs
  • MCPForUnity/Editor/Tools/ManageScene.cs
  • MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.cs
  • MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.uxml
  • Server/pyproject.toml
  • Server/src/main.py
  • Server/src/services/tools/debug_request_context.py
  • Server/src/services/tools/manage_asset.py
  • Server/src/services/tools/manage_gameobject.py
  • Server/src/services/tools/manage_scene.py
  • Server/src/services/tools/read_console.py
  • Server/src/services/tools/run_tests.py
  • Server/src/services/tools/utils.py
  • Server/tests/integration/test_debug_request_context_diagnostics.py
  • Server/tests/integration/test_manage_gameobject_param_coercion.py
  • Server/tests/integration/test_manage_scene_paging_params.py
  • Server/tests/integration/test_tool_signatures_paging.py
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/CodexConfigHelperTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageSceneHierarchyPagingTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageSceneHierarchyPagingTests.cs.meta
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptableObjectTests.cs
  • docs/README-DEV.md
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-05T16:22:04.960Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 265
File: README.md:204-204
Timestamp: 2025-09-05T16:22:04.960Z
Learning: In the Unity MCP project, the ServerInstaller.cs creates a symlink from ~/Library/AppSupport to ~/Library/Application Support on macOS to mitigate argument parsing and quoting issues in some MCP clients. The README documentation should use the shortened AppSupport path, not the full "Application Support" path with spaces.

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • Server/src/services/tools/run_tests.py
  • docs/README-DEV.md
  • MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
  • Server/src/services/tools/manage_gameobject.py
  • MCPForUnity/Editor/Services/ServerManagementService.cs
📚 Learning: 2025-09-03T16:00:55.839Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 0
File: :0-0
Timestamp: 2025-09-03T16:00:55.839Z
Learning: ComponentResolver in UnityMcpBridge/Editor/Tools/ManageGameObject.cs is a nested static class within ManageGameObject, not a sibling type. The `using static MCPForUnity.Editor.Tools.ManageGameObject;` import is required to access ComponentResolver methods directly without the outer class qualifier.

Applied to files:

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

Applied to files:

  • docs/README-DEV.md
  • MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
  • MCPForUnity/Editor/Services/ServerManagementService.cs
🧬 Code graph analysis (15)
Server/src/services/tools/run_tests.py (1)
Server/src/services/tools/utils.py (1)
  • coerce_int (63-77)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs (1)
MCPForUnity/Editor/Tools/ManageGameObject.cs (1)
  • HandleCommand (42-253)
Server/tests/integration/test_manage_gameobject_param_coercion.py (2)
Server/src/services/tools/manage_gameobject.py (1)
  • manage_gameobject (16-254)
Server/tests/integration/test_helpers.py (1)
  • DummyContext (16-55)
MCPForUnity/Editor/Helpers/CodexConfigHelper.cs (4)
MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs (1)
  • GetDevModeForceRefresh (553-557)
MCPForUnity/Editor/Constants/EditorPrefKeys.cs (1)
  • EditorPrefKeys (7-49)
MCPForUnity/Editor/External/Tommy.cs (1)
  • TomlString (168-193)
MCPForUnity/Editor/Helpers/AssetPathUtility.cs (1)
  • uvxPath (169-176)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/CodexConfigHelperTests.cs (1)
MCPForUnity/Editor/Services/MCPServiceLocator.cs (2)
  • MCPServiceLocator (11-91)
  • Register (40-62)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageSceneHierarchyPagingTests.cs (1)
MCPForUnity/Editor/Tools/ManageScene.cs (1)
  • HandleCommand (90-191)
Server/src/services/tools/debug_request_context.py (1)
Server/src/core/telemetry.py (1)
  • get_package_version (66-80)
MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs (3)
MCPForUnity/Editor/Helpers/CodexConfigHelper.cs (1)
  • GetDevModeForceRefresh (20-30)
MCPForUnity/Editor/Helpers/AssetPathUtility.cs (1)
  • uvxPath (169-176)
MCPForUnity/Editor/Constants/EditorPrefKeys.cs (1)
  • EditorPrefKeys (7-49)
Server/src/services/tools/manage_scene.py (1)
Server/src/services/tools/utils.py (2)
  • coerce_int (63-77)
  • coerce_bool (11-24)
Server/tests/integration/test_debug_request_context_diagnostics.py (1)
Server/src/services/tools/debug_request_context.py (1)
  • debug_request_context (16-80)
MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.cs (1)
MCPForUnity/Editor/Constants/EditorPrefKeys.cs (1)
  • EditorPrefKeys (7-49)
MCPForUnity/Editor/Tools/ManageScene.cs (1)
MCPForUnity/Editor/Tools/ManageGameObject.cs (2)
  • Type (2557-2571)
  • GameObject (1604-1627)
Server/tests/integration/test_tool_signatures_paging.py (2)
Server/src/services/tools/manage_scene.py (1)
  • manage_scene (14-90)
Server/src/services/tools/manage_gameobject.py (1)
  • manage_gameobject (16-254)
MCPForUnity/Editor/Tools/ManageGameObject.cs (1)
MCPForUnity/Editor/Helpers/GameObjectSerializer.cs (3)
  • JToken (537-556)
  • GameObjectSerializer (17-557)
  • GetComponentData (122-429)
Server/src/services/tools/manage_gameobject.py (1)
Server/src/services/tools/utils.py (3)
  • coerce_bool (11-24)
  • parse_json_payload (27-60)
  • coerce_int (63-77)
🪛 LanguageTool
docs/README-DEV.md

[style] ~174-~174: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ...ded) Some Unity tool calls can return very large JSON payloads (deep hierarchies, compo...

(EN_WEAK_ADJECTIVE)

🪛 Ruff (0.14.10)
Server/tests/integration/test_manage_gameobject_param_coercion.py

43-43: Unused function argument: cmd

(ARG001)


43-43: Unused function argument: kwargs

(ARG001)

Server/src/main.py

34-35: try-except-pass detected, consider logging the exception

(S110)


34-34: Do not catch blind exception: Exception

(BLE001)

Server/tests/integration/test_manage_scene_paging_params.py

11-11: Unused function argument: cmd

(ARG001)


11-11: Unused function argument: kwargs

(ARG001)

Server/src/services/tools/utils.py

76-76: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (35)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageScriptableObjectTests.cs (2)

6-6: LGTM!

The System.Threading import is necessary for the Thread.Sleep call in the new WaitForUnityReady helper method.


26-26: LGTM!

The WaitForUnityReady() calls are strategically placed to ensure Unity is in a stable state before and after asset operations, which improves test determinism.

Also applies to: 50-50

Server/src/main.py (1)

272-283: Excellent documentation addition for payload safety.

This guidance clearly communicates paging strategies and payload-sizing best practices to LLM clients, directly addressing the PR's goal of preventing large-response crashes and timeouts. The specific parameter recommendations (page sizes, include_properties=false for initial queries, generate_preview=false) provide actionable defaults that will help models avoid overwhelming single responses.

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

1-11: LGTM! Standard Unity metadata file.

This is a standard Unity .meta file for the new test file. No issues detected.

docs/README-DEV.md (1)

76-76: LGTM! Clear dev mode documentation.

The description accurately reflects the new DevModeForceServerRefresh feature and explains the trade-off (reliability vs. speed).

MCPForUnity/Editor/Constants/EditorPrefKeys.cs (1)

23-23: LGTM! Consistent constant addition.

The new DevModeForceServerRefresh key follows the established naming convention and is properly namespaced with the MCPForUnity. prefix.

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

1-80: LGTM! Useful diagnostic additions.

The new server field provides valuable debugging context (version, cwd, argv) without introducing security concerns. The implementation is clean and follows the existing pattern for other diagnostic fields.

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

10-10: LGTM! Good refactoring to use shared utility.

Importing coerce_int from the shared utils module reduces code duplication and ensures consistent coercion behavior across tools.


70-72: LGTM! Clean replacement of local coercion logic.

Line 70 correctly uses the shared coerce_int utility, removing the need for the local _coerce_int helper. The behavior is preserved while improving maintainability.

MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.uxml (1)

45-50: LGTM! Well-structured dev mode UI addition.

The new Dev Mode section follows existing UXML patterns and provides clear guidance about the performance tradeoff (slower startup for fresh builds).

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

4-27: LGTM! Clean test coverage for server diagnostics.

The test properly stubs dependencies and verifies the new server diagnostics payload structure.

TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/CodexConfigHelperTests.cs (3)

35-36: LGTM! Proper test state management.

The new fields track DevModeForceServerRefresh state correctly, following the established pattern for other EditorPrefs in this test class.

Also applies to: 46-47


58-60: Good defensive test setup.

Forcing DevModeForceServerRefresh to false ensures deterministic test behavior regardless of developer settings. The comment clearly explains why this matters for arg-order assertions.


76-79: LGTM! Complete test cleanup.

The TearDown and OneTimeTearDown methods properly restore both the PlatformService and EditorPrefs state, preventing test pollution.

Also applies to: 102-110

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

35-36: LGTM! Stricter boolean type verification.

The assertions now verify that string inputs like "true" and "0" are properly coerced to boolean True and False, ensuring the coercion logic works correctly.


39-72: LGTM! Good coverage for paging parameter coercion.

The test validates that paging-related parameters are correctly coerced from strings to their expected types (integers and booleans) and properly mapped to the Unity command payload.

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

37-42: Verify lenient assertion style.

The assertions here allow either integers or strings (e.g., p["pageSize"] in (10, "10")), which differs from the stricter type checking in test_manage_gameobject_param_coercion.py (Line 69: assert p["pageSize"] == 25).

Should these assertions be strict to verify coercion actually occurred, or is this intentional to test that the function accepts both forms?

For consistency with the gameobject test, consider:

🔎 Proposed strict assertions
-    assert p["pageSize"] in (10, "10")
-    assert p["cursor"] in (20, "20")
-    assert p["maxNodes"] in (1000, "1000")
-    assert p["maxDepth"] in (6, "6")
-    assert p["maxChildrenPerNode"] in (200, "200")
-    assert p["includeTransform"] in (True, "true")
+    assert p["pageSize"] == 10
+    assert p["cursor"] == 20
+    assert p["maxNodes"] == 1000
+    assert p["maxDepth"] == 6
+    assert p["maxChildrenPerNode"] == 200
+    assert p["includeTransform"] is True
MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs (2)

84-86: LGTM! Defensive EditorPrefs access.

The try-catch ensures EditorPrefs failures don't break configuration generation, safely defaulting to false (dev mode disabled).


155-177: LGTM! Clear dev mode flag implementation.

The function correctly prepends cache-busting flags when dev mode is enabled, with helpful comments explaining the flag purposes and ordering consistency.

MCPForUnity/Editor/Helpers/CodexConfigHelper.cs (3)

20-30: LGTM! Consistent EditorPrefs access pattern.

The implementation matches ConfigJsonBuilder.cs line 84-86, providing consistent defensive behavior across the codebase.


32-38: LGTM! Clean helper for TOML manipulation.

The helper properly handles null checks and encapsulates the TOML-specific logic for adding dev mode flags.


62-67: LGTM! Consistent dev mode integration.

Both stdio paths correctly retrieve the dev mode flag and apply the cache-busting arguments in the proper order (dev flags → --from → package name → transport flags), consistent with ConfigJsonBuilder.cs.

Also applies to: 212-217

MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.cs (1)

33-91: Dev-mode refresh toggle wiring looks consistent and safe

The new devModeForceRefreshToggle is correctly:

  • Queried from UXML,
  • Initialized from EditorPrefKeys.DevModeForceServerRefresh,
  • Kept in sync in UpdatePathOverrides, and
  • Propagated via OnHttpServerCommandUpdateRequested on changes.

The only behavioral side effect is that UpdatePathOverrides will re-fire the value-changed callback when the stored value changes, which is harmless and keeps server args up to date. No changes needed from a correctness standpoint.

Also applies to: 108-112, 157-161, 188-218

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

12-13: Search normalization and paging coercion are robust and align with payload-safety goals

  • page_size / page_number coercion via coerce_int tolerates string/float inputs while rejecting booleans, which matches the union type.
  • For action=="search", moving t: filters from path into search_pattern and scoping path to "Assets" should effectively prevent unintended full-project scans.
  • Mapping asset_type into filter_type when the latter is unset is a good compatibility bridge for callers that overloaded asset_type for search.

No functional issues spotted in the new logic.

Also applies to: 17-23, 27-47, 92-118

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

8-103: Hierarchy paging test is well-scoped and non-brittle

The test:

  • Forces paging with pageSize=10 and asserts scope, truncated, next_cursor, and item counts.
  • Validates child paging via parent (instance ID) with a smaller page size.
  • Cleans up all created objects in TearDown.

The coverage matches the new GetSceneHierarchyPaged behavior without overfitting to specific scene contents.

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

10-11: Paging and include_properties parameters are correctly coerced and forwarded

  • page_size, cursor, and max_components are safely coerced with coerce_int, and omitted on failure rather than sending bad types.
  • include_properties is normalized via coerce_bool, matching the C# side’s expectation of a strict bool.
  • The outgoing keys (pageSize, cursor, maxComponents, includeProperties) align with the new GetComponentsFromTarget signature, while remaining optional so older flows still behave as before.

This is a clean, backwards-compatible extension of the tool surface.

Also applies to: 71-76, 132-147, 181-220

MCPForUnity/Editor/Tools/ManageGameObject.cs (1)

177-221: get_components paging and summary-first behavior are implemented correctly

  • The get_components case now:
    • Accepts both pageSize/page_size and maxComponents/max_components, plus includeProperties/include_properties.
    • Applies sensible defaults (25 page size, 0 cursor, 50 maxComponents) and clamps them inside GetComponentsFromTarget, which guards against pathological payloads.
  • GetComponentsFromTarget:
    • Builds a stable component list once, paginates by cursor + effectiveTake, and returns a structured payload (cursor, pageSize, next_cursor, truncated, total, items).
    • Returns lightweight metadata via BuildComponentMetadata when includeProperties == false, matching the “summary-first, deep-on-demand” design.
    • Enforces a rough 250k-character cap when includeProperties is true, stopping early and using next_cursor to allow further paging without ever throwing on oversized components.
    • Downgrades serialization errors per-component into error objects instead of failing the whole call.

Overall this is a solid hardening of get_components with good defaults and safe failure modes.

Also applies to: 1230-1342, 1344-1357

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

21-38: SceneCommand paging fields and routing to GetSceneHierarchyPaged are coherent and backward compatible

  • New SceneCommand fields (parent, pageSize, cursor, maxNodes, maxDepth, maxChildrenPerNode, includeTransform) are wired via ToSceneCommand, with:
    • Numeric coercion through BI and support for both camelCase and snake_case keys.
    • Boolean coercion for includeTransform via BB, tolerating "true", "false", "1", "0", and empty strings.
  • HandleCommand now delegates "get_hierarchy" to GetSceneHierarchyPaged(cmd) but keeps other actions unchanged, so existing behavior is preserved outside hierarchy queries.

This keeps the public contract stable while adding the new paging/safety surface.

Also applies to: 40-85, 148-176, 488-575


488-575: Hierarchy paging implementation is defensively bounded and matches the new tests

  • GetSceneHierarchyPaged:
    • Validates that an active, loaded scene exists.
    • Applies conservative, clamped defaults (pageSize default 50, maxNodes default 1000) and computes effectiveTake = min(pageSize, maxNodes) to cap both per-page and total work.
    • Distinguishes two scopes:
      • scope="roots" when parent is null, using GetRootGameObjects().
      • scope="children" when a parent is supplied and resolved via ResolveGameObject, iterating only direct children.
    • Computes cursor, next_cursor, truncated, and total consistently with the paging tests.
  • ResolveGameObject supports:
    • Instance IDs via EditorUtility.InstanceIDToObject,
    • Simple hierarchy paths via GameObject.Find(path), and
    • Name searches (including inactive children) within the active scene.
  • BuildGameObjectSummary:
    • Returns a compact node summary (name, instanceID, active flags, tag/layer/static, full path, child count).
    • Omits inlined children by design, instead exposing childrenCursor and childrenPageSizeDefault alongside a childrenTruncated flag to encourage child-level paging.
    • Optionally includes local transform data when includeTransform is true.

The implementation is consistent with the Python-side manage_scene paging parameters and the new Unity tests.

Also applies to: 577-622, 624-681

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

6-8: Paging and safety parameters are cleanly plumbed through to the Unity side

  • New arguments (parent, page_size, cursor, max_nodes, max_depth, max_children_per_node, include_transform) are optional and normalized via coerce_int / coerce_bool, preventing bad types from reaching C#.
  • The outgoing keys (pageSize, cursor, maxNodes, maxDepth, maxChildrenPerNode, includeTransform) align with SceneCommand and GetSceneHierarchyPaged in ManageScene.cs.
  • Existing behavior for non-get_hierarchy actions is unaffected, since these fields are only added when non-None.

The parameter handling is straightforward and maintains compatibility with earlier callers.

Also applies to: 31-38, 43-52, 53-79

MCPForUnity/Editor/Services/ServerManagementService.cs (5)

233-273: Good safety improvements for multi-PID handling.

The guardrails preventing termination of the Unity Editor process and non-MCP-like processes are well-implemented. The logging is clear for debugging refused terminations.


282-337: LGTM!

The platform-aware implementation is solid. Using -nP flags on lsof avoids DNS lookups for faster/more reliable operation, and -sTCP:LISTEN correctly filters to only listening sockets. Deduplication handles cases where the same PID may appear multiple times (e.g., IPv4/IPv6 bindings).


339-343: LGTM!

Simple defensive helper with safe fallback. Returning -1 on failure correctly prevents false matches against valid PIDs.


396-427: Graceful-then-forceful termination pattern is appropriate.

Minor note: On Unix, kill -15 returns success if the signal was sent, not if the process has terminated. The escalation to -9 only triggers if the signal couldn't be sent (e.g., permission denied). For this use case, this is acceptable—if the old server doesn't terminate quickly, the new server will fail to bind and the user will see an error. Adding a wait-and-verify loop would add complexity for marginal benefit.


458-464: LGTM!

Dev mode flag integration is clean. The safe fallback to false on exception and the correct placement of --no-cache --refresh before the package name follow uvx CLI conventions.

dependencies = [
"httpx>=0.27.2",
"fastmcp>=2.13.0,<2.13.2",
"fastmcp==2.14.1",
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 29, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

fastmcp 2.14.1 changelog breaking changes release

💡 Result:

FastMCP 2.14.1 (released Dec 15, 2025) — summary of notable changes and breaking items:

  • Main features: sampling-with-tools (SEP-1577) and new ctx.sample_step(), result_type for structured outputs, AnthropicSamplingHandler + OpenAI handler promoted to production. [1][3]

  • Breaking / migration notes:

    • 2.14 series removes several deprecated 2.x APIs (users should pin versions and audit for any APIs previously marked deprecated). [1][2]
    • Earlier breaking changes called out in the changelog that still apply when upgrading (decorate-return behavior, websockets optional, server helper-method deprecations) — review your code for: decorators now return the objects they create (not the original function), websockets may need to be added as an extra dependency if you rely on it, and automatic server conversion helper methods are deprecated in favor of using the decorators directly. [2][4]

Sources:
[1] FastMCP Updates — FastMCP 2.14.1 (release notes).
[2] FastMCP Changelog (breaking changes and deprecations).
[3] PyPI fastmcp 2.14.1 upload metadata.


🌐 Web query:

fastmcp PyPI 2.14.1 version security

💡 Result:

Short answer: fastmcp 2.14.1 includes the security fixes — it is >= the releases that fixed the known FastMCP advisories, so it is not vulnerable to the disclosed issues affecting <2.13.0 and the MCP-SDK-related issue fixed in 2.14.0. See sources below.

Details and sources:

  • PyPI release 2.14.1 (uploaded Dec 15, 2025). [1]
  • Public advisories / CVEs: reflected XSS and auth “confused deputy” issues (CVE/GHSA) and a Windows command‑injection — all fixed in 2.13.0. [2][3][4]
  • An MCP-SDK-related advisory that required FastMCP to require newer SDK versions was addressed in 2.14.0 (so 2.14.1 includes that fix). [5]

Recommendation: upgrade to (or keep) fastmcp >= 2.14.1 and review the FastMCP security advisories / changelog for any mitigation steps relevant to your deployment.

Sources:
[1] PyPI fastmcp 2.14.1 release info.
[2] NVD / CVE-2025-62800 (XSS advisory).
[3] NVD / CVE-2025-62801 (Windows command injection).
[4] FastMCP GitHub security advisories summary.
[5] FastMCP release notes / updates (2.14.0+).


🏁 Script executed:

# Check how fastmcp is used in the codebase to understand potential impact
find . -type f -name "*.py" | head -20 | xargs grep -l "fastmcp" 2>/dev/null | head -10

Repository: CoplayDev/unity-mcp

Length of output: 246


🏁 Script executed:

# Check the pyproject.toml file to see the broader context
cat -n Server/pyproject.toml

Repository: CoplayDev/unity-mcp

Length of output: 2248


Audit code for fastmcp 2.14 breaking changes before merging.

The upgrade to fastmcp 2.14.1 is necessary for the new pagination and dev-mode features, and the version is secure with recent critical fixes. However, fastmcp 2.14 removes several deprecated 2.x APIs that require code review:

  • Decorator return behavior: Decorators now return the objects they create (not the original function)
  • Websockets handling: Websockets support is now optional—verify it's explicitly included if needed
  • Server helper methods: Automatic server conversion helpers are deprecated; use decorators directly instead

Review Server/src/services/custom_tool_service.py, active_tool.py, and custom_tools.py for deprecated API usage. The exact pinning (==2.14.1) is appropriate here to ensure stability against future breaking changes; if a 2.14.2 patch is released, update manually if needed.

🤖 Prompt for AI Agents
In Server/pyproject.toml at line 33, we upgraded fastmcp to 2.14.1 which removes
deprecated 2.x APIs — audit Server/src/services/custom_tool_service.py,
active_tool.py, and custom_tools.py for three changes: (1) update any decorators
that expected the original function to be returned so they now accept and handle
the decorator returning the created object (adjust call sites and tests to use
the returned object instead of the original function), (2) ensure websocket
functionality is explicitly enabled/imported where used (add explicit websocket
dependency or import and feature flag where websocket handlers are registered),
and (3) replace any automatic server conversion helper calls with the new
decorator-based APIs (apply the provided decorators directly to handlers and
remove helper usage). Keep the exact pin to ==2.14.1 and run tests to confirm
behavior.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We're good, 2.14.1 is the latest version and fixes issues in 2.14. Can upgrade later if we see fit. We were bitten by pinning a ">" for fastmcp version a few weeks back, when they introduced a change that broke all mcp tools everywhere.

Copy link
Contributor

Choose a reason for hiding this comment

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

@dsarno, understood! Your exact pinning strategy makes perfect sense given the prior breakage from range pins. Sticking with ==2.14.1 will give you stability and control over when to upgrade—wise choice after being bitten once. Thanks for the context!


✏️ Learnings added
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 490
File: Server/pyproject.toml:33-33
Timestamp: 2025-12-29T04:54:17.743Z
Learning: For fastmcp dependency in Server/pyproject.toml: prefer exact version pinning (e.g., fastmcp==2.14.1) rather than range pins like >=2.13.0, due to past issues where fastmcp introduced breaking changes that affected all MCP tools.

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

Comment on lines +11 to +36
# Workaround for environments where tool signature evaluation runs with a globals
# dict that does not include common `typing` names (e.g. when annotations are strings
# and evaluated via `eval()` during schema generation).
# Making these names available in builtins avoids `NameError: Annotated/Literal/... is not defined`.
try: # pragma: no cover - startup safety guard
import builtins
import typing as _typing

_typing_names = (
"Annotated",
"Literal",
"Any",
"Union",
"Optional",
"Dict",
"List",
"Tuple",
"Set",
"FrozenSet",
)
for _name in _typing_names:
if not hasattr(builtins, _name) and hasattr(_typing, _name):
setattr(builtins, _name, getattr(_typing, _name)) # type: ignore[attr-defined]
except Exception:
pass

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reconsider the builtins patching approach—this is a significant anti-pattern.

Modifying builtins to inject typing names is a workaround that can cause unexpected side effects and maintenance issues:

  1. Global namespace pollution: All modules and third-party libraries will see these additions, potentially causing conflicts or unexpected behavior.
  2. Masks root cause: The real issue is likely that schema generation (Pydantic/FastMCP) lacks the proper evaluation context for annotations.
  3. Debugging complexity: Future developers will struggle to understand where these builtins come from.

Better alternatives:

  • Use from __future__ import annotations at the top of tool definition modules to defer annotation evaluation
  • Ensure schema generation passes the correct globalns/localns to typing.get_type_hints() or equivalent
  • Import typing names in the modules where they're used, wrapped in if TYPE_CHECKING: blocks when needed only for static analysis
  • If FastMCP schema generation is the issue, consider opening an issue upstream or providing a proper globals dict during schema extraction

Additionally, the static analysis tools correctly flag the bare except Exception: pass pattern—at minimum, log the exception for debugging.

🔎 Alternative approach: Use proper TYPE_CHECKING imports

Instead of patching builtins, ensure tool modules use proper imports:

from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing import Annotated, Literal, Any, Union, Optional, Dict, List, Tuple, Set, FrozenSet

If the issue is in schema generation, provide a proper evaluation namespace:

import typing
import sys

# When evaluating annotations, provide a proper namespace
eval_globals = {
    **sys.modules[module_name].__dict__,
    **vars(typing),
}
typing.get_type_hints(func, globalns=eval_globals)
🧰 Tools
🪛 Ruff (0.14.10)

34-35: try-except-pass detected, consider logging the exception

(S110)


34-34: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In Server/src/main.py around lines 11 to 36, remove the builtins patch that
injects typing names (global namespace pollution and maintenance hazard) and
replace it with a proper fix: delete the try/except block that mutates builtins;
instead add from __future__ import annotations to modules that define
tools/schemas, import necessary typing symbols guarded by typing.TYPE_CHECKING
where needed, and ensure any schema-generation code calls
typing.get_type_hints(..., globalns=...) with a supplied evaluation namespace
(or adjust the schema extractor to provide it). If you must keep a fallback, log
the caught exception rather than using a bare except: pass and limit the
fallback scope to only the schema-evaluation call rather than mutating builtins.

@dsarno dsarno merged commit 35165e1 into CoplayDev:main Dec 29, 2025
2 checks passed
@msanatan
Copy link
Member

🔥

@dsarno dsarno deleted the improve-get-hierarchy-and-get-components branch January 2, 2026 19:19
@coderabbitai coderabbitai bot mentioned this pull request Jan 29, 2026
4 tasks
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.

get_components consistently crashing unity 6.3 LTS

2 participants