diff --git a/src/ModelContextProtocol/Configuration/McpServerServiceCollectionExtensions.cs b/src/ModelContextProtocol/Configuration/McpServerServiceCollectionExtensions.cs index 4e772a024..53a16af5c 100644 --- a/src/ModelContextProtocol/Configuration/McpServerServiceCollectionExtensions.cs +++ b/src/ModelContextProtocol/Configuration/McpServerServiceCollectionExtensions.cs @@ -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? configureOptions = null) { services.AddOptions(); - services.AddTransient, McpServerOptionsSetup>(); + services.TryAddEnumerable(ServiceDescriptor.Transient, McpServerOptionsSetup>()); if (configureOptions is not null) { services.Configure(configureOptions); diff --git a/tests/ModelContextProtocol.Tests/SseIntegrationTests.cs b/tests/ModelContextProtocol.Tests/SseIntegrationTests.cs index b7baa8bdd..5ba5d3a9f 100644 --- a/tests/ModelContextProtocol.Tests/SseIntegrationTests.cs +++ b/tests/ModelContextProtocol.Tests/SseIntegrationTests.cs @@ -10,6 +10,7 @@ using ModelContextProtocol.Server; using ModelContextProtocol.Tests.Utils; using ModelContextProtocol.Utils.Json; +using TestServerWithHosting.Tools; namespace ModelContextProtocol.Tests; @@ -95,6 +96,56 @@ public async Task ConnectAndReceiveNotification_InMemoryServer() 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(); + + Builder.Services.AddMcpServer(options => + { + Interlocked.Increment(ref secondOptionsCallbackCallCount); + }) + .WithTools(); + + + 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 + { + ["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();