-
Notifications
You must be signed in to change notification settings - Fork 744
Fix: #709 - Tool state persistence and filtering #722
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c513072
09c61e1
10754e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,9 +1,11 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Collections.Generic; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Linq; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Threading; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using MCPForUnity.Editor.Constants; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using MCPForUnity.Editor.Helpers; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using MCPForUnity.Editor.Services; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using MCPForUnity.Editor.Services.Transport; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using MCPForUnity.Editor.Tools; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using UnityEditor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using UnityEngine.UIElements; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -223,23 +225,61 @@ private VisualElement CreateToolRow(ToolMetadata tool) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return row; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private void HandleToggleChange(ToolMetadata tool, bool enabled, bool updateSummary = true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private void HandleToggleChange( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ToolMetadata tool, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool enabled, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool updateSummary = true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool reregisterTools = true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MCPServiceLocator.ToolDiscovery.SetToolEnabled(tool.Name, enabled); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (updateSummary) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UpdateSummary(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (reregisterTools) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Trigger tool reregistration with connected MCP server | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ReregisterToolsAsync(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+259
to
+264
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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}"); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -C3Repository: 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 -80Repository: 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 -A10Repository: 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 -20Repository: 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.
| 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.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -55,6 +55,43 @@ def __init__(self): | |||||||||||||||||||||||
| super().__init__() | ||||||||||||||||||||||||
| self._active_by_key: dict[str, str] = {} | ||||||||||||||||||||||||
| self._lock = RLock() | ||||||||||||||||||||||||
| self._unity_managed_tool_names = { | ||||||||||||||||||||||||
| "batch_execute", | ||||||||||||||||||||||||
| "execute_menu_item", | ||||||||||||||||||||||||
| "find_gameobjects", | ||||||||||||||||||||||||
| "get_test_job", | ||||||||||||||||||||||||
| "manage_asset", | ||||||||||||||||||||||||
| "manage_components", | ||||||||||||||||||||||||
| "manage_editor", | ||||||||||||||||||||||||
| "manage_gameobject", | ||||||||||||||||||||||||
| "manage_material", | ||||||||||||||||||||||||
| "manage_prefabs", | ||||||||||||||||||||||||
| "manage_scene", | ||||||||||||||||||||||||
| "manage_script", | ||||||||||||||||||||||||
| "manage_scriptable_object", | ||||||||||||||||||||||||
| "manage_shader", | ||||||||||||||||||||||||
| "manage_texture", | ||||||||||||||||||||||||
| "manage_vfx", | ||||||||||||||||||||||||
| "read_console", | ||||||||||||||||||||||||
| "refresh_unity", | ||||||||||||||||||||||||
| "run_tests", | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| self._tool_alias_to_unity_target = { | ||||||||||||||||||||||||
| # Server-side script helpers route to Unity's manage_script command. | ||||||||||||||||||||||||
| "apply_text_edits": "manage_script", | ||||||||||||||||||||||||
| "create_script": "manage_script", | ||||||||||||||||||||||||
| "delete_script": "manage_script", | ||||||||||||||||||||||||
| "find_in_file": "manage_script", | ||||||||||||||||||||||||
| "get_sha": "manage_script", | ||||||||||||||||||||||||
| "script_apply_edits": "manage_script", | ||||||||||||||||||||||||
| "validate_script": "manage_script", | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| self._server_only_tool_names = { | ||||||||||||||||||||||||
| "debug_request_context", | ||||||||||||||||||||||||
| "execute_custom_tool", | ||||||||||||||||||||||||
| "manage_script_capabilities", | ||||||||||||||||||||||||
| "set_active_instance", | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def get_session_key(self, ctx) -> str: | ||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||
|
|
@@ -260,3 +297,108 @@ async def on_read_resource(self, context: MiddlewareContext, call_next): | |||||||||||||||||||||||
| """Inject active Unity instance into resource context if available.""" | ||||||||||||||||||||||||
| await self._inject_unity_instance(context) | ||||||||||||||||||||||||
| return await call_next(context) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| async def on_list_tools(self, context: MiddlewareContext, call_next): | ||||||||||||||||||||||||
| """Filter MCP tool listing to the Unity-enabled set when session data is available.""" | ||||||||||||||||||||||||
| await self._inject_unity_instance(context) | ||||||||||||||||||||||||
| tools = await call_next(context) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if not self._should_filter_tool_listing(): | ||||||||||||||||||||||||
| return tools | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| enabled_tool_names = await self._resolve_enabled_tool_names_for_context(context) | ||||||||||||||||||||||||
| if not enabled_tool_names: | ||||||||||||||||||||||||
| return tools | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| filtered = [] | ||||||||||||||||||||||||
| for tool in tools: | ||||||||||||||||||||||||
| tool_name = getattr(tool, "name", None) | ||||||||||||||||||||||||
| if self._is_tool_visible(tool_name, enabled_tool_names): | ||||||||||||||||||||||||
| filtered.append(tool) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return filtered | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def _should_filter_tool_listing(self) -> bool: | ||||||||||||||||||||||||
| transport = (config.transport_mode or "stdio").lower() | ||||||||||||||||||||||||
| return transport == "http" and PluginHub.is_configured() | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| async def _resolve_enabled_tool_names_for_context( | ||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||
| context: MiddlewareContext, | ||||||||||||||||||||||||
| ) -> set[str] | None: | ||||||||||||||||||||||||
| ctx = context.fastmcp_context | ||||||||||||||||||||||||
| user_id = ctx.get_state("user_id") if config.http_remote_hosted else None | ||||||||||||||||||||||||
| active_instance = ctx.get_state("unity_instance") | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| project_hashes = self._resolve_candidate_project_hashes(active_instance) | ||||||||||||||||||||||||
| if not project_hashes: | ||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||
| sessions_data = await PluginHub.get_sessions(user_id=user_id) | ||||||||||||||||||||||||
| sessions = sessions_data.sessions if sessions_data else {} | ||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
| 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, | |
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 👍 / 👎.
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
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.
| except Exception: | |
| except Exception: | |
| logger.debug( | |
| "Failed to fetch tools for project %s in UnityInstanceMiddleware; skipping this project.", | |
| project_hash, | |
| exc_info=True, | |
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 👍 / 👎.
There was a problem hiding this comment.
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
_lifecycleCtsis disposed between the initial check and use.In
ReregisterToolsAsync, there’s a race between the_lifecycleCts != nullcheck andSendRegisterToolsAsync(_lifecycleCts.Token): if the CTS is disposed in between (e.g., during shutdown), you may getObjectDisposedExceptioninstead ofOperationCanceledException.To harden this:
_lifecycleCtsinto a local and use that consistently, andObjectDisposedExceptionthe same asOperationCanceledException(treat as benign cancellation rather than an error) to avoid noisy shutdown logs.