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
RefreshResourceToolMapAsync completes, _selectedAppHostPath is set to the connected AppHost's actual path (e.g., /path/to/AppHost.csproj) via connection.AppHostInfo?.AppHostPath.
- But
_auxiliaryBackchannelMonitor.SelectedAppHostPath is always null unless the user explicitly calls the select_apphost tool — it is only set by SelectAppHostTool.
- 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
Description
After the fix in #14494 (which removed notification sending from
HandleListToolsAsync), the MCP server still enters an infinitetools/list→tools/list_changedloop under certain conditions. The fix in #14494 addressed one notification path, but a second root cause remains: theTryGetResourceToolMapcache never returns a hit, causing everytools/listrequest to trigger a fullRefreshResourceToolMapAsync.Root Cause
In
McpResourceToolRefreshService.TryGetResourceToolMap()(line 38):RefreshResourceToolMapAsynccompletes,_selectedAppHostPathis set to the connected AppHost's actual path (e.g.,/path/to/AppHost.csproj) viaconnection.AppHostInfo?.AppHostPath._auxiliaryBackchannelMonitor.SelectedAppHostPathis alwaysnullunless the user explicitly calls theselect_apphosttool — it is only set bySelectAppHostTool.null != "/path/to/AppHost.csproj"→ always returns false → the cache is never used.This means every
tools/listcall triggersRefreshResourceToolMapAsync→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/logsshows:tools/list_changed notification → client calls tools/list → handler refreshes (cache miss) → handler completes → another tools/list_changed → repeatProposed Fix
Change line 38 to compare
_selectedAppHostPathagainst the effective selected connection (which applies the full selection logic: explicit > in-scope > fallback), not the explicit-onlySelectedAppHostPath:This is a one-line fix that ensures the cache is used when the effective connection has not changed.
Environment