Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
122 changes: 114 additions & 8 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,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();
Comment thread
JamesNK marked this conversation as resolved.
Outdated

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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<string> 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 =
"""
Expand All @@ -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;
}

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