Skip to content

Comments

Fix: #709 - Tool state persistence and filtering#722

Closed
whatevertogo wants to merge 3 commits intoCoplayDev:betafrom
whatevertogo:fix/preserve-tool-toggle-state-restarts
Closed

Fix: #709 - Tool state persistence and filtering#722
whatevertogo wants to merge 3 commits intoCoplayDev:betafrom
whatevertogo:fix/preserve-tool-toggle-state-restarts

Conversation

@whatevertogo
Copy link
Contributor

@whatevertogo whatevertogo commented Feb 11, 2026

Fixes #709. Unity restart no longer resets tool states. Client tool lists now respect Unity's enabled tools.

Summary by Sourcery

Align Unity client tool enablement state with server-side tool registration and ensure tool preferences persist across sessions and service instances.

New Features:

  • Expose a transport client getter and a tool reregistration API so the Unity editor can trigger dynamic tool registration over HTTP.

Bug Fixes:

  • Prevent Unity editor restart from resetting tool enablement preferences and ensure disabled built-in tools remain disabled when rediscovered.
  • Filter server tool listings over HTTP to only include tools enabled in the connected Unity instance while keeping server-only tools visible and honoring script tool aliases.

Enhancements:

  • Add client-side logic to batch-update tool enablement without redundant registrations and to re-register tools once after bulk changes.
  • Improve tooling around Unity tool discovery and preference handling with clearer behavior for non-existent tools and stored preferences.

Tests:

  • Add server-side tests characterizing tool list filtering behavior based on Unity-registered tools and aliases.
  • Add Unity edit-mode tests covering tool preference persistence, default values, and behavior for built-in tools with disabled auto-registration.

Summary by CodeRabbit

  • New Features
    • Added tool re-registration after toggle changes; bulk enable/disable now performs a single re-registration to keep the UI responsive.
    • Made transport client retrieval available to callers.
    • Server (HTTP) now filters the tool list to show only Unity-enabled tools and supported aliases for the active context.
    • Preference handling respects stored states; disabling built-in tools remains persisted across rediscovery.
  • Tests
    • Added unit tests for tool discovery, preferences, and persistence.
    • Added server tests characterizing HTTP tool-list filtering behavior.

whatevertogo and others added 3 commits February 11, 2026 22:04
Fixes CoplayDev#709

- Remove logic in ToolDiscoveryService.EnsurePreferenceInitialized
  that forced built-in tools to be re-enabled on every Unity launch
- Add ReregisterToolsAsync() to IMcpTransportClient for dynamic tool
  list updates when toggles change in the UI
- Implement tool reregistration in WebSocketTransportClient
- Add ReregisterToolsAsync() to McpToolsSection to trigger updates
  when tool toggles are flipped
- Add unit tests for ToolDiscoveryService preference persistence

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 11, 2026 15:15
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 11, 2026

Reviewer's Guide

Adds Unity-aware tool filtering on the server and wiring on the Unity client to persist tool enablement and re‑register tools over HTTP WebSocket, plus tests for both transport filtering and editor tool discovery persistence.

Sequence diagram for Unity tool toggle and HTTP WebSocket tool reregistration

sequenceDiagram
    actor UnityUser
    participant McpToolsSection
    participant ToolDiscoveryService
    participant TransportManager
    participant IMcpTransportClient
    participant WebSocketTransportClient
    participant MCPServer

    UnityUser->>McpToolsSection: Toggle tool checkbox
    McpToolsSection->>ToolDiscoveryService: SetToolEnabled(toolName, enabled)
    McpToolsSection->>McpToolsSection: HandleToggleChange(tool, enabled, updateSummary, reregisterTools)
    alt updateSummary
        McpToolsSection->>McpToolsSection: UpdateSummary()
    end
    alt reregisterTools
        McpToolsSection->>McpToolsSection: ReregisterToolsAsync() (ThreadPool)
        McpToolsSection->>TransportManager: GetClient(TransportMode_Http)
        TransportManager-->>McpToolsSection: IMcpTransportClient or null
        alt client is null or not connected
            McpToolsSection-->>McpToolsSection: Skip reregistration
        else client is WebSocketTransportClient and connected
            McpToolsSection->>IMcpTransportClient: ReregisterToolsAsync()
            activate WebSocketTransportClient
            WebSocketTransportClient->>WebSocketTransportClient: SendRegisterToolsAsync(_lifecycleCts_Token)
            WebSocketTransportClient->>MCPServer: Send tools registration
            MCPServer-->>WebSocketTransportClient: Ack registration
            deactivate WebSocketTransportClient
        end
    end
Loading

Sequence diagram for server-side Unity-aware tool list filtering

