Use TryAddEnumerable for McpServerOptionsSetup (#252)

This commit is contained in:
Stephen Halter 2025-04-09 10:41:34 -07:00 • committed by GitHub
commit b3a5a82544
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 53 additions and 1 deletions

View file

@ -1,5 +1,6 @@
using ModelContextProtocol.Server;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Microsoft.Extensions.DependencyInjection;
@ -17,7 +18,7 @@ public static class McpServerServiceCollectionExtensions
public static IMcpServerBuilder AddMcpServer(this IServiceCollection services, Action<McpServerOptions>? configureOptions = null)
{
services.AddOptions();
services.AddTransient<IConfigureOptions<McpServerOptions>, McpServerOptionsSetup>();
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<McpServerOptions>, McpServerOptionsSetup>());
if (configureOptions is not null)
{
services.Configure(configureOptions);

View file

@ -10,6 +10,7 @@ using ModelContextProtocol.Protocol.Transport;
using ModelContextProtocol.Server;
using ModelContextProtocol.Tests.Utils;
using ModelContextProtocol.Utils.Json;
using TestServerWithHosting.Tools;
namespace ModelContextProtocol.Tests;
@ -95,6 +96,56 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : KestrelInMemo
Assert.Equal("Hello from server!", message);
}
[Fact]
public async Task AddMcpServer_CanBeCalled_MultipleTimes()
{
var firstOptionsCallbackCallCount = 0;
var secondOptionsCallbackCallCount = 0;
Builder.Services.AddMcpServer(options =>
{
Interlocked.Increment(ref firstOptionsCallbackCallCount);
})
.WithTools<EchoTool>();
Builder.Services.AddMcpServer(options =>
{
Interlocked.Increment(ref secondOptionsCallbackCallCount);
})
.WithTools<SampleLlmTool>();
await using var app = Builder.Build();
app.MapMcp();
await app.StartAsync(TestContext.Current.CancellationToken);
using var httpClient = CreateHttpClient();
await using var mcpClient = await ConnectMcpClient(httpClient);
// Options can be lazily initialized, but they must be instantiated by the time an MCP client can finish connecting.
// Callbacks can be called multiple times if configureOptionsAsync is configured, because that uses the IOptionsFactory,
// but that's not the case in this test.
Assert.Equal(1, firstOptionsCallbackCallCount);
Assert.Equal(1, secondOptionsCallbackCallCount);
var tools = await mcpClient.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
Assert.Equal(2, tools.Count);
Assert.Contains(tools, tools => tools.Name == "Echo");
Assert.Contains(tools, tools => tools.Name == "sampleLLM");
var echoResponse = await mcpClient.CallToolAsync(
"Echo",
new Dictionary<string, object?>
{
["message"] = "from client!"
},
cancellationToken: TestContext.Current.CancellationToken);
var textContent = Assert.Single(echoResponse.Content, c => c.Type == "text");
Assert.Equal("hello from client!", textContent.Text);
}
private static void MapAbsoluteEndpointUriMcp(IEndpointRouteBuilder endpoints)
{
var loggerFactory = endpoints.ServiceProvider.GetRequiredService<ILoggerFactory>();