Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.14.0" />
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.14.0" />
<PackageVersion Include="Milvus.Client" Version="2.3.0-preview.1" /> <!-- No stable release available -->
<PackageVersion Include="ModelContextProtocol" Version="0.8.0-preview.1" />
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="0.8.0-preview.1" />
<PackageVersion Include="ModelContextProtocol" Version="1.0.0" />
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="1.0.0" />
<PackageVersion Include="MongoDB.Driver" Version="3.6.0" />
<PackageVersion Include="MongoDB.Driver.Core.Extensions.DiagnosticSources" Version="3.0.0" />
<PackageVersion Include="MySqlConnector.DependencyInjection" Version="2.5.0" />
Expand Down
56 changes: 28 additions & 28 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<MicrosoftDotNetRemoteExecutorVersion>11.0.0-beta.25610.3</MicrosoftDotNetRemoteExecutorVersion>
<MicrosoftDotNetXUnitV3ExtensionsVersion>11.0.0-beta.25610.3</MicrosoftDotNetXUnitV3ExtensionsVersion>
<MicrosoftDotNetBuildTasksArchivesVersion>11.0.0-beta.25610.3</MicrosoftDotNetBuildTasksArchivesVersion>
<MicrosoftExtensionsFileSystemGlobbingVersion>10.0.2</MicrosoftExtensionsFileSystemGlobbingVersion>
<MicrosoftExtensionsFileSystemGlobbingVersion>10.0.3</MicrosoftExtensionsFileSystemGlobbingVersion>
<!-- dotnet/extensions -->
<MicrosoftExtensionsAIVersion>10.2.0</MicrosoftExtensionsAIVersion>
<MicrosoftExtensionsAIOpenAIVersion>10.1.0-preview.1.25608.1</MicrosoftExtensionsAIOpenAIVersion>
Expand All @@ -65,35 +65,35 @@
<!-- .NET 10.0 Package Versions -->
<PropertyGroup Label="Preview">
<!-- EF -->
<MicrosoftEntityFrameworkCoreCosmosPreviewVersion>10.0.2</MicrosoftEntityFrameworkCoreCosmosPreviewVersion>
<MicrosoftEntityFrameworkCoreDesignPreviewVersion>10.0.2</MicrosoftEntityFrameworkCoreDesignPreviewVersion>
<MicrosoftEntityFrameworkCoreSqlServerPreviewVersion>10.0.2</MicrosoftEntityFrameworkCoreSqlServerPreviewVersion>
<MicrosoftEntityFrameworkCoreToolsPreviewVersion>10.0.2</MicrosoftEntityFrameworkCoreToolsPreviewVersion>
<MicrosoftEntityFrameworkCoreCosmosPreviewVersion>10.0.3</MicrosoftEntityFrameworkCoreCosmosPreviewVersion>
<MicrosoftEntityFrameworkCoreDesignPreviewVersion>10.0.3</MicrosoftEntityFrameworkCoreDesignPreviewVersion>
<MicrosoftEntityFrameworkCoreSqlServerPreviewVersion>10.0.3</MicrosoftEntityFrameworkCoreSqlServerPreviewVersion>
<MicrosoftEntityFrameworkCoreToolsPreviewVersion>10.0.3</MicrosoftEntityFrameworkCoreToolsPreviewVersion>
<!-- ASP.NET Core -->
<MicrosoftAspNetCoreAuthenticationCertificatePreviewVersion>10.0.2</MicrosoftAspNetCoreAuthenticationCertificatePreviewVersion>
<MicrosoftAspNetCoreAuthenticationJwtBearerPreviewVersion>10.0.2</MicrosoftAspNetCoreAuthenticationJwtBearerPreviewVersion>
<MicrosoftAspNetCoreAuthenticationOpenIdConnectPreviewVersion>10.0.2</MicrosoftAspNetCoreAuthenticationOpenIdConnectPreviewVersion>
<MicrosoftAspNetCoreOpenApiPreviewVersion>10.0.2</MicrosoftAspNetCoreOpenApiPreviewVersion>
<MicrosoftAspNetCoreOutputCachingStackExchangeRedisPreviewVersion>10.0.2</MicrosoftAspNetCoreOutputCachingStackExchangeRedisPreviewVersion>
<MicrosoftAspNetCoreTestHostPreviewVersion>10.0.2</MicrosoftAspNetCoreTestHostPreviewVersion>
<MicrosoftExtensionsCachingStackExchangeRedisPreviewVersion>10.0.2</MicrosoftExtensionsCachingStackExchangeRedisPreviewVersion>
<MicrosoftExtensionsDiagnosticsHealthChecksEntityFrameworkCorePreviewVersion>10.0.2</MicrosoftExtensionsDiagnosticsHealthChecksEntityFrameworkCorePreviewVersion>
<MicrosoftExtensionsDiagnosticsHealthChecksPreviewVersion>10.0.2</MicrosoftExtensionsDiagnosticsHealthChecksPreviewVersion>
<MicrosoftExtensionsFeaturesPreviewVersion>10.0.2</MicrosoftExtensionsFeaturesPreviewVersion>
<MicrosoftAspNetCoreSignalRClientPreviewVersion>10.0.2</MicrosoftAspNetCoreSignalRClientPreviewVersion>
<MicrosoftAspNetCoreAuthenticationCertificatePreviewVersion>10.0.3</MicrosoftAspNetCoreAuthenticationCertificatePreviewVersion>
<MicrosoftAspNetCoreAuthenticationJwtBearerPreviewVersion>10.0.3</MicrosoftAspNetCoreAuthenticationJwtBearerPreviewVersion>
<MicrosoftAspNetCoreAuthenticationOpenIdConnectPreviewVersion>10.0.3</MicrosoftAspNetCoreAuthenticationOpenIdConnectPreviewVersion>
<MicrosoftAspNetCoreOpenApiPreviewVersion>10.0.3</MicrosoftAspNetCoreOpenApiPreviewVersion>
<MicrosoftAspNetCoreOutputCachingStackExchangeRedisPreviewVersion>10.0.3</MicrosoftAspNetCoreOutputCachingStackExchangeRedisPreviewVersion>
<MicrosoftAspNetCoreTestHostPreviewVersion>10.0.3</MicrosoftAspNetCoreTestHostPreviewVersion>
<MicrosoftExtensionsCachingStackExchangeRedisPreviewVersion>10.0.3</MicrosoftExtensionsCachingStackExchangeRedisPreviewVersion>
<MicrosoftExtensionsDiagnosticsHealthChecksEntityFrameworkCorePreviewVersion>10.0.3</MicrosoftExtensionsDiagnosticsHealthChecksEntityFrameworkCorePreviewVersion>
<MicrosoftExtensionsDiagnosticsHealthChecksPreviewVersion>10.0.3</MicrosoftExtensionsDiagnosticsHealthChecksPreviewVersion>
<MicrosoftExtensionsFeaturesPreviewVersion>10.0.3</MicrosoftExtensionsFeaturesPreviewVersion>
<MicrosoftAspNetCoreSignalRClientPreviewVersion>10.0.3</MicrosoftAspNetCoreSignalRClientPreviewVersion>
<!-- Runtime -->
<MicrosoftExtensionsHostingAbstractionsPreviewVersion>10.0.2</MicrosoftExtensionsHostingAbstractionsPreviewVersion>
<MicrosoftExtensionsHostingPreviewVersion>10.0.2</MicrosoftExtensionsHostingPreviewVersion>
<MicrosoftExtensionsCachingMemoryPreviewVersion>10.0.2</MicrosoftExtensionsCachingMemoryPreviewVersion>
<MicrosoftExtensionsConfigurationAbstractionsPreviewVersion>10.0.2</MicrosoftExtensionsConfigurationAbstractionsPreviewVersion>
<MicrosoftExtensionsConfigurationBinderPreviewVersion>10.0.2</MicrosoftExtensionsConfigurationBinderPreviewVersion>
<MicrosoftExtensionsDependencyInjectionAbstractionsPreviewVersion>10.0.2</MicrosoftExtensionsDependencyInjectionAbstractionsPreviewVersion>
<MicrosoftExtensionsLoggingAbstractionsPreviewVersion>10.0.2</MicrosoftExtensionsLoggingAbstractionsPreviewVersion>
<MicrosoftExtensionsOptionsPreviewVersion>10.0.2</MicrosoftExtensionsOptionsPreviewVersion>
<MicrosoftExtensionsPrimitivesPreviewVersion>10.0.2</MicrosoftExtensionsPrimitivesPreviewVersion>
<MicrosoftExtensionsHttpPreviewVersion>10.0.2</MicrosoftExtensionsHttpPreviewVersion>
<SystemFormatsAsn1PreviewVersion>10.0.2</SystemFormatsAsn1PreviewVersion>
<SystemTextJsonPreviewVersion>10.0.2</SystemTextJsonPreviewVersion>
<MicrosoftExtensionsHostingAbstractionsPreviewVersion>10.0.3</MicrosoftExtensionsHostingAbstractionsPreviewVersion>
<MicrosoftExtensionsHostingPreviewVersion>10.0.3</MicrosoftExtensionsHostingPreviewVersion>
<MicrosoftExtensionsCachingMemoryPreviewVersion>10.0.3</MicrosoftExtensionsCachingMemoryPreviewVersion>
<MicrosoftExtensionsConfigurationAbstractionsPreviewVersion>10.0.3</MicrosoftExtensionsConfigurationAbstractionsPreviewVersion>
<MicrosoftExtensionsConfigurationBinderPreviewVersion>10.0.3</MicrosoftExtensionsConfigurationBinderPreviewVersion>
<MicrosoftExtensionsDependencyInjectionAbstractionsPreviewVersion>10.0.3</MicrosoftExtensionsDependencyInjectionAbstractionsPreviewVersion>
<MicrosoftExtensionsLoggingAbstractionsPreviewVersion>10.0.3</MicrosoftExtensionsLoggingAbstractionsPreviewVersion>
<MicrosoftExtensionsOptionsPreviewVersion>10.0.3</MicrosoftExtensionsOptionsPreviewVersion>
<MicrosoftExtensionsPrimitivesPreviewVersion>10.0.3</MicrosoftExtensionsPrimitivesPreviewVersion>
<MicrosoftExtensionsHttpPreviewVersion>10.0.3</MicrosoftExtensionsHttpPreviewVersion>
<SystemFormatsAsn1PreviewVersion>10.0.3</SystemFormatsAsn1PreviewVersion>
<SystemTextJsonPreviewVersion>10.0.3</SystemTextJsonPreviewVersion>
</PropertyGroup>
<!-- .NET 9.0 Package Versions -->
<PropertyGroup Label="Current">
Expand Down
27 changes: 14 additions & 13 deletions src/Aspire.Dashboard/Mcp/McpExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,20 @@ public static IMcpServerBuilder AddAspireMcpTools(this IServiceCollection servic
}