sequenceDiagram
    participant UnityClient
    participant UnityInstanceMiddleware
    participant PluginHub
    participant FastMcpContext
    participant MCPServerBackend

    UnityClient->>UnityInstanceMiddleware: list_tools request
    UnityInstanceMiddleware->>UnityInstanceMiddleware: on_list_tools(context, call_next)
    UnityInstanceMiddleware->>UnityInstanceMiddleware: _inject_unity_instance(context)
    UnityInstanceMiddleware->>MCPServerBackend: call_next(context)
    MCPServerBackend-->>UnityInstanceMiddleware: tools
    UnityInstanceMiddleware->>UnityInstanceMiddleware: _should_filter_tool_listing()
    alt filtering disabled
        UnityInstanceMiddleware-->>UnityClient: tools
    else filtering enabled
        UnityInstanceMiddleware->>FastMcpContext: get_state(user_id)
        UnityInstanceMiddleware->>FastMcpContext: get_state(unity_instance)
        UnityInstanceMiddleware->>UnityInstanceMiddleware: _resolve_candidate_project_hashes(active_instance)
        alt project_hashes empty
            UnityInstanceMiddleware->>PluginHub: get_sessions(user_id)
            PluginHub-->>UnityInstanceMiddleware: sessions
            alt no sessions
                UnityInstanceMiddleware-->>UnityClient: tools
            else one or more sessions
                UnityInstanceMiddleware->>UnityInstanceMiddleware: derive project_hashes from sessions
            end
        end
        UnityInstanceMiddleware->>UnityInstanceMiddleware: enabled_tool_names = {}
        loop project_hash in project_hashes
            UnityInstanceMiddleware->>PluginHub: get_tools_for_project(project_hash)
            PluginHub-->>UnityInstanceMiddleware: registered_tools
            UnityInstanceMiddleware->>UnityInstanceMiddleware: collect tool.name into enabled_tool_names
        end
        UnityInstanceMiddleware->>UnityInstanceMiddleware: filtered = []
        loop tool in tools
            UnityInstanceMiddleware->>UnityInstanceMiddleware: _is_tool_visible(tool.name, enabled_tool_names)
            alt visible
                UnityInstanceMiddleware->>UnityInstanceMiddleware: add tool to filtered
            end
        end
        UnityInstanceMiddleware-->>UnityClient: filtered
    end
Loading

Updated class diagram for Unity transport clients, tools UI, and server middleware

classDiagram
    class IMcpTransportClient {
        <<interface>>
        +bool IsConnected
        +Task StartAsync()
        +Task StopAsync()
        +Task~bool~ VerifyAsync()
        +Task ReregisterToolsAsync()
    }

    class WebSocketTransportClient {
        +bool IsConnected
        -CancellationTokenSource _lifecycleCts
        +Task StartAsync()
        +Task StopAsync()
        +Task~bool~ VerifyAsync()
        +Task ReregisterToolsAsync()
        -Task SendRegisterToolsAsync(CancellationToken token)
    }

    class StdioTransportClient {
        +bool IsConnected
        +Task StartAsync()
        +Task StopAsync()
        +Task~bool~ VerifyAsync()
        +Task ReregisterToolsAsync()
    }

    class TransportManager {
        -IMcpTransportClient _httpClient
        -IMcpTransportClient _stdioClient
        +IMcpTransportClient GetOrCreateClient(TransportMode mode)
        +IMcpTransportClient GetClient(TransportMode mode)
        +Task~bool~ StartAsync(TransportMode mode)
        +Task StopAsync(TransportMode mode)
        +Task~bool~ VerifyAsync(TransportMode mode)
        +TransportState GetState(TransportMode mode)
        +bool IsRunning(TransportMode mode)
    }

    class McpToolsSection {
        -Dictionary~string, Toggle~ toolToggleMap
        -List~ToolMetadata~ allTools
        -VisualElement CreateToolRow(ToolMetadata tool)
        -void HandleToggleChange(ToolMetadata tool, bool enabled, bool updateSummary, bool reregisterTools)
        -void ReregisterToolsAsync()
        -void SetAllToolsState(bool enabled)
        -void UpdateSummary()
    }

    class ToolDiscoveryService {
        +void SetToolEnabled(string toolName, bool enabled)
        +bool IsToolEnabled(string toolName)
        -void EnsurePreferenceInitialized(ToolMetadata metadata)
    }

    class UnityInstanceMiddleware {
        -dict~string, string~ _active_by_key
        -set~string~ _unity_managed_tool_names
        -dict~string, string~ _tool_alias_to_unity_target
        -set~string~ _server_only_tool_names
        +async Task on_list_tools(MiddlewareContext context, Func~MiddlewareContext, Task~ call_next)
        -bool _should_filter_tool_listing()
        -async Task~set~string~~ _resolve_enabled_tool_names_for_context(MiddlewareContext context)
        -static List~string~ _resolve_candidate_project_hashes(string active_instance)
        -bool _is_tool_visible(string tool_name, set~string~ enabled_tool_names)
    }

    class MCPServiceLocator {
        <<static>>
        +ToolDiscoveryService ToolDiscovery
        +TransportManager TransportManager
    }

    IMcpTransportClient <|.. WebSocketTransportClient
    IMcpTransportClient <|.. StdioTransportClient

    TransportManager --> IMcpTransportClient : uses
    McpToolsSection --> ToolDiscoveryService : uses
    McpToolsSection --> TransportManager : uses
    McpToolsSection --> MCPServiceLocator : accesses
    MCPServiceLocator --> ToolDiscoveryService : provides
    MCPServiceLocator --> TransportManager : provides

    UnityInstanceMiddleware --> PluginHub : uses
    UnityInstanceMiddleware --> MiddlewareContext : uses
    UnityInstanceMiddleware --> FastMcpContext : uses
Loading

File-Level Changes

Change Details Files
Introduce server-side filtering of MCP tool listings based on Unity-registered tools and session/project context.
  • Add HTTP-only on_list_tools middleware hook that injects Unity instance context and filters the tool list against enabled Unity tools from PluginHub
  • Define canonical Unity-managed tool set, server-only tools, and alias mappings so aliases (e.g., create_script) follow their Unity tool's enabled state
  • Resolve candidate Unity project hashes from session/unity_instance or from PluginHub sessions, unioning across multiple sessions, and build enabled tool name set per project
  • Ensure unknown/non-Unity-managed tools and server-only tools remain visible for forward compatibility and internal features
Server/src/transport/unity_instance_middleware.py
Server/tests/test_transport_characterization.py
Ensure Unity editor tool enablement state is persisted via EditorPrefs and propagated to the MCP server via dynamic re-registration over HTTP.
  • Extend MCP tools UI toggle handler to optionally suppress immediate re-registration and introduce a batched SetAllToolsState implementation that only re-registers once when any state changes
  • Add asynchronous, fire-and-forget ReregisterToolsAsync in the tools section that calls the HTTP client’s ReregisterToolsAsync if connected
  • Expose TransportManager.GetClient publicly so UI code can access the active client for a given transport mode
  • Extend the IMcpTransportClient interface and WebSocket client with a ReregisterToolsAsync method that re-sends the current tool registration with lifecycle cancellation and logging, while stdio provides a no-op implementation
MCPForUnity/Editor/Windows/Components/Tools/McpToolsSection.cs
MCPForUnity/Editor/Services/Transport/TransportManager.cs
MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs
MCPForUnity/Editor/Services/Transport/Transports/StdioTransportClient.cs
MCPForUnity/Editor/Services/Transport/IMcpTransportClient.cs
Tighten ToolDiscoveryService preference initialization and add editor tests to validate persistence and defaulting behavior.
  • Remove logic that could override stored false preferences for built-in, non-auto-register tools during preference initialization
  • Add edit-mode tests to verify SetToolEnabled writes to EditorPrefs, IsToolEnabled honors stored values and defaults, tool enablement persists across service instances, and DiscoverAllTools does not flip a built-in tool back to enabled when preference is false
MCPForUnity/Editor/Services/ToolDiscoveryService.cs
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/ToolDiscoveryServiceTests.cs
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/ToolDiscoveryServiceTests.cs.meta
TestProjects/UnityMCPTests/Assets/Tests/Editor.meta
TestProjects/UnityMCPTests/Assets/Tests/Editor/State.meta

Assessment against linked issues

Issue Objective Addressed Explanation
#709 Ensure that disabled Unity MCP tools remain disabled across Unity restarts (tool enable/disable state is correctly persisted and restored).
#709 Ensure that disabled Unity MCP tools do not appear in the client’s tool list when connecting from Claude Code / Trae (tool list respects Unity’s enabled tools).

Possibly linked issues


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 Feb 11, 2026

📝 Walkthrough

Walkthrough

This PR resolves issue #709 by implementing persistent tool state and dynamic reregistration. It removes special-case preference override logic from ToolDiscoveryService, adds a ReregisterToolsAsync mechanism across transport clients, enhances the UI to trigger reregistration after tool state changes with bulk optimization, and implements server-side filtering of tool listings based on Unity-enabled state.

Changes

Cohort / File(s) Summary
Transport Interface & Client Implementations
MCPForUnity/Editor/Services/Transport/IMcpTransportClient.cs, MCPForUnity/Editor/Services/Transport/Transports/StdioTransportClient.cs, MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs
Added ReregisterToolsAsync() method to interface; StdioTransportClient provides no-op implementation (tools registered at startup); WebSocketTransportClient implements full reregistration with connection state checks and logging.
Transport Manager & Tool Discovery
MCPForUnity/Editor/Services/Transport/TransportManager.cs, MCPForUnity/Editor/Services/ToolDiscoveryService.cs
Changed GetClient() from private to public to enable external access; removed override logic that forced preference values for built-in auto-register-false tools.
UI Tool Management
MCPForUnity/Editor/Windows/Components/Tools/McpToolsSection.cs
Added async reregistration flow triggered after tool state changes; introduced bulk-change optimization to defer reregistration until all toggles processed; added private ReregisterToolsAsync() method using TransportManager HTTP client.
Server-side Tool Filtering
Server/src/transport/unity_instance_middleware.py
Implemented HTTP-filtered MCP tool listing via new on_list_tools middleware that restricts visible tools to Unity-enabled ones; added session management for mapping tool aliases and project contexts.
Test Coverage
Server/tests/test_transport_characterization.py, TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/ToolDiscoveryServiceTests.cs
Added server-side tests validating tool filtering behavior; added comprehensive unit tests for ToolDiscoveryService covering preference persistence, state recovery, and built-in tool handling.
Unity Meta Files
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/ToolDiscoveryServiceTests.cs.meta, TestProjects/UnityMCPTests/Assets/Tests/Editor.meta, TestProjects/UnityMCPTests/Assets/Tests/Editor/State.meta
Standard Unity metadata files for test structure organization.

Sequence Diagram(s)

sequenceDiagram
    participant UI as McpToolsSection
    participant TM as TransportManager
    participant TC as TransportClient
    participant Server

    UI->>UI: HandleToggleChange
    UI->>UI: SetAllToolsState with reregisterTools=false
    loop For each tool
        UI->>UI: Toggle without reregistration
    end
    UI->>TM: ReregisterToolsAsync()
    TM->>TC: GetClient() returns HTTP client
    TM->>TC: ReregisterToolsAsync()
    TC->>Server: SendRegisterToolsAsync()
    Server->>Server: on_list_tools filters by enabled state
    Server-->>TC: Filtered tool list
    TC-->>TM: Success logged
    TM-->>UI: Complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • dsarno
  • msanatan

Poem

