summaryrefslogtreecommitdiff
path: root/tests/Agent.Core.Tests/McpHttpTests.cs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/Agent.Core.Tests/McpHttpTests.cs')
-rw-r--r--tests/Agent.Core.Tests/McpHttpTests.cs88
1 files changed, 88 insertions, 0 deletions
diff --git a/tests/Agent.Core.Tests/McpHttpTests.cs b/tests/Agent.Core.Tests/McpHttpTests.cs
new file mode 100644
index 0000000..7e3f1cf
--- /dev/null
+++ b/tests/Agent.Core.Tests/McpHttpTests.cs
@@ -0,0 +1,88 @@
+using System.Net;
+using System.Net.Sockets;
+using Agent.Core.Localization;
+using Agent.Core.Ollama;
+using Agent.Core.Runtime;
+using Agent.Mcp;
+using ModelContextProtocol;
+using ModelContextProtocol.Client;
+
+namespace Agent.Core.Tests;
+
+public sealed class McpHttpTests
+{
+ [Fact(Timeout = 30_000)]
+ public async Task Loopback_streamable_http_works_and_get_sse_legacy_sse_are_rejected()
+ {
+ int port = FreePort();
+ using var stop = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken);
+ LocaleCatalog locale = LocaleCatalog.Load(Path.GetFullPath("config/locales"), "en");
+ var settings = new OllamaSettings(new Uri("http://127.0.0.1:1"), "unused", false, "0", new Dictionary<string, System.Text.Json.JsonElement>());
+ Task server = McpHost.RunHttpAsync(locale, settings, new AgentRuntimeSettings("", "en", [], "sandbox"), new VisionLimits(), "127.0.0.1", port, stop.Token);
+ using var http = new HttpClient();
+ Uri endpoint = new($"http://127.0.0.1:{port}/mcp");
+ await WaitUntilListening(http, endpoint, TestContext.Current.CancellationToken);
+
+ var transport = new HttpClientTransport(new HttpClientTransportOptions
+ {
+ Name = "http-test",
+ Endpoint = endpoint,
+ TransportMode = HttpTransportMode.StreamableHttp
+ });
+ await using (McpClient client = await McpClient.CreateAsync(transport, cancellationToken: TestContext.Current.CancellationToken))
+ {
+ IList<McpClientTool> tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
+ Assert.Equal(["agent_run", "agent_status"], tools.Select(static tool => tool.Name).Order(StringComparer.Ordinal));
+ }
+
+ using HttpResponseMessage get = await http.GetAsync(endpoint, TestContext.Current.CancellationToken);
+ Assert.Equal(HttpStatusCode.MethodNotAllowed, get.StatusCode);
+ using var hostileOrigin = new HttpRequestMessage(HttpMethod.Post, endpoint);
+ hostileOrigin.Headers.Add("Origin", "https://example.invalid");
+ using HttpResponseMessage forbidden = await http.SendAsync(hostileOrigin, TestContext.Current.CancellationToken);
+ Assert.Equal(HttpStatusCode.Forbidden, forbidden.StatusCode);
+ using var loopbackOrigin = new HttpRequestMessage(HttpMethod.Get, endpoint);
+ loopbackOrigin.Headers.Add("Origin", $"http://127.0.0.1:{port}");
+ using HttpResponseMessage loopbackAllowed = await http.SendAsync(loopbackOrigin, TestContext.Current.CancellationToken);
+ Assert.Equal(HttpStatusCode.MethodNotAllowed, loopbackAllowed.StatusCode);
+ Assert.Equal(HttpStatusCode.NotFound, (await http.GetAsync(new Uri($"http://127.0.0.1:{port}/sse"), TestContext.Current.CancellationToken)).StatusCode);
+ Assert.Equal(HttpStatusCode.NotFound, (await http.PostAsync(new Uri($"http://127.0.0.1:{port}/message"), null, TestContext.Current.CancellationToken)).StatusCode);
+
+ await stop.CancelAsync();
+ await server;
+ }
+
+ [Fact]
+ public async Task Non_loopback_bind_is_rejected_before_server_start()
+ {
+ LocaleCatalog locale = LocaleCatalog.Load(Path.GetFullPath("config/locales"), "en");
+ var settings = new OllamaSettings(new Uri("http://127.0.0.1:1"), "unused", false, "0", new Dictionary<string, System.Text.Json.JsonElement>());
+ await Assert.ThrowsAsync<ArgumentException>(() => McpHost.RunHttpAsync(locale, settings,
+ new AgentRuntimeSettings("", "en", [], "sandbox"), new VisionLimits(), "0.0.0.0", 8080, TestContext.Current.CancellationToken));
+ }
+
+ private static int FreePort()
+ {
+ TcpListener listener = new(IPAddress.Loopback, 0);
+ listener.Start();
+ int port = ((IPEndPoint)listener.LocalEndpoint).Port;
+ listener.Stop();
+ return port;
+ }
+
+ private static async Task WaitUntilListening(HttpClient client, Uri endpoint, CancellationToken cancellationToken)
+ {
+ for (int attempt = 0; attempt < 100; attempt++)
+ {
+ try
+ {
+ using HttpResponseMessage response = await client.GetAsync(endpoint, cancellationToken);
+ return;
+ }
+ catch (HttpRequestException) when (attempt < 99)
+ {
+ await Task.Delay(25, cancellationToken);
+ }
+ }
+ }
+}