Skip to content

Fix TryGetResourceToolMap cache miss causing perpetual tools/list refresh loop#14539

Merged
mitchdenny merged 1 commit intorelease/13.2from
fix/mcp-tool-refresh-cache-miss
Feb 19, 2026
Merged

Fix TryGetResourceToolMap cache miss causing perpetual tools/list refresh loop#14539
mitchdenny merged 1 commit intorelease/13.2from
fix/mcp-tool-refresh-cache-miss

Conversation

@mitchdenny
Copy link
Member

Summary

Fixes #14538

TryGetResourceToolMap always returned false because it compared _selectedAppHostPath against _auxiliaryBackchannelMonitor.SelectedAppHostPath, which is only set by explicit select_apphost tool calls (usually null). After RefreshResourceToolMapAsync sets _selectedAppHostPath to the actual AppHost path, the comparison null != "/path/to/AppHost" always failed — so every tools/list call triggered a full refresh instead of using the cached map.

This caused ~3,800 refresh cycles per 3.5 seconds and generated ~130GB of log data in a 4-hour session.

Root Cause

// Before: SelectedAppHostPath is only set by explicit select_apphost (usually null)
if (_invalidated || _selectedAppHostPath != _auxiliaryBackchannelMonitor.SelectedAppHostPath)

Fix

// After: SelectedConnection uses full selection logic (explicit > in-scope > fallback)
if (_invalidated || _selectedAppHostPath != _auxiliaryBackchannelMonitor.SelectedConnection?.AppHostInfo?.AppHostPath)

Uses SelectedConnection which applies the same selection logic that RefreshResourceToolMapAsync uses via GetSelectedConnectionAsync, ensuring the comparison matches.

Testing

  • Added McpServer_ListTools_CachesResourceToolMap_WhenConnectionUnchanged test verifying that GetResourceSnapshotsAsync is only called once across two consecutive ListToolsAsync calls (proving cache hit on second call)
  • All 9 AgentMcpCommand tests pass

Related

Copilot AI review requested due to automatic review settings February 18, 2026 02:39
@github-actions
Copy link
Contributor

github-actions bot commented Feb 18, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14539

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 14539"

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 an MCP server performance bug where TryGetResourceToolMap never hit the cache due to comparing against an explicit-only selection value, triggering repeated full refreshes during tools/list.

Changes:

  • Update TryGetResourceToolMap to compare the cached AppHost path against the effective selected connection’s AppHost path.
  • Add a unit test ensuring consecutive ListToolsAsync calls reuse the cached resource tool map (no redundant snapshot fetch).
  • Extend the test backchannel to allow intercepting GetResourceSnapshotsAsync calls for call-count assertions.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/Aspire.Cli/Mcp/McpResourceToolRefreshService.cs Fixes cache validation to use the effective selected connection’s AppHost path.
tests/Aspire.Cli.Tests/TestServices/TestAppHostAuxiliaryBackchannel.cs Adds a handler hook to count/override GetResourceSnapshotsAsync calls in tests.
tests/Aspire.Cli.Tests/Commands/AgentMcpCommandTests.cs Adds a regression test verifying cache hits across repeated tools/list.

@mitchdenny mitchdenny self-assigned this Feb 18, 2026
@mitchdenny mitchdenny added this to the 13.2 milestone Feb 18, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 18, 2026

🎬 CLI E2E Test Recordings

The following terminal recordings are available for commit 25b99a6:

Test Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
CreateAndDeployToDockerCompose ▶️ View Recording
CreateAndDeployToDockerComposeInteractive ▶️ View Recording
CreateAndPublishToKubernetes ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateEmptyAppHostProject ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateStartWaitAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
ResourcesCommandShowsRunningResources ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording

📹 Recordings uploaded automatically from CI run #22125121406

lock (_lock)
{
if (_invalidated || _selectedAppHostPath != _auxiliaryBackchannelMonitor.SelectedAppHostPath)
if (_invalidated || _selectedAppHostPath != _auxiliaryBackchannelMonitor.SelectedConnection?.AppHostInfo?.AppHostPath)
Copy link
Member

Choose a reason for hiding this comment

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

I think the API here is confusing. SelectedConnection to an app host and SelectedAppHostPath have different meanings but similar names

Copy link
Member

@JamesNK JamesNK Feb 18, 2026

Choose a reason for hiding this comment

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

Now there is also ResolvedAppHostPath which gets value from SelectedConnection. But SelectedAppHostPath doesn’t…

Either SelectedConnection or SelectedAppHostPath need a new name. And ResolvedAppHostPath should match name with SelectedConnection

Copy link
Member

Choose a reason for hiding this comment

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

SelectedAppHostPath Is what is specified via MCP. Why not call it McpSpecifiedAppHostPath?

…resh loop

TryGetResourceToolMap always returned false because it compared
_selectedAppHostPath against _auxiliaryBackchannelMonitor.SelectedAppHostPath,
which is only set by explicit select_apphost calls (usually null). After
RefreshResourceToolMapAsync sets _selectedAppHostPath to the connection's actual
path, the comparison null != "/path/to/AppHost" always failed, so every
tools/list call triggered a full refresh instead of using the cached map.

Fix: Add ResolvedAppHostPath property to IAuxiliaryBackchannelMonitor that
returns SelectedConnection?.AppHostInfo?.AppHostPath, and compare against that.
Rename field to _lastRefreshedAppHostPath for clarity.

Fixes #14538

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny mitchdenny force-pushed the fix/mcp-tool-refresh-cache-miss branch from d8249d2 to 25b99a6 Compare February 18, 2026 03:19
@mitchdenny mitchdenny merged commit 17cae0d into release/13.2 Feb 19, 2026
343 checks passed
@mitchdenny mitchdenny deleted the fix/mcp-tool-refresh-cache-miss branch February 19, 2026 02:30
@dotnet-policy-service dotnet-policy-service bot modified the milestone: 13.2 Feb 19, 2026
radical pushed a commit to radical/aspire that referenced this pull request Feb 19, 2026
…resh loop (dotnet#14539)

TryGetResourceToolMap always returned false because it compared
_selectedAppHostPath against _auxiliaryBackchannelMonitor.SelectedAppHostPath,
which is only set by explicit select_apphost calls (usually null). After
RefreshResourceToolMapAsync sets _selectedAppHostPath to the connection's actual
path, the comparison null != "/path/to/AppHost" always failed, so every
tools/list call triggered a full refresh instead of using the cached map.

Fix: Add ResolvedAppHostPath property to IAuxiliaryBackchannelMonitor that
returns SelectedConnection?.AppHostInfo?.AppHostPath, and compare against that.
Rename field to _lastRefreshedAppHostPath for clarity.

Fixes dotnet#14538

Co-authored-by: Mitch Denny <mitch@mitchdeny.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP server: TryGetResourceToolMap cache never hits due to SelectedAppHostPath mismatch, causing perpetual tools/list refresh loop

3 participants