🐰 Tools once hidden, now they persist with grace,
Reregistration flows through cyberspace,
Bulk toggles dance, no flickering in sight,
Server filters true—disabled tools take flight! ✨
A rabbit hops through filters, clean and neat!

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely summarizes the main fix: tool state persistence and filtering, directly addressing the core problem from issue #709.
Description check ✅ Passed The description covers all required sections including type of change, changes made, testing, and related issues, though optional sections like Testing/Screenshots are not detailed.
Linked Issues check ✅ Passed The PR addresses all coding requirements from issue #709: preventing tool state reset on restart [UI + ToolDiscoveryService logic], filtering disabled tools from client lists [server-side filtering], exposing ReregisterToolsAsync API [interface + implementation], and batching reregistration calls [McpToolsSection].
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing issue #709: UI preference persistence, server-side tool filtering, transport client reregistration API, and supporting unit tests. No unrelated functionality was modified.

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

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

  • The hard-coded tool name sets and alias mappings in UnityInstanceMiddleware (_unity_managed_tool_names, _tool_alias_to_unity_target, _server_only_tool_names) may become a maintenance hotspot; consider centralizing these definitions or deriving them from a single Unity-side source to reduce the risk of drift.
  • In McpToolsSection.ReregisterToolsAsync, using ThreadPool.QueueUserWorkItem with client.ReregisterToolsAsync().Wait() introduces a sync-over-async pattern on a thread-pool thread; consider using Task.Run with await client.ReregisterToolsAsync() to keep it fully async and avoid potential blocking or deadlock issues.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The hard-coded tool name sets and alias mappings in `UnityInstanceMiddleware` (_unity_managed_tool_names, _tool_alias_to_unity_target, _server_only_tool_names) may become a maintenance hotspot; consider centralizing these definitions or deriving them from a single Unity-side source to reduce the risk of drift.
- In `McpToolsSection.ReregisterToolsAsync`, using `ThreadPool.QueueUserWorkItem` with `client.ReregisterToolsAsync().Wait()` introduces a sync-over-async pattern on a thread-pool thread; consider using `Task.Run` with `await client.ReregisterToolsAsync()` to keep it fully async and avoid potential blocking or deadlock issues.

## Individual Comments

### Comment 1
<location> `Server/src/transport/unity_instance_middleware.py:352-353` </location>
<code_context>
+            else:
+                # Multiple sessions without explicit selection: use a union so we don't
+                # hide tools that are valid in at least one visible Unity instance.
+                project_hashes = [
+                    session.hash
+                    for session in sessions.values()
+                    if getattr(session, "hash", None)
</code_context>

<issue_to_address>
**issue (bug_risk):** Guard against missing `hash` attribute when building `project_hashes` for multiple sessions.

In this comprehension, `session.hash` is accessed before the `if` condition is evaluated, so a session without a `hash` attribute will still raise `AttributeError`. Use `getattr` for both access and filtering, e.g.:
```python
project_hashes = [
    h
    for session in sessions.values()
    if (h := getattr(session, "hash", None))
]
```
or an equivalent version without the walrus operator.
</issue_to_address>

### Comment 2
<location> `MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs:509-530` </location>
<code_context>
             McpLog.Info($"[WebSocket] Sent {tools.Count} tools registration", false);
         }

