diff --git a/src/Aspire.Cli/Backchannel/IAuxiliaryBackchannelMonitor.cs b/src/Aspire.Cli/Backchannel/IAuxiliaryBackchannelMonitor.cs
index 36f0a90d257..5ab0d332686 100644
--- a/src/Aspire.Cli/Backchannel/IAuxiliaryBackchannelMonitor.cs
+++ b/src/Aspire.Cli/Backchannel/IAuxiliaryBackchannelMonitor.cs
@@ -31,6 +31,11 @@ internal interface IAuxiliaryBackchannelMonitor
///
IAppHostAuxiliaryBackchannel? SelectedConnection { get; }
+ ///
+ /// Gets the AppHost path of the currently resolved connection, or null if no connection is available.
+ ///
+ string? ResolvedAppHostPath => SelectedConnection?.AppHostInfo?.AppHostPath;
+
///
/// Gets all connections that are within the scope of the specified working directory.
///
diff --git a/src/Aspire.Cli/Mcp/McpResourceToolRefreshService.cs b/src/Aspire.Cli/Mcp/McpResourceToolRefreshService.cs
index e3d3c368333..31e633058d2 100644
--- a/src/Aspire.Cli/Mcp/McpResourceToolRefreshService.cs
+++ b/src/Aspire.Cli/Mcp/McpResourceToolRefreshService.cs
@@ -20,7 +20,7 @@ internal sealed class McpResourceToolRefreshService : IMcpResourceToolRefreshSer
private McpServer? _server;
private Dictionary _resourceToolMap = new(StringComparer.Ordinal);
private bool _invalidated = true;
- private string? _selectedAppHostPath;
+ private string? _lastRefreshedAppHostPath;
public McpResourceToolRefreshService(
IAuxiliaryBackchannelMonitor auxiliaryBackchannelMonitor,
@@ -35,7 +35,7 @@ public bool TryGetResourceToolMap(out IReadOnlyDictionary
+ {
+ Interlocked.Increment(ref getResourceSnapshotsCallCount);
+ return Task.FromResult(new List
+ {
+ new ResourceSnapshot
+ {
+ Name = "db-mcp-xyz",
+ DisplayName = "db-mcp",
+ ResourceType = "Container",
+ State = "Running",
+ McpServer = new ResourceSnapshotMcpServer
+ {
+ EndpointUrl = "http://localhost:8080/mcp",
+ Tools =
+ [
+ new Tool
+ {
+ Name = "query_db",
+ Description = "Query the database"
+ }
+ ]
+ }
+ }
+ });
+ }
+ };
+
+ _backchannelMonitor.AddConnection(mockBackchannel.Hash, mockBackchannel.SocketPath, mockBackchannel);
+
+ // Act - Call ListTools twice
+ var tools1 = await _mcpClient.ListToolsAsync(cancellationToken: _cts.Token).DefaultTimeout();
+ var tools2 = await _mcpClient.ListToolsAsync(cancellationToken: _cts.Token).DefaultTimeout();
+
+ // Assert - Both calls return the resource tool
+ Assert.Contains(tools1, t => t.Name == "db_mcp_query_db");
+ Assert.Contains(tools2, t => t.Name == "db_mcp_query_db");
+
+ // The resource tool map should be cached after the first call,
+ // so GetResourceSnapshotsAsync should only be called once (during the first refresh).
+ // Before the fix, TryGetResourceToolMap always returned false due to
+ // SelectedAppHostPath vs SelectedConnection path mismatch, causing every
+ // ListTools call to trigger a full refresh.
+ Assert.Equal(1, getResourceSnapshotsCallCount);
+ }
+
[Fact]
public async Task McpServer_CallTool_UnknownTool_ReturnsError()
{
diff --git a/tests/Aspire.Cli.Tests/TestServices/TestAppHostAuxiliaryBackchannel.cs b/tests/Aspire.Cli.Tests/TestServices/TestAppHostAuxiliaryBackchannel.cs
index 31c7ea06dd8..fe266efc374 100644
--- a/tests/Aspire.Cli.Tests/TestServices/TestAppHostAuxiliaryBackchannel.cs
+++ b/tests/Aspire.Cli.Tests/TestServices/TestAppHostAuxiliaryBackchannel.cs
@@ -46,6 +46,12 @@ internal sealed class TestAppHostAuxiliaryBackchannel : IAppHostAuxiliaryBackcha
///
public Func?, CancellationToken, Task>? CallResourceMcpToolHandler { get; set; }
+ ///
+ /// Gets or sets the function to call when GetResourceSnapshotsAsync is invoked.
+ /// If null, returns the ResourceSnapshots list.
+ ///
+ public Func>>? GetResourceSnapshotsHandler { get; set; }
+
public Task GetDashboardUrlsAsync(CancellationToken cancellationToken = default)
{
return Task.FromResult(DashboardUrlsState);
@@ -53,6 +59,11 @@ internal sealed class TestAppHostAuxiliaryBackchannel : IAppHostAuxiliaryBackcha
public Task> GetResourceSnapshotsAsync(CancellationToken cancellationToken = default)
{
+ if (GetResourceSnapshotsHandler is not null)
+ {
+ return GetResourceSnapshotsHandler(cancellationToken);
+ }
+
return Task.FromResult(ResourceSnapshots);
}