using Microsoft.Extensions.DependencyInjection; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; namespace ModelContextProtocol.Tests.Server; /// /// Verifies that the server-to-client request methods (, /// , /// ) keep working when the negotiated protocol revision is /// 2026-07-28 on a stateful transport - for example, stdio. /// /// /// Under 2026-07-28 the spec removes the corresponding server-to-client request methods, but /// the SDK only fails fast in stateless mode (where the existing ThrowIf*Unsupported guards already /// throw "X is not supported in stateless mode" because is /// ). Stdio is implicitly stateful - one per process - so the /// legacy elicitation/create / sampling/createMessage / roots/list flow still works. /// Starting with 2026-07-28, Streamable HTTP servers are stateless by default, so those configurations /// throw through the existing stateless guard unless the author explicitly opts back into sessions. /// public sealed class July2026ProtocolBackcompatTests : ClientServerTestBase { public July2026ProtocolBackcompatTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper, startServer: false) { } protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder) { services.Configure(options => { options.ProtocolVersion = "2026-07-28"; }); mcpServerBuilder.WithTools([ McpServerTool.Create(ElicitToolAsync, new() { Name = "elicit-tool" }), McpServerTool.Create(SampleToolAsync, new() { Name = "sample-tool" }), McpServerTool.Create(RootsToolAsync, new() { Name = "roots-tool" }), ]); } [Fact] public async Task ElicitAsync_OnStatefulTransport_ResolvesViaLegacyRequest() { StartServer(); await using var client = await CreateMcpClientForServer(new McpClientOptions { ProtocolVersion = "2026-07-28", Capabilities = new ClientCapabilities { Elicitation = new ElicitationCapability(), }, Handlers = new McpClientHandlers { ElicitationHandler = (_, _) => new ValueTask(new ElicitResult { Action = "accept" }), }, }); var result = await client.CallToolAsync("elicit-tool", cancellationToken: TestContext.Current.CancellationToken); Assert.Equal("elicit-ok:accept", Assert.IsType(result.Content[0]).Text); } [Fact] public async Task SampleAsync_OnStatefulTransport_ResolvesViaLegacyRequest() { StartServer(); await using var client = await CreateMcpClientForServer(new McpClientOptions { ProtocolVersion = "2026-07-28", Capabilities = new ClientCapabilities { Sampling = new SamplingCapability(), }, Handlers = new McpClientHandlers { SamplingHandler = (_, _, _) => new ValueTask(new CreateMessageResult { Model = "test-model", Role = Role.Assistant, Content = [new TextContentBlock { Text = "hello back" }], }), }, }); var result = await client.CallToolAsync("sample-tool", cancellationToken: TestContext.Current.CancellationToken); Assert.Equal("sample-ok:hello back", Assert.IsType(result.Content[0]).Text); } [Fact] public async Task RequestRootsAsync_OnStatefulTransport_ResolvesViaLegacyRequest() { StartServer(); await using var client = await CreateMcpClientForServer(new McpClientOptions { ProtocolVersion = "2026-07-28", Capabilities = new ClientCapabilities { Roots = new RootsCapability(), }, Handlers = new McpClientHandlers { RootsHandler = (_, _) => new ValueTask(new ListRootsResult { Roots = [new Root { Uri = "file:///home", Name = "home" }], }), }, }); var result = await client.CallToolAsync("roots-tool", cancellationToken: TestContext.Current.CancellationToken); Assert.Equal("roots-ok:file:///home", Assert.IsType(result.Content[0]).Text); } private static async Task ElicitToolAsync(McpServer server, CancellationToken cancellationToken) { var elicit = await server.ElicitAsync(new ElicitRequestParams { Message = "Need input", RequestedSchema = new(), }, cancellationToken); return $"elicit-ok:{elicit.Action}"; } private static async Task SampleToolAsync(McpServer server, CancellationToken cancellationToken) { var sample = await server.SampleAsync(new CreateMessageRequestParams { Messages = [ new SamplingMessage { Role = Role.User, Content = [new TextContentBlock { Text = "ping" }], }, ], MaxTokens = 16, }, cancellationToken); var text = sample.Content.OfType().FirstOrDefault()?.Text; return $"sample-ok:{text}"; } private static async Task RootsToolAsync(McpServer server, CancellationToken cancellationToken) { var roots = await server.RequestRootsAsync(new ListRootsRequestParams(), cancellationToken); return $"roots-ok:{roots.Roots.FirstOrDefault()?.Uri}"; } }