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(options: null, 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(options: null, 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(options: null, 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(options: null, 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", options: null, 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(cancellationToken: TestContext.Current.CancellationToken)); await Assert.ThrowsAsync(async () => await client.ListResourcesAsync(cancellationToken: TestContext.Current.CancellationToken)); } }