mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 12:44:43 +00:00
144 lines
4.7 KiB
Zig
144 lines
4.7 KiB
Zig
const std = @import("std");
|
|
const lp = @import("lightpanda");
|
|
const protocol = @import("protocol.zig");
|
|
const resources = @import("resources.zig");
|
|
const Server = @import("Server.zig");
|
|
const tools = @import("tools.zig");
|
|
|
|
pub fn processRequests(server: *Server, reader: *std.io.Reader) !void {
|
|
var arena: std.heap.ArenaAllocator = .init(server.allocator);
|
|
defer arena.deinit();
|
|
|
|
while (true) {
|
|
_ = arena.reset(.retain_capacity);
|
|
const aa = arena.allocator();
|
|
|
|
const buffered_line = reader.takeDelimiter('\n') catch |err| switch (err) {
|
|
error.StreamTooLong => {
|
|
log.err(.mcp, "Message too long", .{});
|
|
continue;
|
|
},
|
|
else => return err,
|
|
} orelse break;
|
|
|
|
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 });
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
const log = @import("../log.zig");
|
|
|
|
const Method = enum {
|
|
initialize,
|
|
@"notifications/initialized",
|
|
@"tools/list",
|
|
@"tools/call",
|
|
@"resources/list",
|
|
@"resources/read",
|
|
};
|
|
|
|
const method_map = std.StaticStringMap(Method).initComptime(.{
|
|
.{ "initialize", .initialize },
|
|
.{ "notifications/initialized", .@"notifications/initialized" },
|
|
.{ "tools/list", .@"tools/list" },
|
|
.{ "tools/call", .@"tools/call" },
|
|
.{ "resources/list", .@"resources/list" },
|
|
.{ "resources/read", .@"resources/read" },
|
|
});
|
|
|
|
pub fn handleMessage(server: *Server, arena: std.mem.Allocator, msg: []const u8) !void {
|
|
const req = std.json.parseFromSliceLeaky(protocol.Request, arena, msg, .{
|
|
.ignore_unknown_fields = true,
|
|
}) catch |err| {
|
|
log.warn(.mcp, "JSON Parse Error", .{ .err = err, .msg = msg });
|
|
try server.sendError(.null, .ParseError, "Parse error");
|
|
return;
|
|
};
|
|
|
|
const method = method_map.get(req.method) orelse {
|
|
if (req.id != null) {
|
|
try server.sendError(req.id.?, .MethodNotFound, "Method not found");
|
|
}
|
|
return;
|
|
};
|
|
|
|
switch (method) {
|
|
.initialize => try handleInitialize(server, req),
|
|
.@"notifications/initialized" => {},
|
|
.@"tools/list" => try tools.handleList(server, arena, req),
|
|
.@"tools/call" => try tools.handleCall(server, arena, req),
|
|
.@"resources/list" => try resources.handleList(server, req),
|
|
.@"resources/read" => try resources.handleRead(server, arena, req),
|
|
}
|
|
}
|
|
|
|
fn handleInitialize(server: *Server, req: protocol.Request) !void {
|
|
const result = protocol.InitializeResult{
|
|
.protocolVersion = "2025-11-25",
|
|
.capabilities = .{
|
|
.resources = .{},
|
|
.tools = .{},
|
|
},
|
|
.serverInfo = .{
|
|
.name = "lightpanda",
|
|
.version = "0.1.0",
|
|
},
|
|
};
|
|
|
|
try server.sendResult(req.id.?, result);
|
|
}
|
|
|
|
const testing = @import("../testing.zig");
|
|
|
|
test "MCP.router - handleMessage - synchronous unit tests" {
|
|
defer testing.reset();
|
|
const allocator = testing.allocator;
|
|
const app = testing.test_app;
|
|
|
|
var out_alloc: std.io.Writer.Allocating = .init(testing.arena_allocator);
|
|
defer out_alloc.deinit();
|
|
|
|
var server = try Server.init(allocator, app, &out_alloc.writer);
|
|
defer server.deinit();
|
|
|
|
const aa = testing.arena_allocator;
|
|
|
|
// 1. Valid handshake
|
|
try handleMessage(server, aa,
|
|
\\{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test-client","version":"1.0.0"}}}
|
|
);
|
|
try testing.expectJson(
|
|
\\{ "id": 1, "result": { "capabilities": { "tools": {} } } }
|
|
, out_alloc.writer.buffered());
|
|
out_alloc.writer.end = 0;
|
|
|
|
// 2. Tools list
|
|
try handleMessage(server, aa,
|
|
\\{"jsonrpc":"2.0","id":2,"method":"tools/list"}
|
|
);
|
|
try testing.expectJson(.{ .id = 2 }, out_alloc.writer.buffered());
|
|
try testing.expect(std.mem.indexOf(u8, out_alloc.writer.buffered(), "\"name\":\"goto\"") != null);
|
|
out_alloc.writer.end = 0;
|
|
|
|
// 3. Method not found
|
|
try handleMessage(server, aa,
|
|
\\{"jsonrpc":"2.0","id":3,"method":"unknown_method"}
|
|
);
|
|
try testing.expectJson(.{ .id = 3, .@"error" = .{ .code = -32601 } }, out_alloc.writer.buffered());
|
|
out_alloc.writer.end = 0;
|
|
|
|
// 4. Parse error
|
|
{
|
|
const old_filter = log.opts.filter_scopes;
|
|
log.opts.filter_scopes = &.{.mcp};
|
|
defer log.opts.filter_scopes = old_filter;
|
|
|
|
try handleMessage(server, aa, "invalid json");
|
|
try testing.expectJson("{\"id\": null, \"error\": {\"code\": -32700}}", out_alloc.writer.buffered());
|
|
}
|
|
}
|