using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; using ModelContextProtocol.Tests.Utils; using System.IO.Pipelines; namespace ModelContextProtocol.Tests; public abstract class ClientServerTestBase : LoggedTest, IAsyncDisposable { private readonly Pipe _clientToServerPipe = new(); private readonly Pipe _serverToClientPipe = new(); private readonly IMcpServerBuilder _builder; private readonly CancellationTokenSource _cts; private readonly Task _serverTask; public ClientServerTestBase(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { ServiceCollection sc = new(); sc.AddLogging(); sc.AddSingleton(XunitLoggerProvider); _builder = sc .AddMcpServer() .WithStreamServerTransport(_clientToServerPipe.Reader.AsStream(), _serverToClientPipe.Writer.AsStream()); ConfigureServices(sc, _builder); ServiceProvider = sc.BuildServiceProvider(validateScopes: true); _cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken); Server = ServiceProvider.GetRequiredService(); _serverTask = Server.RunAsync(_cts.Token); } protected McpServer Server { get; } protected IServiceProvider ServiceProvider { get; } protected virtual void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder) { } public async ValueTask DisposeAsync() { await _cts.CancelAsync(); _clientToServerPipe.Writer.Complete(); _serverToClientPipe.Writer.Complete(); await _serverTask; if (ServiceProvider is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync(); } else if (ServiceProvider is IDisposable disposable) { disposable.Dispose(); } _cts.Dispose(); Dispose(); } protected async Task CreateMcpClientForServer(McpClientOptions? clientOptions = null) { return await McpClient.CreateAsync( new StreamClientTransport( serverInput: _clientToServerPipe.Writer.AsStream(), _serverToClientPipe.Reader.AsStream(), LoggerFactory), clientOptions: clientOptions, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } }