Files
KimiFleet/McpStdioServer.cs
T

108 lines
5.9 KiB
C#

using System.Text.Json;
sealed class McpStdioServer(FleetHost fleet)
{
private static readonly JsonSerializerOptions Json = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
public async Task RunAsync()
{
string? line;
while ((line = await Console.In.ReadLineAsync()) is not null)
{
try
{
using var document = JsonDocument.Parse(line);
var request = document.RootElement;
if (!request.TryGetProperty("method", out var methodNode)) continue;
var method = methodNode.GetString();
if (!request.TryGetProperty("id", out var id)) continue; // notifications have no response
object result = method switch
{
"initialize" => new
{
protocolVersion = "2025-03-26",
capabilities = new { tools = new { } },
serverInfo = new { name = "KimiFleet", version = "0.1.0" },
instructions = "Use KimiFleet tools to create scoped Kimi K3 agents, assign work, and require peer review."
},
"tools/list" => new { tools = Tools },
"tools/call" => await CallToolAsync(request.GetProperty("params")),
_ => throw new InvalidOperationException($"Unsupported MCP method '{method}'.")
};
await SendAsync(id, result);
}
catch (Exception ex)
{
Console.Error.WriteLine($"[mcp] {ex.Message}");
await Console.Out.WriteLineAsync(JsonSerializer.Serialize(new { jsonrpc = "2.0", error = new { code = -32603, message = ex.Message } }, Json));
await Console.Out.FlushAsync();
}
}
}
private async Task<object> CallToolAsync(JsonElement call)
{
var name = call.GetProperty("name").GetString() ?? throw new InvalidOperationException("Tool call has no name.");
var arguments = call.TryGetProperty("arguments", out var value) ? value : default;
object payload = name switch
{
"fleet_list_agents" => fleet.ListAgents(),
"fleet_start_agent" => await fleet.StartAgentAsync(new FleetHost.AgentSpec(
Required(arguments, "name"), Required(arguments, "workspace"),
Optional(arguments, "role"), StringArray(arguments, "scopes"))),
"fleet_assign_task" => Assign(Required(arguments, "agent"), Required(arguments, "prompt")),
"fleet_request_review" => Review(Required(arguments, "reviewer"), Required(arguments, "author"), Optional(arguments, "context")),
"fleet_stop_agent" => Stop(Required(arguments, "agent")),
_ => throw new InvalidOperationException($"Unknown KimiFleet tool '{name}'.")
};
return new { content = new[] { new { type = "text", text = JsonSerializer.Serialize(payload, Json) } } };
}
private object Assign(string agent, string prompt)
{
fleet.Assign(agent, prompt);
return new { accepted = true, agent };
}
private object Review(string reviewer, string author, string? context)
{
fleet.Review(reviewer, author, context);
return new { accepted = true, reviewer, author };
}
private object Stop(string agent)
{
fleet.Stop(agent);
return new { stopped = agent };
}
private static string Required(JsonElement input, string name) =>
input.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(value.GetString())
? value.GetString()! : throw new InvalidOperationException($"'{name}' is required.");
private static string? Optional(JsonElement input, string name) =>
input.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.String ? value.GetString() : null;
private static string[]? StringArray(JsonElement input, string name) =>
input.TryGetProperty(name, out var values) && values.ValueKind == JsonValueKind.Array
? values.EnumerateArray().Where(x => x.ValueKind == JsonValueKind.String).Select(x => x.GetString()!).ToArray() : null;
private static async Task SendAsync(JsonElement id, object result)
{
var json = $"{{\"jsonrpc\":\"2.0\",\"id\":{id.GetRawText()},\"result\":{JsonSerializer.Serialize(result, Json)}}}";
await Console.Out.WriteLineAsync(json);
await Console.Out.FlushAsync();
}
private static readonly object[] Tools =
[
Tool("fleet_list_agents", "List active Kimi K3 agents, their roles, scope locks and state.", new { type = "object", properties = new { } }),
Tool("fleet_start_agent", "Start a yolo Kimi K3 ACP agent with exclusive source scopes.", new { type = "object", required = new[] { "name", "workspace" }, properties = new { name = new { type = "string" }, workspace = new { type = "string" }, role = new { type = "string" }, scopes = new { type = "array", items = new { type = "string" } } } }),
Tool("fleet_assign_task", "Assign an autonomous task to an active Kimi agent.", new { type = "object", required = new[] { "agent", "prompt" }, properties = new { agent = new { type = "string" }, prompt = new { type = "string" } } }),
Tool("fleet_request_review", "Ask a second agent for read-only peer review of an author's current diff.", new { type = "object", required = new[] { "reviewer", "author" }, properties = new { reviewer = new { type = "string" }, author = new { type = "string" }, context = new { type = "string" } } }),
Tool("fleet_stop_agent", "Stop an agent and release its exclusive scopes.", new { type = "object", required = new[] { "agent" }, properties = new { agent = new { type = "string" } } })
];
private static object Tool(string name, string description, object inputSchema) => new { name, description, inputSchema };
}