1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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 = [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<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();
}
|