summaryrefslogtreecommitdiff
path: root/tests/Agent.Mcp.TestHost
diff options
context:
space:
mode:
Diffstat (limited to 'tests/Agent.Mcp.TestHost')
-rw-r--r--tests/Agent.Mcp.TestHost/Agent.Mcp.TestHost.csproj8
-rw-r--r--tests/Agent.Mcp.TestHost/ConformanceTools.cs92
-rw-r--r--tests/Agent.Mcp.TestHost/Program.cs72
3 files changed, 172 insertions, 0 deletions
diff --git a/tests/Agent.Mcp.TestHost/Agent.Mcp.TestHost.csproj b/tests/Agent.Mcp.TestHost/Agent.Mcp.TestHost.csproj
new file mode 100644
index 0000000..ff1c533
--- /dev/null
+++ b/tests/Agent.Mcp.TestHost/Agent.Mcp.TestHost.csproj
@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ </PropertyGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\Agent.Mcp\Agent.Mcp.csproj" />
+ </ItemGroup>
+</Project>
diff --git a/tests/Agent.Mcp.TestHost/ConformanceTools.cs b/tests/Agent.Mcp.TestHost/ConformanceTools.cs
new file mode 100644
index 0000000..3d8879d
--- /dev/null
+++ b/tests/Agent.Mcp.TestHost/ConformanceTools.cs
@@ -0,0 +1,92 @@
+using System.ComponentModel;
+using ModelContextProtocol;
+using ModelContextProtocol.Protocol;
+using ModelContextProtocol.Server;
+
+#pragma warning disable MCP9005 // The frozen alpha conformance fixture deliberately probes the legacy sampling capability.
+
+namespace Agent.Mcp.TestHost;
+
+[McpServerToolType]
+public sealed class ConformanceTools
+{
+ [McpServerTool(Name = "test_missing_capability")]
+ [Description("Conformance-only sampling capability probe.")]
+ public async Task<string> TestMissingCapability(McpServer server, CancellationToken cancellationToken)
+ {
+ await server.SampleAsync(new CreateMessageRequestParams
+ {
+ MaxTokens = 1,
+ Messages = [new SamplingMessage { Role = Role.User, Content = [new TextContentBlock { Text = "probe" }] }]
+ }, cancellationToken).ConfigureAwait(false);
+ return "sampled";
+ }
+
+ [McpServerTool(Name = "test_streaming_elicitation")]
+ [Description("Conformance-only response-stream framing probe.")]
+ public string TestStreamingElicitation() => "stream complete";
+
+ [McpServerTool(Name = "test_logging_tool")]
+ [Description("Conformance-only request log-level probe.")]
+ public string TestLoggingTool() => "no request-scoped log emitted";
+
+ [McpServerTool(Name = "confirm_delete")]
+ [Description("Conformance-only elicitation task fixture.")]
+ public string ConfirmDelete(RequestContext<CallToolRequestParams> context, string filename)
+ {
+ if (context.Params?.InputResponses?.Count > 0) return $"delete {filename}: accepted";
+ throw new InputRequiredException(new Dictionary<string, InputRequest>
+ {
+ ["confirm-delete"] = Elicitation($"Confirm deletion of {filename}", "confirm", new ElicitRequestParams.BooleanSchema())
+ }, "confirm-delete");
+ }
+
+ [McpServerTool(Name = "multi_input")]
+ [Description("Conformance-only multiple elicitation task fixture.")]
+ public string MultiInput(RequestContext<CallToolRequestParams> context)
+ {
+ if (context.Params?.InputResponses?.Count >= 2) return "both inputs received";
+ throw new InputRequiredException(new Dictionary<string, InputRequest>
+ {
+ ["first"] = Elicitation("First confirmation", "confirm", new ElicitRequestParams.BooleanSchema()),
+ ["second"] = Elicitation("Second confirmation", "confirm", new ElicitRequestParams.BooleanSchema())
+ }, "multi-input");
+ }
+
+ [McpServerTool(Name = "test_tool_with_task")]
+ [Description("Conformance-only MRTR and Tasks composition fixture.")]
+ public string TestToolWithTask(RequestContext<CallToolRequestParams> context)
+ {
+ if (context.Params?.InputResponses is { Count: > 0 } responses)
+ {
+ string response = System.Text.Json.JsonSerializer.Serialize(responses);
+ return response.Contains("Alice", StringComparison.Ordinal) ? "Hello, Alice" : "Hello, user";
+ }
+ throw new InputRequiredException(new Dictionary<string, InputRequest>
+ {
+ ["user-name"] = Elicitation("Provide a user name", "name", new ElicitRequestParams.StringSchema())
+ }, "user-name");
+ }
+
+ private static InputRequest Elicitation(string message, string propertyName, ElicitRequestParams.PrimitiveSchemaDefinition schema) =>
+ InputRequest.ForElicitation(new ElicitRequestParams
+ {
+ Mode = "form",
+ Message = message,
+ RequestedSchema = new ElicitRequestParams.RequestSchema
+ {
+ Properties = new Dictionary<string, ElicitRequestParams.PrimitiveSchemaDefinition> { [propertyName] = schema },
+ Required = [propertyName]
+ }
+ });
+
+ public sealed class Confirmation
+ {
+ public bool Confirm { get; set; }
+ }
+
+ public sealed class UserName
+ {
+ public string? Name { get; set; }
+ }
+}
diff --git a/tests/Agent.Mcp.TestHost/Program.cs b/tests/Agent.Mcp.TestHost/Program.cs
new file mode 100644
index 0000000..3447beb
--- /dev/null
+++ b/tests/Agent.Mcp.TestHost/Program.cs
@@ -0,0 +1,72 @@
+using Agent.Core.Localization;
+using Agent.Core.Ollama;
+using Agent.Core.Runtime;
+using Agent.Mcp;
+using ModelContextProtocol.Server;
+using ModelContextProtocol.Protocol;
+using System.Reflection;
+using Microsoft.Extensions.DependencyInjection;
+using ModelContextProtocol;
+
+namespace Agent.Mcp.TestHost;
+
+public static class Marker;
+
+internal static class Program
+{
+ public static async Task Main(string[] args)
+ {
+ if (args.Length is not (2 or 4)) throw new ArgumentException("Expected <locale> <task-state> or http <port> <locale> <task-state>.");
+ bool http = args.Length == 4;
+ int offset = http ? 2 : 0;
+ LocaleCatalog locale = LocaleCatalog.Load(args[offset], "en");
+ var ollama = new OllamaSettings(new Uri("http://127.0.0.1:1"), "unused", false, "0", new Dictionary<string, System.Text.Json.JsonElement>());
+ var store = new McpTaskStoreSettings(args[offset + 1], TimeSpan.FromHours(24), 1000);
+ var runtime = new AgentRuntimeSettings("", "en", [], "sandbox");
+ McpServerTool[] fixtures =
+ [
+ McpServerTool.Create((string name) => $"Hello, {name}!", new McpServerToolCreateOptions { Name = "greet" }),
+ McpServerTool.Create(async (int seconds, CancellationToken cancellationToken) =>
+ {
+ await Task.Delay(TimeSpan.FromSeconds(seconds), cancellationToken).ConfigureAwait(false);
+ return $"Computed after {seconds} seconds";
+ }, new McpServerToolCreateOptions { Name = "slow_compute" }),
+ McpServerTool.Create(async () =>
+ {
+ await Task.Delay(1000).ConfigureAwait(false);
+ return new CallToolResult { IsError = true, Content = [new TextContentBlock { Text = "intentional tool error" }] };
+ }, new McpServerToolCreateOptions { Name = "failing_job" }),
+ McpServerTool.Create((Func<string>)(() => throw new InvalidOperationException("intentional protocol error")),
+ new McpServerToolCreateOptions { Name = "protocol_error_job" }),
+ .. typeof(ConformanceTools).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
+ .Where(static method => method.GetCustomAttribute<McpServerToolAttribute>() is not null)
+ .Select(method => McpServerTool.Create(method, new ConformanceTools()))
+ ];
+ var taskTools = new HashSet<string>(["agent_run", "slow_compute", "confirm_delete", "multi_input", "protocol_error_job"], StringComparer.Ordinal);
+ var requiredTaskTools = new HashSet<string>(["failing_job", "test_tool_with_task"], StringComparer.Ordinal);
+ if (http)
+ await McpHost.RunHttpAsync(locale, ollama, runtime, new VisionLimits(), "127.0.0.1", int.Parse(args[1], System.Globalization.CultureInfo.InvariantCulture), CancellationToken.None,
+ taskStoreSettings: store, additionalTools: fixtures, taskCapableTools: taskTools, requiredTaskTools: requiredTaskTools,
+ configureBuilder: ConfigureConformanceFilters).ConfigureAwait(false);
+ else
+ await McpHost.RunStdioAsync(locale, ollama, runtime, new VisionLimits(), CancellationToken.None, taskStoreSettings: store).ConfigureAwait(false);
+ }
+
+ private static void ConfigureConformanceFilters(IMcpServerBuilder builder)
+ {
+ builder.WithRequestFilters(filters => filters.AddCallToolFilter(next => async (context, cancellationToken) =>
+ {
+ if (context.Params?.Name == "test_missing_capability")
+ {
+#pragma warning disable MCP9005 // Frozen alpha conformance probes the legacy sampling capability shape.
+ throw new MissingRequiredClientCapabilityException(
+ new ClientCapabilities { Sampling = new SamplingCapability() },
+ "The conformance diagnostic requires the sampling capability.");
+#pragma warning restore MCP9005
+ }
+ if (context.Params?.Name == "protocol_error_job")
+ throw new McpProtocolException("intentional protocol error", McpErrorCode.InternalError);
+ return await next(context, cancellationToken).ConfigureAwait(false);
+ }));
+ }
+}