diff options
Diffstat (limited to 'tests/Agent.Mcp.TestHost')
| -rw-r--r-- | tests/Agent.Mcp.TestHost/Agent.Mcp.TestHost.csproj | 8 | ||||
| -rw-r--r-- | tests/Agent.Mcp.TestHost/ConformanceTools.cs | 92 | ||||
| -rw-r--r-- | tests/Agent.Mcp.TestHost/Program.cs | 72 |
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); + })); + } +} |