+        public async Task ReregisterToolsAsync()
+        {
+            if (!IsConnected || _lifecycleCts == null)
+            {
+                McpLog.Warn("[WebSocket] Cannot reregister tools: not connected");
+                return;
+            }
+
+            try
+            {
+                await SendRegisterToolsAsync(_lifecycleCts.Token).ConfigureAwait(false);
+                McpLog.Info("[WebSocket] Tool reregistration completed", false);
+            }
+            catch (System.OperationCanceledException)
+            {
+                McpLog.Warn("[WebSocket] Tool reregistration cancelled");
+            }
+            catch (System.Exception ex)
+            {
+                McpLog.Error($"[WebSocket] Tool reregistration failed: {ex.Message}");
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Handle potential races where `_lifecycleCts` is disposed between the initial check and use.

In `ReregisterToolsAsync`, there’s a race between the `_lifecycleCts != null` check and `SendRegisterToolsAsync(_lifecycleCts.Token)`: if the CTS is disposed in between (e.g., during shutdown), you may get `ObjectDisposedException` instead of `OperationCanceledException`.

To harden this:
- capture `_lifecycleCts` into a local and use that consistently, and
- consider handling `ObjectDisposedException` the same as `OperationCanceledException` (treat as benign cancellation rather than an error) to avoid noisy shutdown logs.

```suggestion
        public async Task ReregisterToolsAsync()
        {
            // Capture CTS locally to avoid races with concurrent disposal/change
            var lifecycleCts = _lifecycleCts;

            if (!IsConnected || lifecycleCts == null)
            {
                McpLog.Warn("[WebSocket] Cannot reregister tools: not connected");
                return;
            }

            try
            {
                await SendRegisterToolsAsync(lifecycleCts.Token).ConfigureAwait(false);
                McpLog.Info("[WebSocket] Tool reregistration completed", false);
            }
            catch (System.OperationCanceledException)
            {
                McpLog.Warn("[WebSocket] Tool reregistration cancelled");
            }
            catch (System.ObjectDisposedException)
            {
                // Treat disposal during shutdown the same as cancellation to avoid noisy logs
                McpLog.Warn("[WebSocket] Tool reregistration cancelled");
            }
            catch (System.Exception ex)
            {
                McpLog.Error($"[WebSocket] Tool reregistration failed: {ex.Message}");
            }
        }
```
</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 +352 to +353
project_hashes = [
session.hash
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Guard against missing hash attribute when building project_hashes for multiple sessions.

In this comprehension, session.hash is accessed before the if condition is evaluated, so a session without a hash attribute will still raise AttributeError. Use getattr for both access and filtering, e.g.:

project_hashes = [
    h
    for session in sessions.values()
    if (h := getattr(session, "hash", None))
]

or an equivalent version without the walrus operator.

Comment on lines +509 to +530
public async Task ReregisterToolsAsync()
{
if (!IsConnected || _lifecycleCts == null)
{
McpLog.Warn("[WebSocket] Cannot reregister tools: not connected");
return;
}

try
{
await SendRegisterToolsAsync(_lifecycleCts.Token).ConfigureAwait(false);
McpLog.Info("[WebSocket] Tool reregistration completed", false);
}
catch (System.OperationCanceledException)
{
McpLog.Warn("[WebSocket] Tool reregistration cancelled");
}
catch (System.Exception ex)
{
McpLog.Error($"[WebSocket] Tool reregistration failed: {ex.Message}");
}
}
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): Handle potential races where _lifecycleCts is disposed between the initial check and use.

In ReregisterToolsAsync, there’s a race between the _lifecycleCts != null check and SendRegisterToolsAsync(_lifecycleCts.Token): if the CTS is disposed in between (e.g., during shutdown), you may get ObjectDisposedException instead of OperationCanceledException.

To harden this:

  • capture _lifecycleCts into a local and use that consistently, and
  • consider handling ObjectDisposedException the same as OperationCanceledException (treat as benign cancellation rather than an error) to avoid noisy shutdown logs.
Suggested change
public async Task ReregisterToolsAsync()
{
if (!IsConnected || _lifecycleCts == null)
{
McpLog.Warn("[WebSocket] Cannot reregister tools: not connected");
return;
}
try
{
await SendRegisterToolsAsync(_lifecycleCts.Token).ConfigureAwait(false);
McpLog.Info("[WebSocket] Tool reregistration completed", false);
}
catch (System.OperationCanceledException)
{
McpLog.Warn("[WebSocket] Tool reregistration cancelled");
}
catch (System.Exception ex)
{
McpLog.Error($"[WebSocket] Tool reregistration failed: {ex.Message}");
}
}
public async Task ReregisterToolsAsync()
{
// Capture CTS locally to avoid races with concurrent disposal/change
var lifecycleCts = _lifecycleCts;
if (!IsConnected || lifecycleCts == null)
{
McpLog.Warn("[WebSocket] Cannot reregister tools: not connected");
return;
}
try
{
await SendRegisterToolsAsync(lifecycleCts.Token).ConfigureAwait(false);
McpLog.Info("[WebSocket] Tool reregistration completed", false);
}
catch (System.OperationCanceledException)
{
McpLog.Warn("[WebSocket] Tool reregistration cancelled");
}
catch (System.ObjectDisposedException)
{
// Treat disposal during shutdown the same as cancellation to avoid noisy logs
McpLog.Warn("[WebSocket] Tool reregistration cancelled");
}
catch (System.Exception ex)
{
McpLog.Error($"[WebSocket] Tool reregistration failed: {ex.Message}");
}
}

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes issue #709 by persisting tool enabled/disabled state across Unity restarts and ensuring MCP clients only see Unity tools that are enabled in the Unity editor (while preserving server-only tools and selected aliases).

Changes:

  • Stop overwriting stored tool enabled/disabled preferences during tool discovery (EditorPrefs persistence fix).
  • Add server-side tool-list filtering in UnityInstanceMiddleware.on_list_tools() based on PluginHub-registered tool names, with alias handling.
  • Add Unity edit-mode tests and server characterization tests to cover persistence and tool-list filtering behavior.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
MCPForUnity/Editor/Services/ToolDiscoveryService.cs Fixes preference initialization so stored disabled states aren’t overwritten on discovery/restart.
MCPForUnity/Editor/Windows/Components/Tools/McpToolsSection.cs Triggers tool re-registration with the connected server when tool toggles change (including bulk enable/disable).
MCPForUnity/Editor/Services/Transport/IMcpTransportClient.cs Extends transport interface with ReregisterToolsAsync().
MCPForUnity/Editor/Services/Transport/TransportManager.cs Exposes GetClient() publicly so UI can request the active client instance.
MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs Implements tool re-registration by resending tool registration over the WS connection.
MCPForUnity/Editor/Services/Transport/Transports/StdioTransportClient.cs Implements no-op re-registration (stdio doesn’t support dynamic tool re-registration).
Server/src/transport/unity_instance_middleware.py Filters tool listing to Unity-enabled set (plus server-only + alias mapping) when in HTTP mode with PluginHub configured.
Server/tests/test_transport_characterization.py Adds characterization tests verifying filtering behavior and alias/server-only exceptions.
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/ToolDiscoveryServiceTests.cs Adds edit-mode tests ensuring tool state persistence and no override of stored disabled values.
TestProjects/UnityMCPTests/Assets/Tests/Editor.meta / .../State.meta / ...ToolDiscoveryServiceTests.cs.meta Adds Unity asset metadata for the new test folders/files.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +259 to +264
client.ReregisterToolsAsync().Wait();
}
}
catch (Exception ex)
{
McpLog.Warn($"Failed to reregister tools: {ex.Message}");
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

ReregisterToolsAsync() blocks on an async call using .Wait(). This can wrap exceptions in AggregateException (often resulting in an unhelpful One or more errors occurred message) and unnecessarily blocks a ThreadPool thread. Prefer an async flow (e.g., Task.Run(async () => await client.ReregisterToolsAsync())) or at least use GetAwaiter().GetResult() and log the inner exception details.

Suggested change
client.ReregisterToolsAsync().Wait();
}
}
catch (Exception ex)
{
McpLog.Warn($"Failed to reregister tools: {ex.Message}");
// Use GetAwaiter().GetResult() to avoid AggregateException wrapping
client.ReregisterToolsAsync().GetAwaiter().GetResult();
}
}
catch (Exception ex)
{
// Log full exception details, including any inner exceptions
if (ex is AggregateException aggregateException && aggregateException.InnerException != null)
{
McpLog.Warn($"Failed to reregister tools: {aggregateException.InnerException}");
}
else
{
McpLog.Warn($"Failed to reregister tools: {ex}");
}

Copilot uses AI. Check for mistakes.
try:
sessions_data = await PluginHub.get_sessions(user_id=user_id)
sessions = sessions_data.sessions if sessions_data else {}
except Exception:
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The except Exception: return None around PluginHub.get_sessions(...) swallows all errors without logging. This can make it hard to understand why tool filtering is skipped, and can hide real bugs. Consider logging (debug/warn) and re-raising SystemExit/KeyboardInterrupt (consistent with the rest of this file).

Suggested change
except Exception:
except (KeyboardInterrupt, SystemExit):
# Preserve process-control semantics.
raise
except Exception as exc:
logger.warning(
"Failed to fetch sessions from PluginHub for user %r; "
"skipping tool filtering: %s",
user_id,
exc,
)

Copilot uses AI. Check for mistakes.
for project_hash in project_hashes:
try:
registered_tools = await PluginHub.get_tools_for_project(project_hash)
except Exception:
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The except Exception: continue around PluginHub.get_tools_for_project(...) silently drops failures and can yield an empty enabled-tool set, causing the middleware to return the unfiltered tool list. Consider logging the exception (at least debug) so failures don't silently negate filtering behavior.

Suggested change
except Exception:
except Exception:
logger.debug(
"Failed to fetch tools for project %s in UnityInstanceMiddleware; skipping this project.",
project_hash,
exc_info=True,
)

Copilot uses AI. Check for mistakes.
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: 3

🤖 Fix all issues with AI agents
In `@MCPForUnity/Editor/Windows/Components/Tools/McpToolsSection.cs`:
- Around line 248-267: ReregisterToolsAsync currently hardcodes
TransportMode.Http when calling MCPServiceLocator.TransportManager.GetClient,
which ignores the user's configured transport; change it to use the active
transport mode from MCPServiceLocator.Bridge.ActiveMode when obtaining the
client (keep the existing null and IsConnected checks and the try/catch around
client.ReregisterToolsAsync().Wait()). Update references in the
ReregisterToolsAsync method to call GetClient with
MCPServiceLocator.Bridge.ActiveMode so reregistration respects the user's
preferred transport.

In `@Server/src/transport/unity_instance_middleware.py`:
- Around line 362-371: The current loop over project_hashes swallows all
exceptions from PluginHub.get_tools_for_project by using bare except and
continue; change the except to capture the exception as e (e.g., except
Exception as e) and log the failure (including project_hash and exception
details/traceback) before continuing so failures are discoverable — update the
except block around PluginHub.get_tools_for_project to call the module or
instance logger (e.g., logger.exception or logger.error with exc_info=True)
referencing project_hash and the caught exception, then continue to the next
hash.
- Around line 335-339: The try/except around PluginHub.get_sessions currently
swallows all exceptions; change it to "except Exception as e:" and log the
exception at debug (or debug/exception) level before returning None so
programming errors are visible; specifically update the block around
PluginHub.get_sessions/user_id to capture the exception into a variable (e) and
call the module's logger (e.g., logger.debug or logger.exception) with a clear
message including e and any contextual info (user_id, sessions_data) prior to
returning None.
🧹 Nitpick comments (5)
Server/src/transport/unity_instance_middleware.py (1)

58-94: Hardcoded tool name sets will drift as tools are added/removed.

The _unity_managed_tool_names, _tool_alias_to_unity_target, and _server_only_tool_names are static copies of the current tool inventory. New tools added in Unity won't appear here, and removed tools will linger. The fail-open approach in _is_tool_visible (line 401) mitigates this for new tools — they'll remain visible even when disabled, but won't be filterable until this list is updated.

Consider whether these sets could be derived dynamically (e.g., from the PluginHub registry or a shared manifest), or at least add a comment noting the maintenance requirement.

MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs (1)

509-530: ReregisterToolsAsync implementation is correct and well-guarded.

The pre-checks, delegation to SendRegisterToolsAsync, and structured exception handling are all appropriate. The broad catch (Exception) at line 526 provides a safety net for edge cases like the _lifecycleCts being disposed concurrently.

Minor style nit: Lines 522 and 526 use fully-qualified System.OperationCanceledException and System.Exception, but the file already has using System; at line 1, so the System. prefix is unnecessary. The rest of the file uses the unqualified forms (e.g., lines 326, 336, 558, 566).

Optional cleanup
-            catch (System.OperationCanceledException)
+            catch (OperationCanceledException)
             {
                 McpLog.Warn("[WebSocket] Tool reregistration cancelled");
             }
-            catch (System.Exception ex)
+            catch (Exception ex)
             {
                 McpLog.Error($"[WebSocket] Tool reregistration failed: {ex.Message}");
             }
MCPForUnity/Editor/Windows/Components/Tools/McpToolsSection.cs (1)

251-259: Consider Task.Run over ThreadPool.QueueUserWorkItem + .Wait().

.Wait() on a Task inside a thread pool callback works here but risks AggregateException wrapping. Task.Run(async () => await ...) is more idiomatic for fire-and-forget async and avoids synchronous blocking:

Suggested change
- ThreadPool.QueueUserWorkItem(_ =>
+ _ = Task.Run(async () =>
  {
      try
      {
          var transportManager = MCPServiceLocator.TransportManager;
          var client = transportManager.GetClient(TransportMode.Http);
          if (client != null && client.IsConnected)
          {
-             client.ReregisterToolsAsync().Wait();
+             await client.ReregisterToolsAsync();
          }
      }
      catch (Exception ex)
      {
          McpLog.Warn($"Failed to reregister tools: {ex.Message}");
      }
  });
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/ToolDiscoveryServiceTests.cs (2)

116-153: Test relies on the test project containing a built-in tool with AutoRegister=false.

Line 124 asserts that at least one such tool exists. If the test project's tool configuration ever changes (e.g., all built-in tools become AutoRegister=true), this test will fail with the message but no obvious link back to the cause. Consider adding a brief comment documenting which tool(s) this currently matches, to ease future debugging.


51-59: Redundant cleanup duplicates SetUp logic.

Lines 55-59 manually clean up the preference key, but SetUp (lines 17-22) already does this before each test. The duplicate is harmless but unnecessary.

Comment on lines +248 to 267
private void ReregisterToolsAsync()
{
// Fire and forget - don't block UI
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
var transportManager = MCPServiceLocator.TransportManager;
var client = transportManager.GetClient(TransportMode.Http);
if (client != null && client.IsConnected)
{
client.ReregisterToolsAsync().Wait();
}
}
catch (Exception ex)
{
McpLog.Warn($"Failed to reregister tools: {ex.Message}");
}
});
}
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if BridgeService exposes preferred mode publicly and how Stdio transport implements ReregisterToolsAsync
echo "=== BridgeControlService public API ==="
rg -n "PreferredMode|_preferredMode|ResolvePreferredMode" --type=cs -C2

