diff --git a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs
index 8e264741b..a4a3d6910 100644
--- a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs
+++ b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs
@@ -295,7 +295,7 @@ subscribeHandler is null && unsubscribeHandler is null && resources is null &&
var subscribe = resourcesCapability?.Subscribe;
// Handle resources provided via DI.
- if (resources is { IsEmpty: false })
+ if (resources is not null)
{
var originalListResourcesHandler = listResourcesHandler;
listResourcesHandler = async (request, cancellationToken) =>
@@ -456,7 +456,7 @@ private void ConfigurePrompts(McpServerOptions options)
var listChanged = promptsCapability?.ListChanged;
// Handle tools provided via DI by augmenting the handlers to incorporate them.
- if (prompts is { IsEmpty: false })
+ if (prompts is not null)
{
var originalListPromptsHandler = listPromptsHandler;
listPromptsHandler = async (request, cancellationToken) =>
@@ -544,7 +544,7 @@ private void ConfigureTools(McpServerOptions options)
var listChanged = toolsCapability?.ListChanged;
// Handle tools provided via DI by augmenting the handlers to incorporate them.
- if (tools is { IsEmpty: false })
+ if (tools is not null)
{
var originalListToolsHandler = listToolsHandler;
listToolsHandler = async (request, cancellationToken) =>
diff --git a/tests/Common/Utils/MockHttpHandler.cs b/tests/Common/Utils/MockHttpHandler.cs
index d15ec3dc0..5e58a6cd5 100644
--- a/tests/Common/Utils/MockHttpHandler.cs
+++ b/tests/Common/Utils/MockHttpHandler.cs
@@ -1,6 +1,4 @@
-using System.Net.Http;
-
-namespace ModelContextProtocol.Tests.Utils;
+namespace ModelContextProtocol.Tests.Utils;
public class MockHttpHandler : HttpMessageHandler
{
diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/ModelContextProtocol.AspNetCore.Tests.csproj b/tests/ModelContextProtocol.AspNetCore.Tests/ModelContextProtocol.AspNetCore.Tests.csproj
index 7adeb03d3..5d21d0a0a 100644
--- a/tests/ModelContextProtocol.AspNetCore.Tests/ModelContextProtocol.AspNetCore.Tests.csproj
+++ b/tests/ModelContextProtocol.AspNetCore.Tests/ModelContextProtocol.AspNetCore.Tests.csproj
@@ -44,7 +44,6 @@
-
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -52,6 +51,10 @@
+
+
+
+
diff --git a/tests/ModelContextProtocol.TestSseServer/ModelContextProtocol.TestSseServer.csproj b/tests/ModelContextProtocol.TestSseServer/ModelContextProtocol.TestSseServer.csproj
index 3296ff481..e2a1c16c2 100644
--- a/tests/ModelContextProtocol.TestSseServer/ModelContextProtocol.TestSseServer.csproj
+++ b/tests/ModelContextProtocol.TestSseServer/ModelContextProtocol.TestSseServer.csproj
@@ -9,8 +9,6 @@
-
-
diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs
index 7d037fb2d..c2e2b9d51 100644
--- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs
+++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs
@@ -7,7 +7,6 @@
using Moq;
using System.Collections;
using System.ComponentModel;
-using System.Text.Json;
using System.Threading.Channels;
using static ModelContextProtocol.Tests.Configuration.McpServerBuilderExtensionsPromptsTests;
diff --git a/tests/ModelContextProtocol.Tests/ModelContextProtocol.Tests.csproj b/tests/ModelContextProtocol.Tests/ModelContextProtocol.Tests.csproj
index 944eec4d5..425944624 100644
--- a/tests/ModelContextProtocol.Tests/ModelContextProtocol.Tests.csproj
+++ b/tests/ModelContextProtocol.Tests/ModelContextProtocol.Tests.csproj
@@ -52,7 +52,6 @@
-
@@ -61,6 +60,10 @@
+
+
+
+
diff --git a/tests/ModelContextProtocol.Tests/Protocol/CancellationTests.cs b/tests/ModelContextProtocol.Tests/Protocol/CancellationTests.cs
index 80c6b1ed9..e21f1f952 100644
--- a/tests/ModelContextProtocol.Tests/Protocol/CancellationTests.cs
+++ b/tests/ModelContextProtocol.Tests/Protocol/CancellationTests.cs
@@ -1,5 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
-using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
diff --git a/tests/ModelContextProtocol.Tests/Protocol/ElicitationTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ElicitationTests.cs
index f3ae33ed5..76f967bed 100644
--- a/tests/ModelContextProtocol.Tests/Protocol/ElicitationTests.cs
+++ b/tests/ModelContextProtocol.Tests/Protocol/ElicitationTests.cs
@@ -1,7 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
-using ModelContextProtocol.Server;
using System.Text.Json;
namespace ModelContextProtocol.Tests.Configuration;
diff --git a/tests/ModelContextProtocol.Tests/Server/EmptyCollectionTests.cs b/tests/ModelContextProtocol.Tests/Server/EmptyCollectionTests.cs
new file mode 100644
index 000000000..882fd5045
--- /dev/null
+++ b/tests/ModelContextProtocol.Tests/Server/EmptyCollectionTests.cs
@@ -0,0 +1,165 @@
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.DependencyInjection;
+using ModelContextProtocol.Protocol;
+using ModelContextProtocol.Server;
+
+namespace ModelContextProtocol.Tests.Server;
+
+///
+/// Tests to verify that handlers are synthesized for empty collections that can be populated dynamically.
+/// This addresses the issue where handlers were only created when collections had items.
+///
+public class EmptyCollectionTests : ClientServerTestBase
+{
+ public EmptyCollectionTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { }
+
+ private McpServerResourceCollection _resourceCollection = [];
+ private McpServerPrimitiveCollection _toolCollection = [];
+ private McpServerPrimitiveCollection _promptCollection = [];
+
+ protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder) =>
+ mcpServerBuilder.Services.Configure(options =>
+ {
+ options.ResourceCollection = _resourceCollection;
+ options.ToolCollection = _toolCollection;
+ options.PromptCollection = _promptCollection;
+ });
+
+ [Fact]
+ public async Task EmptyResourceCollection_CanAddResourcesDynamically()
+ {
+ var client = await CreateMcpClientForServer();
+
+ // Initially, the resource collection is empty
+ var initialResources = await client.ListResourcesAsync(TestContext.Current.CancellationToken);
+ Assert.Empty(initialResources);
+
+ // Add a resource dynamically
+ _resourceCollection.Add(McpServerResource.Create(
+ () => "test content",
+ new() { UriTemplate = "test://resource/1" }));
+
+ // The resource should now be listed
+ var updatedResources = await client.ListResourcesAsync(TestContext.Current.CancellationToken);
+ Assert.Single(updatedResources);
+ Assert.Equal("test://resource/1", updatedResources[0].Uri);
+ }
+
+ [Fact]
+ public async Task EmptyToolCollection_CanAddToolsDynamically()
+ {
+ var client = await CreateMcpClientForServer();
+
+ // Initially, the tool collection is empty
+ var initialTools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
+ Assert.Empty(initialTools);
+
+ // Add a tool dynamically
+ _toolCollection.Add(McpServerTool.Create(
+ () => "test result",
+ new() { Name = "test_tool", Description = "A test tool" }));
+
+ // The tool should now be listed
+ var updatedTools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
+ Assert.Single(updatedTools);
+ Assert.Equal("test_tool", updatedTools[0].Name);
+ }
+
+ [Fact]
+ public async Task EmptyPromptCollection_CanAddPromptsDynamically()
+ {
+ var client = await CreateMcpClientForServer();
+
+ // Initially, the prompt collection is empty
+ var initialPrompts = await client.ListPromptsAsync(TestContext.Current.CancellationToken);
+ Assert.Empty(initialPrompts);
+
+ // Add a prompt dynamically
+ _promptCollection.Add(McpServerPrompt.Create(
+ () => new ChatMessage(ChatRole.User, "test prompt"),
+ new() { Name = "test_prompt", Description = "A test prompt" }));
+
+ // The prompt should now be listed
+ var updatedPrompts = await client.ListPromptsAsync(TestContext.Current.CancellationToken);
+ Assert.Single(updatedPrompts);
+ Assert.Equal("test_prompt", updatedPrompts[0].Name);
+ }
+
+ [Fact]
+ public async Task EmptyResourceCollection_CanCallReadResourceAfterAddingDynamically()
+ {
+ var client = await CreateMcpClientForServer();
+
+ // Add a resource dynamically
+ _resourceCollection.Add(McpServerResource.Create(
+ () => "dynamic content",
+ new() { UriTemplate = "test://resource/dynamic" }));
+
+ // Read the resource
+ var result = await client.ReadResourceAsync("test://resource/dynamic", TestContext.Current.CancellationToken);
+ Assert.NotNull(result);
+ Assert.Single(result.Contents);
+ Assert.IsType(result.Contents[0]);
+ Assert.Equal("dynamic content", ((TextResourceContents)result.Contents[0]).Text);
+ }
+
+ [Fact]
+ public async Task EmptyToolCollection_CanCallToolAfterAddingDynamically()
+ {
+ var client = await CreateMcpClientForServer();
+
+ // Add a tool dynamically
+ _toolCollection.Add(McpServerTool.Create(
+ () => "dynamic result",
+ new() { Name = "dynamic_tool", Description = "A dynamic tool" }));
+
+ // Call the tool
+ var result = await client.CallToolAsync("dynamic_tool", cancellationToken: TestContext.Current.CancellationToken);
+ Assert.NotNull(result);
+ Assert.Single(result.Content);
+ Assert.IsType(result.Content[0]);
+ Assert.Equal("dynamic result", ((TextContentBlock)result.Content[0]).Text);
+ }
+
+ [Fact]
+ public async Task EmptyPromptCollection_CanGetPromptAfterAddingDynamically()
+ {
+ var client = await CreateMcpClientForServer();
+
+ // Add a prompt dynamically
+ _promptCollection.Add(McpServerPrompt.Create(
+ () => new ChatMessage(ChatRole.User, "dynamic prompt content"),
+ new() { Name = "dynamic_prompt", Description = "A dynamic prompt" }));
+
+ // Get the prompt
+ var result = await client.GetPromptAsync("dynamic_prompt", cancellationToken: TestContext.Current.CancellationToken);
+ Assert.NotNull(result);
+ Assert.Single(result.Messages);
+ Assert.Equal(Role.User, result.Messages[0].Role);
+ Assert.IsType(result.Messages[0].Content);
+ Assert.Equal("dynamic prompt content", ((TextContentBlock)result.Messages[0].Content).Text);
+ }
+}
+
+///
+/// Tests to verify that handlers are NOT synthesized when collections are null.
+/// This ensures we don't unnecessarily create capabilities when nothing is configured.
+///
+public class NullCollectionTests : ClientServerTestBase
+{
+ public NullCollectionTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { }
+
+ [Fact]
+ public async Task ListFails()
+ {
+ Assert.Null(Server.ServerOptions.Capabilities?.Resources);
+ Assert.Null(Server.ServerOptions.Capabilities?.Tools);
+ Assert.Null(Server.ServerOptions.Capabilities?.Prompts);
+
+ var client = await CreateMcpClientForServer();
+
+ await Assert.ThrowsAsync(async () => await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken));
+ await Assert.ThrowsAsync(async () => await client.ListPromptsAsync(TestContext.Current.CancellationToken));
+ await Assert.ThrowsAsync(async () => await client.ListResourcesAsync(TestContext.Current.CancellationToken));
+ }
+}