mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 12:44:43 +00:00
mcp: simplify I/O architecture and remove test harness
This commit is contained in:
@@ -5,40 +5,27 @@ const resources = @import("resources.zig");
|
||||
const Server = @import("Server.zig");
|
||||
const tools = @import("tools.zig");
|
||||
|
||||
pub fn processRequests(server: *Server, in_stream: std.fs.File) !void {
|
||||
server.is_running.store(true, .release);
|
||||
pub fn processRequests(server: *Server, reader: *std.io.Reader) !void {
|
||||
var arena: std.heap.ArenaAllocator = .init(server.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
const Streams = enum { stdin };
|
||||
var poller = std.io.poll(server.allocator, Streams, .{ .stdin = in_stream });
|
||||
defer poller.deinit();
|
||||
while (true) {
|
||||
_ = arena.reset(.retain_capacity);
|
||||
const aa = arena.allocator();
|
||||
|
||||
const r = poller.reader(.stdin);
|
||||
const buffered_line = reader.takeDelimiter('\n') catch |err| switch (err) {
|
||||
error.StreamTooLong => {
|
||||
log.err(.mcp, "Message too long", .{});
|
||||
continue;
|
||||
},
|
||||
else => return err,
|
||||
} orelse break;
|
||||
|
||||
while (server.is_running.load(.acquire)) {
|
||||
const poll_result = try poller.pollTimeout(100 * std.time.ns_per_ms);
|
||||
|
||||
if (!poll_result) {
|
||||
// EOF or all streams closed
|
||||
server.is_running.store(false, .release);
|
||||
break;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const buffered = r.buffered();
|
||||
const newline_idx = std.mem.indexOfScalar(u8, buffered, '\n') orelse break;
|
||||
const line = buffered[0 .. newline_idx + 1];
|
||||
|
||||
const trimmed = std.mem.trim(u8, line, " \r\n\t");
|
||||
if (trimmed.len > 0) {
|
||||
var arena = std.heap.ArenaAllocator.init(server.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
handleMessage(server, arena.allocator(), trimmed) catch |err| {
|
||||
log.err(.mcp, "Failed to handle message", .{ .err = err, .msg = trimmed });
|
||||
};
|
||||
}
|
||||
|
||||
r.toss(line.len);
|
||||
const trimmed = std.mem.trim(u8, buffered_line, " \r\t");
|
||||
if (trimmed.len > 0) {
|
||||
handleMessage(server, aa, trimmed) catch |err| {
|
||||
log.err(.mcp, "Failed to handle message", .{ .err = err, .msg = trimmed });
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,35 +93,36 @@ fn handleInitialize(server: *Server, req: protocol.Request) !void {
|
||||
}
|
||||
|
||||
const testing = @import("../testing.zig");
|
||||
const McpHarness = @import("testing.zig").McpHarness;
|
||||
|
||||
test "handleMessage - synchronous unit tests" {
|
||||
// We need a server, but we want it to write to our fbs
|
||||
// Server.init currently takes std.fs.File, we might need to refactor it
|
||||
// to take a generic writer if we want to be truly "cranky" and avoid OS files.
|
||||
// For now, let's use the harness as it's already set up, but call handleMessage directly.
|
||||
const harness = try McpHarness.init(testing.allocator, testing.test_app);
|
||||
defer harness.deinit();
|
||||
const allocator = testing.allocator;
|
||||
const app = testing.test_app;
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
||||
var out_alloc = std.io.Writer.Allocating.init(allocator);
|
||||
defer out_alloc.deinit();
|
||||
|
||||
var server = try Server.init(allocator, app, &out_alloc.writer);
|
||||
defer server.deinit();
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||
defer arena.deinit();
|
||||
const aa = arena.allocator();
|
||||
|
||||
// 1. Valid request
|
||||
try handleMessage(harness.server, aa,
|
||||
try handleMessage(server, aa,
|
||||
\\{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}
|
||||
);
|
||||
const resp1 = try harness.readResponse(aa);
|
||||
try testing.expect(std.mem.indexOf(u8, resp1, "\"id\":1") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, resp1, "\"name\":\"lightpanda\"") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, out_alloc.writer.buffered(), "\"id\":1") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, out_alloc.writer.buffered(), "\"name\":\"lightpanda\"") != null);
|
||||
out_alloc.writer.end = 0;
|
||||
|
||||
// 2. Method not found
|
||||
try handleMessage(harness.server, aa,
|
||||
try handleMessage(server, aa,
|
||||
\\{"jsonrpc":"2.0","id":2,"method":"unknown_method"}
|
||||
);
|
||||
const resp2 = try harness.readResponse(aa);
|
||||
try testing.expect(std.mem.indexOf(u8, resp2, "\"id\":2") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, resp2, "\"code\":-32601") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, out_alloc.writer.buffered(), "\"id\":2") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, out_alloc.writer.buffered(), "\"code\":-32601") != null);
|
||||
out_alloc.writer.end = 0;
|
||||
|
||||
// 3. Parse error
|
||||
{
|
||||
@@ -142,9 +130,8 @@ test "handleMessage - synchronous unit tests" {
|
||||
log.opts.filter_scopes = &.{.mcp};
|
||||
defer log.opts.filter_scopes = old_filter;
|
||||
|
||||
try handleMessage(harness.server, aa, "invalid json");
|
||||
const resp3 = try harness.readResponse(aa);
|
||||
try testing.expect(std.mem.indexOf(u8, resp3, "\"id\":null") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, resp3, "\"code\":-32700") != null);
|
||||
try handleMessage(server, aa, "invalid json");
|
||||
try testing.expect(std.mem.indexOf(u8, out_alloc.writer.buffered(), "\"id\":null") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, out_alloc.writer.buffered(), "\"code\":-32700") != null);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user