echo ""
echo "=== StdioTransportClient.ReregisterToolsAsync ==="
ast-grep --pattern $'class StdioTransportClient {
  $$$
  ReregisterToolsAsync() {
    $$$
  }
  $$$
}'

echo ""
echo "=== All ReregisterToolsAsync implementations ==="
rg -n "ReregisterToolsAsync" --type=cs -C3

Repository: CoplayDev/unity-mcp

Length of output: 9942


🏁 Script executed:

#!/bin/bash
echo "=== MCPServiceLocator API ==="
rg -n "class MCPServiceLocator|public static.*BridgeService|BridgeService.*get" --type=cs -A2 -B2

echo ""
echo "=== TransportManager.GetClient implementation ==="
ast-grep --pattern $'GetClient($MODE) {
  $$$
}'

echo ""
echo "=== How GetClient is used in other places ==="
rg -n "GetClient\(" --type=cs -B2 -A2 | head -80

Repository: CoplayDev/unity-mcp

Length of output: 2246


🏁 Script executed:

#!/bin/bash
echo "=== MCPServiceLocator full implementation ==="
cat -n MCPForUnity/Editor/Services/MCPServiceLocator.cs

echo ""
echo "=== TransportManager.GetClient full implementation ==="
rg -n "public IMcpTransportClient GetClient" --type=cs -A10

