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 | http "); return 2; } IClientTransport transport = args[0] == "stdio" ? new StdioClientTransport(new StdioClientTransportOptions { Name = "agent-mcp-operational-e2e", Command = Path.GetFullPath(args[1]), Arguments = [Path.GetFullPath(args[2])], 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 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 { ["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 { ["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().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(); }