Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com> Co-authored-by: Eric StJohn <ericstj@microsoft.com>
111 lines
No EOL
3.6 KiB
C#
111 lines
No EOL
3.6 KiB
C#
using System.Diagnostics;
|
|
using System.Net;
|
|
using ModelContextProtocol.Tests.Utils;
|
|
|
|
namespace ModelContextProtocol.Tests;
|
|
|
|
public class EverythingSseServerFixture : IAsyncDisposable
|
|
{
|
|
private readonly int _port;
|
|
private readonly string _containerName;
|
|
|
|
public static bool IsDockerAvailable => _isDockerAvailable ??= CheckIsDockerAvailable();
|
|
private static bool? _isDockerAvailable;
|
|
|
|
public EverythingSseServerFixture(int port)
|
|
{
|
|
_port = port;
|
|
_containerName = $"mcp-everything-server-{_port}";
|
|
}
|
|
|
|
public async Task StartAsync()
|
|
{
|
|
var processStartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "docker",
|
|
Arguments = $"run -p {_port}:3001 --name {_containerName} --rm tzolov/mcp-everything-server:v1",
|
|
RedirectStandardInput = true,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
};
|
|
|
|
_ = Process.Start(processStartInfo)
|
|
?? throw new InvalidOperationException($"Could not start process for {processStartInfo.FileName} with '{processStartInfo.Arguments}'.");
|
|
|
|
// Poll until the server is ready (up to 30 seconds)
|
|
using var httpClient = new HttpClient { Timeout = TestConstants.HttpClientPollingTimeout };
|
|
var endpoint = $"http://localhost:{_port}/sse";
|
|
var deadline = DateTime.UtcNow.AddSeconds(30);
|
|
|
|
while (DateTime.UtcNow < deadline)
|
|
{
|
|
try
|
|
{
|
|
using var response = await httpClient.GetAsync(endpoint, HttpCompletionOption.ResponseHeadersRead);
|
|
if (response.IsSuccessStatusCode || response.StatusCode is HttpStatusCode.MethodNotAllowed)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
catch (Exception e) when (e is HttpRequestException or OperationCanceledException)
|
|
{
|
|
// server not ready
|
|
}
|
|
|
|
await Task.Delay(100);
|
|
}
|
|
|
|
throw new InvalidOperationException($"Docker container failed to start within 30 seconds on port {_port}");
|
|
}
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
try
|
|
{
|
|
|
|
// Stop the container
|
|
var stopInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "docker",
|
|
Arguments = $"stop {_containerName}",
|
|
UseShellExecute = false
|
|
};
|
|
|
|
using var stopProcess = Process.Start(stopInfo)
|
|
?? throw new InvalidOperationException($"Could not stop process for {stopInfo.FileName} with '{stopInfo.Arguments}'.");
|
|
await stopProcess.WaitForExitAsync(TestConstants.DefaultTimeout);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Log the exception but don't throw
|
|
await Console.Error.WriteLineAsync($"Error stopping Docker container: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private static bool CheckIsDockerAvailable()
|
|
{
|
|
#if NET
|
|
try
|
|
{
|
|
ProcessStartInfo processStartInfo = new()
|
|
{
|
|
FileName = "docker",
|
|
// "docker info" returns a non-zero exit code if docker engine is not running.
|
|
Arguments = "info",
|
|
UseShellExecute = false,
|
|
};
|
|
|
|
using var process = Process.Start(processStartInfo);
|
|
process?.WaitForExit();
|
|
return process?.ExitCode is 0;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
#else
|
|
// Do not run docker tests using .NET framework.
|
|
return false;
|
|
#endif
|
|
}
|
|
} |