Repository: CoplayDev/unity-mcp

Length of output: 7093


🏁 Script executed:

#!/bin/bash
echo "=== IBridgeControlService interface ==="
rg -n "interface IBridgeControlService" --type=cs -A20

echo ""
echo "=== TransportMode enum ==="
rg -n "enum TransportMode" --type=cs -A10

echo ""
echo "=== Checking if WebSocketTransportClient is actually used ==="
rg -n "WebSocketTransportClient|WebSocket" --type=cs | head -20

Repository: CoplayDev/unity-mcp

Length of output: 5587


Use the active transport mode instead of hardcoding Http.

ReregisterToolsAsync always targets TransportMode.Http (line 256), but this ignores the user's configured transport preference. If the user has configured Stdio as the active transport (via BridgeControlService.ResolvePreferredMode), the Http client may not be initialized, causing GetClient to return null and reregistration to silently fail.

Use MCPServiceLocator.Bridge.ActiveMode to respect the user's configuration:

Proposed fix
 private void ReregisterToolsAsync()
 {
     // Fire and forget - don't block UI
     ThreadPool.QueueUserWorkItem(_ =>
     {
         try
         {
             var transportManager = MCPServiceLocator.TransportManager;
-            var client = transportManager.GetClient(TransportMode.Http);
+            var mode = MCPServiceLocator.Bridge.ActiveMode;
+            if (!mode.HasValue)
+                return;
+            var client = transportManager.GetClient(mode.Value);
             if (client != null && client.IsConnected)
             {
                 client.ReregisterToolsAsync().Wait();
             }
         }
         catch (Exception ex)
         {
             McpLog.Warn($"Failed to reregister tools: {ex.Message}");
         }
     });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private void ReregisterToolsAsync()
{
// Fire and forget - don't block UI
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
var transportManager = MCPServiceLocator.TransportManager;
var client = transportManager.GetClient(TransportMode.Http);
if (client != null && client.IsConnected)
{
client.ReregisterToolsAsync().Wait();
}
}
catch (Exception ex)
{
McpLog.Warn($"Failed to reregister tools: {ex.Message}");
}
});
}
private void ReregisterToolsAsync()
{
// Fire and forget - don't block UI
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
var transportManager = MCPServiceLocator.TransportManager;
var mode = MCPServiceLocator.Bridge.ActiveMode;
if (!mode.HasValue)
return;
var client = transportManager.GetClient(mode.Value);
if (client != null && client.IsConnected)
{
client.ReregisterToolsAsync().Wait();
}
}
catch (Exception ex)
{
McpLog.Warn($"Failed to reregister tools: {ex.Message}");
}
});
}
🤖 Prompt for AI Agents
In `@MCPForUnity/Editor/Windows/Components/Tools/McpToolsSection.cs` around lines
248 - 267, ReregisterToolsAsync currently hardcodes TransportMode.Http when
calling MCPServiceLocator.TransportManager.GetClient, which ignores the user's
configured transport; change it to use the active transport mode from
MCPServiceLocator.Bridge.ActiveMode when obtaining the client (keep the existing
null and IsConnected checks and the try/catch around
client.ReregisterToolsAsync().Wait()). Update references in the
ReregisterToolsAsync method to call GetClient with
MCPServiceLocator.Bridge.ActiveMode so reregistration respects the user's
preferred transport.