builder
.AddListToolsFilter((next) => async (RequestContext<ListToolsRequestParams> 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<ListToolsRequestParams, ListToolsResult>(next, request, "list_tools", cancellationToken).ConfigureAwait(false);
})
.AddCallToolFilter((next) => async (RequestContext<CallToolRequestParams> request, CancellationToken cancellationToken) =>
{
return await RecordCallToolNameAsync<CallToolRequestParams, CallToolResult>(next, request, request.Params?.Name, cancellationToken).ConfigureAwait(false);
});
.WithRequestFilters(filters => filters
.AddListToolsFilter((next) => async (RequestContext<ListToolsRequestParams> 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<ListToolsRequestParams, ListToolsResult>(next, request, "list_tools", cancellationToken).ConfigureAwait(false);
})
.AddCallToolFilter((next) => async (RequestContext<CallToolRequestParams> request, CancellationToken cancellationToken) =>
{
return await RecordCallToolNameAsync<CallToolRequestParams, CallToolResult>(next, request, request.Params?.Name, cancellationToken).ConfigureAwait(false);
}));

return builder;
}
Expand Down
100 changes: 90 additions & 10 deletions tests/Aspire.Dashboard.Tests/Integration/McpServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -105,11 +106,14 @@ public async Task CallService_McpEndPoint_RequiredApiKeySent_Success()

using var httpClient = IntegrationTestHelpers.CreateHttpClient($"http://{app.McpEndPointAccessor().EndPoint}");

var requestMessage = CreateListToolsRequest();
requestMessage.Headers.TryAddWithoutValidation(McpApiKeyAuthenticationHandler.McpApiKeyHeaderName, apiKey);
void AddApiKey(HttpRequestMessage r) => r.Headers.TryAddWithoutValidation(McpApiKeyAuthenticationHandler.McpApiKeyHeaderName, apiKey);

// Act
var responseMessage = await httpClient.SendAsync(requestMessage).DefaultTimeout(TestConstants.LongTimeoutDuration);
var sessionId = await InitializeSessionAsync(httpClient, AddApiKey);

var listRequest = CreateListToolsRequest(sessionId);
AddApiKey(listRequest);

var responseMessage = await httpClient.SendAsync(listRequest).DefaultTimeout(TestConstants.LongTimeoutDuration);
responseMessage.EnsureSuccessStatusCode();

var responseData = await GetDataFromSseResponseAsync(responseMessage);
Expand All @@ -130,7 +134,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);
Expand Down Expand Up @@ -166,7 +171,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);
Expand Down Expand Up @@ -226,7 +232,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);
Expand Down Expand Up @@ -263,7 +270,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);
Expand Down Expand Up @@ -297,7 +305,75 @@ 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<string> InitializeSessionAsync(HttpClient httpClient, Action<HttpRequestMessage>? 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();

// 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);
configureRequest?.Invoke(notificationRequest);
var notificationResponse = await httpClient.SendAsync(notificationRequest).DefaultTimeout(TestConstants.LongTimeoutDuration);
notificationResponse.EnsureSuccessStatusCode();

return sessionId;
}

internal static HttpRequestMessage CreateListToolsRequest(string? sessionId = null)
{
var json =
"""
Expand All @@ -316,6 +392,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;
}

Expand Down
3 changes: 2 additions & 1 deletion tests/Aspire.Dashboard.Tests/Integration/StartupTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading