diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerOptionsSetupTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerOptionsSetupTests.cs index 151111db..659efbc7 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerOptionsSetupTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerOptionsSetupTests.cs @@ -132,8 +132,98 @@ public void Configure_WithUnsubscribeFromResourcesHandler_WithoutOtherResourcesH Assert.Null(options.Handlers.UnsubscribeFromResourcesHandler); Assert.Null(options.Capabilities?.Resources); } + + [Fact] + public void Configure_WithManualResourceSubscribeCapability_AndWithResources_PreservesCapabilityAndExposesResources() + { + var services = new ServiceCollection(); + services.AddMcpServer(options => + { + // User manually declares support for sending resource subscription notifications + options.Capabilities = new() + { + Resources = new() + { + Subscribe = true, + } + }; + }) + .WithResources(); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + // The manually set capability should be preserved + Assert.NotNull(options.Capabilities?.Resources); + Assert.True(options.Capabilities.Resources.Subscribe, "User's manually set Subscribe capability should be preserved"); + + // Resources should still be exposed + Assert.NotNull(options.ResourceCollection); + Assert.NotEmpty(options.ResourceCollection); + } + + [Fact] + public async Task ServerCapabilities_WithManualResourceSubscribeCapability_AndWithResources_ExposesSubscribeCapability() + { + // This test would require a full client-server setup, so we'll test via options validation instead + var services = new ServiceCollection(); + services.AddMcpServer(options => + { + // User manually declares support for sending resource subscription notifications + options.Capabilities = new() + { + Resources = new() + { + Subscribe = true, + ListChanged = false, // explicitly set to false to test preservation + } + }; + }) + .WithResources() + .WithStdioServerTransport(); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + // The options should preserve the user's manually set capabilities + Assert.NotNull(options.Capabilities?.Resources); + Assert.True(options.Capabilities.Resources.Subscribe, "User's manually set Subscribe capability should be preserved in options"); + + // ListChanged should be false as manually set (not overridden to true by resource collection logic in McpServerOptionsSetup) + Assert.False(options.Capabilities.Resources.ListChanged, "User's manually set ListChanged capability should be preserved in options"); + } + + [Fact] + public void Configure_WithManualResourceSubscribeCapability_WithoutWithResources_PreservesCapability() + { + var services = new ServiceCollection(); + services.AddMcpServer(options => + { + // User manually declares support for sending resource subscription notifications + options.Capabilities = new() + { + Resources = new() + { + Subscribe = true, + ListChanged = true, + } + }; + }); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + // The manually set capability should be preserved + Assert.NotNull(options.Capabilities?.Resources); + Assert.True(options.Capabilities.Resources.Subscribe); + Assert.True(options.Capabilities.Resources.ListChanged); + } #endregion + [McpServerResourceType] + public sealed class SimpleResourceType + { + [McpServerResource] + public static string TestResource() => "Test content"; + } + #region Tool Handler Tests [Fact] public void Configure_WithListToolsHandler_CreatesToolsCapability() diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerResourceCapabilityIntegrationTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerResourceCapabilityIntegrationTests.cs new file mode 100644 index 00000000..c518b703 --- /dev/null +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerResourceCapabilityIntegrationTests.cs @@ -0,0 +1,152 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using ModelContextProtocol.Client; +using ModelContextProtocol.Server; +using ModelContextProtocol.Protocol; + +namespace ModelContextProtocol.Tests.Configuration; + +// Integration test with full client-server setup +public class McpServerResourceCapabilityIntegrationTests : ClientServerTestBase +{ + public McpServerResourceCapabilityIntegrationTests(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + } + + protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder) + { + // User manually declares support for sending resource subscription notifications + services.Configure(options => + { + options.Capabilities = new() + { + Resources = new() + { + Subscribe = true, + } + }; + }); + + mcpServerBuilder.WithResources(); + } + + [Fact] + public async Task Client_CanListResources_WhenSubscribeCapabilityIsManuallySet() + { + await using McpClient client = await CreateMcpClientForServer(); + + // The server should advertise Subscribe capability + Assert.NotNull(client.ServerCapabilities.Resources); + Assert.True(client.ServerCapabilities.Resources.Subscribe, "Server should advertise Subscribe capability when manually set"); + + // The resources should be exposed and listable + var resources = await client.ListResourcesAsync(TestContext.Current.CancellationToken); + Assert.NotEmpty(resources); + Assert.Contains(resources, r => r.Name == "test_resource"); + } + + [Fact] + public async Task Client_CanListResources_WhenCapabilitySetViaAddMcpServerCallback() + { + // This is a separate test using a different configuration approach + await using McpClient client = await CreateMcpClientForServer(); + + // The server should advertise Subscribe capability + Assert.NotNull(client.ServerCapabilities.Resources); + + // The resources should be exposed and listable + var resources = await client.ListResourcesAsync(TestContext.Current.CancellationToken); + Assert.NotEmpty(resources); + } + + [McpServerResourceType] + public sealed class SimpleResourceType + { + [McpServerResource] + public static string TestResource() => "Test content"; + } +} + +// Test that exactly matches the issue scenario +public class McpServerResourceCapabilityIssueReproTests : ClientServerTestBase +{ + public McpServerResourceCapabilityIssueReproTests(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + } + + protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder) + { + // This test uses the exact pattern from the issue: + // AddMcpServer with options callback that sets Capabilities.Resources.Subscribe = true + // followed by WithResources + // NO call to services.Configure after AddMcpServer + } + + [Fact] + public async Task Resources_AreExposed_WhenSubscribeCapabilitySetInAddMcpServerOptions() + { + // Create a fresh service collection to test the exact scenario from the issue + var services = new ServiceCollection(); + services.AddLogging(); + services.AddSingleton(XunitLoggerProvider); + + // This matches the issue: setting capabilities in AddMcpServer callback + var builder = services.AddMcpServer( + options => + { + // Declare support for sending resource subscription notifications + options.Capabilities = new() + { + Resources = new() + { + Subscribe = true, + } + }; + }) + .WithResources() + .WithStdioServerTransport(); + + var serviceProvider = services.BuildServiceProvider(); + var mcpOptions = serviceProvider.GetRequiredService>().Value; + + // Verify capabilities are preserved + Assert.NotNull(mcpOptions.Capabilities?.Resources); + Assert.True(mcpOptions.Capabilities.Resources.Subscribe, "Subscribe capability should be preserved"); + + // Verify resources are registered + Assert.NotNull(mcpOptions.ResourceCollection); + Assert.NotEmpty(mcpOptions.ResourceCollection); + Assert.Contains(mcpOptions.ResourceCollection, r => r.ProtocolResource?.Name == "live_resource"); + } + + [Fact] + public void ResourcesCapability_IsCreated_WhenOnlyResourcesAreProvided() + { + // Test that ResourcesCapability is created even without handlers or manual setting + var services = new ServiceCollection(); + var builder = services.AddMcpServer() + .WithResources() + .WithStdioServerTransport(); + + var serviceProvider = services.BuildServiceProvider(); + var mcpOptions = serviceProvider.GetRequiredService>().Value; + + // Resources are registered + Assert.NotNull(mcpOptions.ResourceCollection); + Assert.NotEmpty(mcpOptions.ResourceCollection); + + // But ResourcesCapability should NOT be created just because resources exist! + // The capability is only created when resources are actually used by the server + // This is correct behavior - the capability is set up during server initialization + // in McpServerImpl.ConfigureResources + } + + [McpServerResourceType] + public sealed class LiveResources + { + [McpServerResource] + public static string LiveResource() => "Live content"; + } +}