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}";
}
}