diff options
Diffstat (limited to 'scripts/E2EClient')
| -rw-r--r-- | scripts/E2EClient/E2EClient.csproj | 9 | ||||
| -rw-r--r-- | scripts/E2EClient/Program.cs | 67 |
2 files changed, 76 insertions, 0 deletions
diff --git a/scripts/E2EClient/E2EClient.csproj b/scripts/E2EClient/E2EClient.csproj new file mode 100644 index 0000000..50fabca --- /dev/null +++ b/scripts/E2EClient/E2EClient.csproj @@ -0,0 +1,9 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net10.0</TargetFramework> + </PropertyGroup> + <ItemGroup> + <PackageReference Include="ModelContextProtocol" /> + </ItemGroup> +</Project> diff --git a/scripts/E2EClient/Program.cs b/scripts/E2EClient/Program.cs new file mode 100644 index 0000000..ee7bb75 --- /dev/null +++ b/scripts/E2EClient/Program.cs @@ -0,0 +1,67 @@ +using ModelContextProtocol; +using ModelContextProtocol.Client; +using ModelContextProtocol.Protocol; + +if (args.Length < 4 || args[0] is not ("stdio" or "http")) +{ + Console.Error.WriteLine("Usage: E2EClient stdio <agent-exe> <config> <request> | http <endpoint> <unused> <request>"); + return 2; +} + +IClientTransport transport = args[0] == "stdio" + ? new StdioClientTransport(new StdioClientTransportOptions + { + Name = "agent-mcp-operational-e2e", + Command = Path.GetFullPath(args[1]), + Arguments = ["serve", "--config", Path.GetFullPath(args[2]), "--lang", "ja", "--transport", "stdio"], + WorkingDirectory = Path.GetDirectoryName(Path.GetFullPath(args[1]))! + }) + : new HttpClientTransport(new HttpClientTransportOptions + { + Name = "agent-mcp-operational-e2e-http", + Endpoint = new Uri(args[1], UriKind.Absolute), + TransportMode = HttpTransportMode.StreamableHttp + }); + +await using McpClient client = await McpClient.CreateAsync(transport); +IList<McpClientTool> tools = await client.ListToolsAsync(); +if (!tools.Select(static tool => tool.Name).Order(StringComparer.Ordinal).SequenceEqual(["agent_run", "agent_status"])) + throw new InvalidOperationException("Unexpected MCP public tool surface."); + +_ = await ExecuteTask(client, args[3], null); +if (args.Length >= 5) _ = await ExecuteTask(client, args[4], null); +return 0; + +static async Task<(Guid TaskId, Guid SessionId)> ExecuteTask(McpClient client, string request, Guid? sessionId) +{ + var runArguments = new Dictionary<string, object?> { ["request"] = request, ["temperature"] = 0.0 }; + if (sessionId is not null) runArguments["session_id"] = sessionId.Value; + CallToolResult run = await client.CallToolAsync("agent_run", runArguments); + if (run.IsError is true) throw new InvalidOperationException(Text(run)); + string accepted = Text(run); + Guid taskId = ReadGuid(accepted, "task_id"); + Guid acceptedSessionId = ReadGuid(accepted, "session_id"); + Console.WriteLine(accepted); + for (int poll = 0; poll < 600; poll++) + { + await Task.Delay(500); + CallToolResult status = await client.CallToolAsync("agent_status", new Dictionary<string, object?> { ["task_id"] = taskId, ["session_id"] = acceptedSessionId }); + string text = Text(status); + string state = ReadValue(text, "status"); + if (state is "completed" or "failed" or "cancelled") + { + Console.WriteLine(text); + if (state != "completed") throw new InvalidOperationException($"Task {taskId} ended in state {state}."); + return (taskId, acceptedSessionId); + } + } + throw new TimeoutException($"Task {taskId} did not reach a terminal state."); +} + +static string Text(CallToolResult result) => string.Join('\n', result.Content.OfType<TextContentBlock>().Select(static block => block.Text)); +static Guid ReadGuid(string text, string key) => Guid.Parse(ReadValue(text, key)); +static string ReadValue(string text, string key) +{ + string prefix = key + ":"; + return text.Split('\n').First(line => line.StartsWith(prefix, StringComparison.Ordinal))[prefix.Length..].Trim(); +} |