Comment on lines +335 to +339
try:
sessions_data = await PluginHub.get_sessions(user_id=user_id)
sessions = sessions_data.sessions if sessions_data else {}
except Exception:
return None
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silent exception swallowing hides bugs.

The bare except Exception: return None at line 338 will silently swallow programming errors (e.g., TypeError, KeyError from unexpected data shapes). At minimum, log the exception at debug level so it's diagnosable.

Proposed fix
             try:
                 sessions_data = await PluginHub.get_sessions(user_id=user_id)
                 sessions = sessions_data.sessions if sessions_data else {}
-            except Exception:
+            except Exception as exc:
+                logger.debug(
+                    "Failed to fetch sessions for tool filtering: %s",
+                    exc,
+                    exc_info=True,
+                )
                 return None
🧰 Tools
🪛 Ruff (0.15.0)

[warning] 338-338: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In `@Server/src/transport/unity_instance_middleware.py` around lines 335 - 339,
The try/except around PluginHub.get_sessions currently swallows all exceptions;
change it to "except Exception as e:" and log the exception at debug (or
debug/exception) level before returning None so programming errors are visible;
specifically update the block around PluginHub.get_sessions/user_id to capture
the exception into a variable (e) and call the module's logger (e.g.,
logger.debug or logger.exception) with a clear message including e and any
contextual info (user_id, sessions_data) prior to returning None.

Comment on lines +362 to +371
for project_hash in project_hashes:
try:
registered_tools = await PluginHub.get_tools_for_project(project_hash)
except Exception:
continue

for tool in registered_tools:
tool_name = getattr(tool, "name", None)
if isinstance(tool_name, str) and tool_name:
enabled_tool_names.add(tool_name)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

try/except/continue silently discards errors — add logging.

Ruff S112 flags this pattern. If get_tools_for_project fails for one hash due to a bug (not a transient network error), this silently skips it, making issues very hard to diagnose.

Proposed fix
         for project_hash in project_hashes:
             try:
                 registered_tools = await PluginHub.get_tools_for_project(project_hash)
-            except Exception:
+            except Exception as exc:
+                logger.debug(
+                    "Failed to get tools for project hash %s: %s",
+                    project_hash,
+                    exc,
+                    exc_info=True,
+                )
                 continue
🧰 Tools
🪛 Ruff (0.15.0)

[error] 365-366: try-except-continue detected, consider logging the exception

(S112)


[warning] 365-365: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In `@Server/src/transport/unity_instance_middleware.py` around lines 362 - 371,
The current loop over project_hashes swallows all exceptions from
PluginHub.get_tools_for_project by using bare except and continue; change the
except to capture the exception as e (e.g., except Exception as e) and log the
failure (including project_hash and exception details/traceback) before
continuing so failures are discoverable — update the except block around
PluginHub.get_tools_for_project to call the module or instance logger (e.g.,
logger.exception or logger.error with exc_info=True) referencing project_hash
and the caught exception, then continue to the next hash.

@whatevertogo
Copy link
Contributor Author

Closing to recreate with proper template

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 10754e5819

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

enabled_tool_names: set[str] = set()
for project_hash in project_hashes:
try:
registered_tools = await PluginHub.get_tools_for_project(project_hash)

Choose a reason for hiding this comment

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

P1 Badge Scope tool lookup to the current user in hosted mode

In remote-hosted deployments, this lookup ignores user_id even though _resolve_enabled_tool_names_for_context already derived it, so filtering uses PluginHub.get_tools_for_project(project_hash) with only a hash. PluginRegistry stores hosted sessions under (user_id, project_hash), which means hash-only resolution can return no session and the enabled set stays empty; on_list_tools then falls back to returning the full unfiltered tool list for that user.

Useful? React with 👍 / 👎.

if isinstance(tool_name, str) and tool_name:
enabled_tool_names.add(tool_name)

return enabled_tool_names or None

Choose a reason for hiding this comment

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

P2 Badge Keep filtering when Unity reports zero enabled tools

This converts an empty enabled-tool set into None, and on_list_tools interprets that as 'no data, skip filtering'. If a Unity instance intentionally disables all Unity-managed tools and re-registers an empty tool list, the middleware will incorrectly return the full unfiltered list instead of hiding disabled tools, so tool listing no longer matches Unity’s actual enabled state.

Useful? React with 👍 / 👎.

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.

Bug: Disabled Tools Reappear and Still Show Up in Client Lists

1 participant