From c9e7df6e58dccdfb3defa01087e574aa346b73d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 22:20:21 +0000 Subject: [PATCH 1/5] Initial plan From 01d34241c3417d7d12efe544610a8476782589dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 22:36:07 +0000 Subject: [PATCH 2/5] Update MCP packages to 1.0.0 and fix breaking API changes - Update ModelContextProtocol and ModelContextProtocol.AspNetCore from 0.8.0-preview.1 to 1.0.0 - Update Microsoft.Extensions.* transitive dependencies from 10.0.2 to 10.0.3 - Migrate AddListToolsFilter/AddCallToolFilter to WithRequestFilters API Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> --- Directory.Packages.props | 4 ++-- eng/Versions.props | 14 ++++++------ src/Aspire.Dashboard/Mcp/McpExtensions.cs | 27 ++++++++++++----------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 03cc81a3c29..5b3f472aafa 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -110,8 +110,8 @@ - - + + diff --git a/eng/Versions.props b/eng/Versions.props index 00baed1bab2..1f58062f866 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -82,18 +82,18 @@ 10.0.2 10.0.2 - 10.0.2 + 10.0.3 10.0.2 10.0.2 - 10.0.2 + 10.0.3 10.0.2 - 10.0.2 - 10.0.2 - 10.0.2 - 10.0.2 + 10.0.3 + 10.0.3 + 10.0.3 + 10.0.3 10.0.2 10.0.2 - 10.0.2 + 10.0.3 diff --git a/src/Aspire.Dashboard/Mcp/McpExtensions.cs b/src/Aspire.Dashboard/Mcp/McpExtensions.cs index 6c2dd726ea7..a307bf91b70 100644 --- a/src/Aspire.Dashboard/Mcp/McpExtensions.cs +++ b/src/Aspire.Dashboard/Mcp/McpExtensions.cs @@ -48,19 +48,20 @@ public static IMcpServerBuilder AddAspireMcpTools(this IServiceCollection servic } builder - .AddListToolsFilter((next) => async (RequestContext request, CancellationToken cancellationToken) => - { - // Calls here are via the tools/list endpoint. See https://modelcontextprotocol.info/docs/concepts/tools/ - // There is no tool name so we hardcode name to list_tools here so we can reuse the same event. - // - // We want to track when users list tools as it's an indicator of whether Aspire MCP is configured (client tools refresh tools via it). - // It's called even if no Aspire tools end up being used. - return await RecordCallToolNameAsync(next, request, "list_tools", cancellationToken).ConfigureAwait(false); - }) - .AddCallToolFilter((next) => async (RequestContext request, CancellationToken cancellationToken) => - { - return await RecordCallToolNameAsync(next, request, request.Params?.Name, cancellationToken).ConfigureAwait(false); - }); + .WithRequestFilters(filters => filters + .AddListToolsFilter((next) => async (RequestContext request, CancellationToken cancellationToken) => + { + // Calls here are via the tools/list endpoint. See https://modelcontextprotocol.info/docs/concepts/tools/ + // There is no tool name so we hardcode name to list_tools here so we can reuse the same event. + // + // We want to track when users list tools as it's an indicator of whether Aspire MCP is configured (client tools refresh tools via it). + // It's called even if no Aspire tools end up being used. + return await RecordCallToolNameAsync(next, request, "list_tools", cancellationToken).ConfigureAwait(false); + }) + .AddCallToolFilter((next) => async (RequestContext request, CancellationToken cancellationToken) => + { + return await RecordCallToolNameAsync(next, request, request.Params?.Name, cancellationToken).ConfigureAwait(false); + })); return builder; } From 68f3b2fd66e4bfbc5d13974ace1971b1ff63256c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 22:45:36 +0000 Subject: [PATCH 3/5] Fix MCP integration tests for protocol changes in 1.0.0 MCP 1.0.0 requires an initialize handshake before other requests. Updated tests to properly initialize the MCP session first. Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> --- .../Integration/McpServiceTests.cs | 122 ++++++++++++++++-- .../Integration/StartupTests.cs | 3 +- 2 files changed, 116 insertions(+), 9 deletions(-) diff --git a/tests/Aspire.Dashboard.Tests/Integration/McpServiceTests.cs b/tests/Aspire.Dashboard.Tests/Integration/McpServiceTests.cs index 16a9a0b7e65..34beaccdcc7 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/McpServiceTests.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/McpServiceTests.cs @@ -32,7 +32,8 @@ public async Task CallService_McpEndPoint_Success() using var httpClient = IntegrationTestHelpers.CreateHttpClient($"http://{app.McpEndPointAccessor().EndPoint}"); - var request = CreateListToolsRequest(); + var sessionId = await InitializeSessionAsync(httpClient); + var request = CreateListToolsRequest(sessionId); // Act var responseMessage = await httpClient.SendAsync(request).DefaultTimeout(TestConstants.LongTimeoutDuration); @@ -105,11 +106,42 @@ public async Task CallService_McpEndPoint_RequiredApiKeySent_Success() using var httpClient = IntegrationTestHelpers.CreateHttpClient($"http://{app.McpEndPointAccessor().EndPoint}"); - var requestMessage = CreateListToolsRequest(); + var requestMessage = CreateInitializeRequest(); requestMessage.Headers.TryAddWithoutValidation(McpApiKeyAuthenticationHandler.McpApiKeyHeaderName, apiKey); // Act - var responseMessage = await httpClient.SendAsync(requestMessage).DefaultTimeout(TestConstants.LongTimeoutDuration); + var initResponse = await httpClient.SendAsync(requestMessage).DefaultTimeout(TestConstants.LongTimeoutDuration); + initResponse.EnsureSuccessStatusCode(); + var sessionId = initResponse.Headers.GetValues("Mcp-Session-Id").First(); + + // Consume the SSE response body to properly release the connection + await initResponse.Content.ReadAsStringAsync().DefaultTimeout(TestConstants.LongTimeoutDuration); + + // Send initialized notification + var notificationJson = + """ + { + "jsonrpc": "2.0", + "method": "notifications/initialized" + } + """; + var notificationContent = new ByteArrayContent(Encoding.UTF8.GetBytes(notificationJson)); + notificationContent.Headers.TryAddWithoutValidation("content-type", "application/json"); + var notificationRequest = new HttpRequestMessage(HttpMethod.Post, "/mcp") + { + Content = notificationContent + }; + notificationRequest.Headers.TryAddWithoutValidation("accept", "application/json"); + notificationRequest.Headers.TryAddWithoutValidation("accept", "text/event-stream"); + notificationRequest.Headers.TryAddWithoutValidation("Mcp-Session-Id", sessionId); + notificationRequest.Headers.TryAddWithoutValidation(McpApiKeyAuthenticationHandler.McpApiKeyHeaderName, apiKey); + var notificationResponse = await httpClient.SendAsync(notificationRequest).DefaultTimeout(TestConstants.LongTimeoutDuration); + notificationResponse.EnsureSuccessStatusCode(); + + var listRequest = CreateListToolsRequest(sessionId); + listRequest.Headers.TryAddWithoutValidation(McpApiKeyAuthenticationHandler.McpApiKeyHeaderName, apiKey); + + var responseMessage = await httpClient.SendAsync(listRequest).DefaultTimeout(TestConstants.LongTimeoutDuration); responseMessage.EnsureSuccessStatusCode(); var responseData = await GetDataFromSseResponseAsync(responseMessage); @@ -130,7 +162,8 @@ public async Task CallService_NoResourceService_ResourceToolsNotRegistered() using var httpClient = IntegrationTestHelpers.CreateHttpClient($"http://{app.McpEndPointAccessor().EndPoint}"); - var request = CreateListToolsRequest(); + var sessionId = await InitializeSessionAsync(httpClient); + var request = CreateListToolsRequest(sessionId); // Act var responseMessage = await httpClient.SendAsync(request).DefaultTimeout(TestConstants.LongTimeoutDuration); @@ -166,7 +199,8 @@ public async Task CallService_WithResourceService_ResourceToolsRegistered() using var httpClient = IntegrationTestHelpers.CreateHttpClient($"http://{app.McpEndPointAccessor().EndPoint}"); - var request = CreateListToolsRequest(); + var sessionId = await InitializeSessionAsync(httpClient); + var request = CreateListToolsRequest(sessionId); // Act var responseMessage = await httpClient.SendAsync(request).DefaultTimeout(TestConstants.LongTimeoutDuration); @@ -226,7 +260,8 @@ public async Task CallService_McpEndPointHttpOnly_Success() using var httpClient = IntegrationTestHelpers.CreateHttpClient($"http://{app.McpEndPointAccessor().EndPoint}"); - var request = CreateListToolsRequest(); + var sessionId = await InitializeSessionAsync(httpClient); + var request = CreateListToolsRequest(sessionId); // Act var responseMessage = await httpClient.SendAsync(request).DefaultTimeout(TestConstants.LongTimeoutDuration); @@ -263,7 +298,8 @@ public async Task CallService_McpTool_TelemetryRecorded() using var httpClient = IntegrationTestHelpers.CreateHttpClient($"http://{app.McpEndPointAccessor().EndPoint}"); - var request = CreateListToolsRequest(); + var sessionId = await InitializeSessionAsync(httpClient); + var request = CreateListToolsRequest(sessionId); // Act var responseMessage = await httpClient.SendAsync(request).DefaultTimeout(TestConstants.LongTimeoutDuration); @@ -297,7 +333,73 @@ public async Task CallService_McpTool_TelemetryRecorded() Assert.True(foundEndOperation, "Expected to find EndOperation telemetry event"); } - internal static HttpRequestMessage CreateListToolsRequest() + internal static HttpRequestMessage CreateInitializeRequest(string? sessionId = null) + { + var json = + """ + { + "jsonrpc": "2.0", + "id": "init", + "method": "initialize", + "params": { + "protocolVersion": "2025-03-26", + "capabilities": {}, + "clientInfo": { + "name": "test-client", + "version": "1.0.0" + } + } + } + """; + var content = new ByteArrayContent(Encoding.UTF8.GetBytes(json)); + content.Headers.TryAddWithoutValidation("content-type", "application/json"); + var request = new HttpRequestMessage(HttpMethod.Post, "/mcp") + { + Content = content + }; + request.Headers.TryAddWithoutValidation("accept", "application/json"); + request.Headers.TryAddWithoutValidation("accept", "text/event-stream"); + if (sessionId is not null) + { + request.Headers.TryAddWithoutValidation("Mcp-Session-Id", sessionId); + } + return request; + } + + internal static async Task InitializeSessionAsync(HttpClient httpClient) + { + var initRequest = CreateInitializeRequest(); + var initResponse = await httpClient.SendAsync(initRequest).DefaultTimeout(TestConstants.LongTimeoutDuration); + initResponse.EnsureSuccessStatusCode(); + var sessionId = initResponse.Headers.GetValues("Mcp-Session-Id").First(); + + // Consume the SSE response body to properly release the connection + await initResponse.Content.ReadAsStringAsync().DefaultTimeout(TestConstants.LongTimeoutDuration); + + // Send initialized notification + var notificationJson = + """ + { + "jsonrpc": "2.0", + "method": "notifications/initialized" + } + """; + var notificationContent = new ByteArrayContent(Encoding.UTF8.GetBytes(notificationJson)); + notificationContent.Headers.TryAddWithoutValidation("content-type", "application/json"); + var notificationRequest = new HttpRequestMessage(HttpMethod.Post, "/mcp") + { + Content = notificationContent + }; + notificationRequest.Headers.TryAddWithoutValidation("accept", "application/json"); + notificationRequest.Headers.TryAddWithoutValidation("accept", "text/event-stream"); + notificationRequest.Headers.TryAddWithoutValidation("Mcp-Session-Id", sessionId); + var notificationResponse = await httpClient.SendAsync(notificationRequest).DefaultTimeout(TestConstants.LongTimeoutDuration); + notificationResponse.EnsureSuccessStatusCode(); + + return sessionId; + } + + internal static HttpRequestMessage CreateListToolsRequest(string? sessionId = null) { var json = """ @@ -316,6 +418,10 @@ internal static HttpRequestMessage CreateListToolsRequest() }; request.Headers.TryAddWithoutValidation("accept", "application/json"); request.Headers.TryAddWithoutValidation("accept", "text/event-stream"); + if (sessionId is not null) + { + request.Headers.TryAddWithoutValidation("Mcp-Session-Id", sessionId); + } return request; } diff --git a/tests/Aspire.Dashboard.Tests/Integration/StartupTests.cs b/tests/Aspire.Dashboard.Tests/Integration/StartupTests.cs index 51ac994d4af..c0473235abe 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/StartupTests.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/StartupTests.cs @@ -416,7 +416,8 @@ await ServerRetryHelper.BindPortWithRetry(async port => { BaseAddress = new Uri($"https://{app.McpEndPointAccessor().EndPoint}") }; - var mcpRequest = McpServiceTests.CreateListToolsRequest(); + var mcpSessionId = await McpServiceTests.InitializeSessionAsync(mcpHttpClient); + var mcpRequest = McpServiceTests.CreateListToolsRequest(mcpSessionId); var responseMessage = await mcpHttpClient.SendAsync(mcpRequest).DefaultTimeout(TestConstants.LongTimeoutDuration); responseMessage.EnsureSuccessStatusCode(); From caaa39516ab3454eaf0860d7e05b5264deb2f0c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 22:57:39 +0000 Subject: [PATCH 4/5] Update all .NET 10.0 package versions from 10.0.2 to 10.0.3 Updates EF Core, ASP.NET Core, Runtime, and other .NET 10.0 packages in eng/Versions.props from 10.0.2 to 10.0.3. Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> --- eng/Versions.props | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 1f58062f866..c763b0d9ba4 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -39,7 +39,7 @@ 11.0.0-beta.25610.3 11.0.0-beta.25610.3 11.0.0-beta.25610.3 - 10.0.2 + 10.0.3 10.2.0 10.1.0-preview.1.25608.1 @@ -65,34 +65,34 @@ - 10.0.2 - 10.0.2 - 10.0.2 - 10.0.2 + 10.0.3 + 10.0.3 + 10.0.3 + 10.0.3 - 10.0.2 - 10.0.2 - 10.0.2 - 10.0.2 - 10.0.2 - 10.0.2 - 10.0.2 - 10.0.2 - 10.0.2 - 10.0.2 - 10.0.2 + 10.0.3 + 10.0.3 + 10.0.3 + 10.0.3 + 10.0.3 + 10.0.3 + 10.0.3 + 10.0.3 + 10.0.3 + 10.0.3 + 10.0.3 10.0.3 - 10.0.2 - 10.0.2 + 10.0.3 + 10.0.3 10.0.3 - 10.0.2 + 10.0.3 10.0.3 10.0.3 10.0.3 10.0.3 - 10.0.2 - 10.0.2 + 10.0.3 + 10.0.3 10.0.3 From f4b1c7c1a27dc710e4e90b34c56ea3eed5953da9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 23:29:12 +0000 Subject: [PATCH 5/5] Refactor RequiredApiKeySent test to use InitializeSessionAsync Add optional configureRequest callback to InitializeSessionAsync and use it in CallService_McpEndPoint_RequiredApiKeySent_Success to reduce code duplication. Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> --- .../Integration/McpServiceTests.cs | 38 +++---------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/tests/Aspire.Dashboard.Tests/Integration/McpServiceTests.cs b/tests/Aspire.Dashboard.Tests/Integration/McpServiceTests.cs index 34beaccdcc7..04bff74f41e 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/McpServiceTests.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/McpServiceTests.cs @@ -106,40 +106,12 @@ public async Task CallService_McpEndPoint_RequiredApiKeySent_Success() using var httpClient = IntegrationTestHelpers.CreateHttpClient($"http://{app.McpEndPointAccessor().EndPoint}"); - var requestMessage = CreateInitializeRequest(); - requestMessage.Headers.TryAddWithoutValidation(McpApiKeyAuthenticationHandler.McpApiKeyHeaderName, apiKey); + void AddApiKey(HttpRequestMessage r) => r.Headers.TryAddWithoutValidation(McpApiKeyAuthenticationHandler.McpApiKeyHeaderName, apiKey); - // Act - var initResponse = await httpClient.SendAsync(requestMessage).DefaultTimeout(TestConstants.LongTimeoutDuration); - initResponse.EnsureSuccessStatusCode(); - var sessionId = initResponse.Headers.GetValues("Mcp-Session-Id").First(); - - // Consume the SSE response body to properly release the connection - await initResponse.Content.ReadAsStringAsync().DefaultTimeout(TestConstants.LongTimeoutDuration); - - // Send initialized notification - var notificationJson = - """ - { - "jsonrpc": "2.0", - "method": "notifications/initialized" - } - """; - var notificationContent = new ByteArrayContent(Encoding.UTF8.GetBytes(notificationJson)); - notificationContent.Headers.TryAddWithoutValidation("content-type", "application/json"); - var notificationRequest = new HttpRequestMessage(HttpMethod.Post, "/mcp") - { - Content = notificationContent - }; - notificationRequest.Headers.TryAddWithoutValidation("accept", "application/json"); - notificationRequest.Headers.TryAddWithoutValidation("accept", "text/event-stream"); - notificationRequest.Headers.TryAddWithoutValidation("Mcp-Session-Id", sessionId); - notificationRequest.Headers.TryAddWithoutValidation(McpApiKeyAuthenticationHandler.McpApiKeyHeaderName, apiKey); - var notificationResponse = await httpClient.SendAsync(notificationRequest).DefaultTimeout(TestConstants.LongTimeoutDuration); - notificationResponse.EnsureSuccessStatusCode(); + var sessionId = await InitializeSessionAsync(httpClient, AddApiKey); var listRequest = CreateListToolsRequest(sessionId); - listRequest.Headers.TryAddWithoutValidation(McpApiKeyAuthenticationHandler.McpApiKeyHeaderName, apiKey); + AddApiKey(listRequest); var responseMessage = await httpClient.SendAsync(listRequest).DefaultTimeout(TestConstants.LongTimeoutDuration); responseMessage.EnsureSuccessStatusCode(); @@ -366,9 +338,10 @@ internal static HttpRequestMessage CreateInitializeRequest(string? sessionId = n return request; } - internal static async Task InitializeSessionAsync(HttpClient httpClient) + internal static async Task InitializeSessionAsync(HttpClient httpClient, Action? configureRequest = null) { var initRequest = CreateInitializeRequest(); + configureRequest?.Invoke(initRequest); var initResponse = await httpClient.SendAsync(initRequest).DefaultTimeout(TestConstants.LongTimeoutDuration); initResponse.EnsureSuccessStatusCode(); var sessionId = initResponse.Headers.GetValues("Mcp-Session-Id").First(); @@ -393,6 +366,7 @@ internal static async Task InitializeSessionAsync(HttpClient httpClient) notificationRequest.Headers.TryAddWithoutValidation("accept", "application/json"); notificationRequest.Headers.TryAddWithoutValidation("accept", "text/event-stream"); notificationRequest.Headers.TryAddWithoutValidation("Mcp-Session-Id", sessionId); + configureRequest?.Invoke(notificationRequest); var notificationResponse = await httpClient.SendAsync(notificationRequest).DefaultTimeout(TestConstants.LongTimeoutDuration); notificationResponse.EnsureSuccessStatusCode();