-
Notifications
You must be signed in to change notification settings - Fork 816
Description
Description
After the fix in #14494 (which removed notification sending from HandleListToolsAsync), the MCP server still enters an infinite tools/list → tools/list_changed loop under certain conditions. The fix in #14494 addressed one notification path, but a second root cause remains: the TryGetResourceToolMap cache never returns a hit, causing every tools/list request to trigger a full RefreshResourceToolMapAsync.
Root Cause
In McpResourceToolRefreshService.TryGetResourceToolMap() (line 38):
if (_invalidated || _selectedAppHostPath != _auxiliaryBackchannelMonitor.SelectedAppHostPath)- After
RefreshResourceToolMapAsynccompletes,_selectedAppHostPathis set to the connected AppHost's actual path (e.g.,/path/to/AppHost.csproj) viaconnection.AppHostInfo?.AppHostPath. - But
_auxiliaryBackchannelMonitor.SelectedAppHostPathis alwaysnullunless the user explicitly calls theselect_apphosttool — it is only set bySelectAppHostTool. - So
null != "/path/to/AppHost.csproj"→ always returns false → the cache is never used.
This means every tools/list call triggers RefreshResourceToolMapAsync → GetSelectedConnectionAsync → ScanAsync, doing redundant work and generating log noise. Combined with the client detecting changes in the response (e.g., due to resource tool oscillation or JSON ordering differences), this creates an infinite loop.
Evidence
Log analysis from ~/.copilot/logs shows:
- 60GB and 58GB log files from a single ~4 hour session
- ~3,800 tool refresh cycles per 3.5 seconds (84% of all log lines are refresh-related)
- The loop starts immediately on MCP server initialization and never stops
- Pattern:
tools/list_changed notification → client calls tools/list → handler refreshes (cache miss) → handler completes → another tools/list_changed → repeat
Proposed Fix
Change line 38 to compare _selectedAppHostPath against the effective selected connection (which applies the full selection logic: explicit > in-scope > fallback), not the explicit-only SelectedAppHostPath:
// Before (always mismatches when no explicit selection):
if (_invalidated || _selectedAppHostPath != _auxiliaryBackchannelMonitor.SelectedAppHostPath)
// After (compares against the actual connection that would be used):
if (_invalidated || _selectedAppHostPath != _auxiliaryBackchannelMonitor.SelectedConnection?.AppHostInfo?.AppHostPath)This is a one-line fix that ensures the cache is used when the effective connection has not changed.
Environment
- Aspire CLI: 13.2.0-preview.1.26115.1 (commit c62d96b, includes Fix MCP server tools/list infinite loop caused by notification race condition #14494 fix)
- OS: Linux
- MCP client: VS Code Copilot CLI (github-copilot-developer 1.0.0)
- Condition: At least one AppHost running (not necessarily in scope of the MCP server's working directory)