diff --git a/src/lightpanda.zig b/src/lightpanda.zig index b381af16..886e87ba 100644 --- a/src/lightpanda.zig +++ b/src/lightpanda.zig @@ -132,5 +132,5 @@ noinline fn assertionFailure(comptime ctx: []const u8, args: anytype) noreturn { test { std.testing.refAllDecls(@This()); - _ = @import("mcp/tests.zig"); + std.testing.refAllDecls(mcp); } diff --git a/src/mcp/Server.zig b/src/mcp/Server.zig index 8ddbdaca..f3fa026a 100644 --- a/src/mcp/Server.zig +++ b/src/mcp/Server.zig @@ -1,7 +1,8 @@ const std = @import("std"); -const App = @import("../App.zig"); -const protocol = @import("protocol.zig"); + const lp = @import("lightpanda"); + +const App = @import("../App.zig"); const HttpClient = @import("../http/Client.zig"); pub const McpServer = struct { diff --git a/src/mcp/protocol.zig b/src/mcp/protocol.zig index 88e281e3..c195560f 100644 --- a/src/mcp/protocol.zig +++ b/src/mcp/protocol.zig @@ -95,3 +95,70 @@ pub const Resource = struct { description: ?[]const u8 = null, mimeType: ?[]const u8 = null, }; + +const testing = @import("../testing.zig"); + +test "protocol request parsing" { + const raw_json = + \\{ + \\ "jsonrpc": "2.0", + \\ "id": 1, + \\ "method": "initialize", + \\ "params": { + \\ "protocolVersion": "2024-11-05", + \\ "capabilities": {}, + \\ "clientInfo": { + \\ "name": "test-client", + \\ "version": "1.0.0" + \\ } + \\ } + \\} + ; + + const parsed = try std.json.parseFromSlice(Request, testing.allocator, raw_json, .{ .ignore_unknown_fields = true }); + defer parsed.deinit(); + + const req = parsed.value; + try testing.expectString("2.0", req.jsonrpc); + try testing.expectString("initialize", req.method); + try testing.expect(req.id.? == .integer); + try testing.expectEqual(@as(i64, 1), req.id.?.integer); + try testing.expect(req.params != null); + + // Test nested parsing of InitializeParams + const init_params = try std.json.parseFromValue(InitializeParams, testing.allocator, req.params.?, .{ .ignore_unknown_fields = true }); + defer init_params.deinit(); + + try testing.expectString("2024-11-05", init_params.value.protocolVersion); + try testing.expectString("test-client", init_params.value.clientInfo.name); + try testing.expectString("1.0.0", init_params.value.clientInfo.version); +} + +test "protocol response formatting" { + const response = Response{ + .id = .{ .integer = 42 }, + .result = .{ .string = "success" }, + }; + + var aw: std.Io.Writer.Allocating = .init(testing.allocator); + defer aw.deinit(); + try std.json.Stringify.value(response, .{ .emit_null_optional_fields = false }, &aw.writer); + + try testing.expectString("{\"jsonrpc\":\"2.0\",\"id\":42,\"result\":\"success\"}", aw.written()); +} + +test "protocol error formatting" { + const response = Response{ + .id = .{ .string = "abc" }, + .@"error" = .{ + .code = -32601, + .message = "Method not found", + }, + }; + + var aw: std.Io.Writer.Allocating = .init(testing.allocator); + defer aw.deinit(); + try std.json.Stringify.value(response, .{ .emit_null_optional_fields = false }, &aw.writer); + + try testing.expectString("{\"jsonrpc\":\"2.0\",\"id\":\"abc\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"}}", aw.written()); +} diff --git a/src/mcp/protocol_tests.zig b/src/mcp/protocol_tests.zig deleted file mode 100644 index 0a5fca0a..00000000 --- a/src/mcp/protocol_tests.zig +++ /dev/null @@ -1,68 +0,0 @@ -const std = @import("std"); -const testing = std.testing; -const protocol = @import("protocol.zig"); - -test "protocol request parsing" { - const raw_json = - \\{ - \\ "jsonrpc": "2.0", - \\ "id": 1, - \\ "method": "initialize", - \\ "params": { - \\ "protocolVersion": "2024-11-05", - \\ "capabilities": {}, - \\ "clientInfo": { - \\ "name": "test-client", - \\ "version": "1.0.0" - \\ } - \\ } - \\} - ; - - const parsed = try std.json.parseFromSlice(protocol.Request, testing.allocator, raw_json, .{ .ignore_unknown_fields = true }); - defer parsed.deinit(); - - const req = parsed.value; - try testing.expectEqualStrings("2.0", req.jsonrpc); - try testing.expectEqualStrings("initialize", req.method); - try testing.expect(req.id.? == .integer); - try testing.expectEqual(@as(i64, 1), req.id.?.integer); - try testing.expect(req.params != null); - - // Test nested parsing of InitializeParams - const init_params = try std.json.parseFromValue(protocol.InitializeParams, testing.allocator, req.params.?, .{ .ignore_unknown_fields = true }); - defer init_params.deinit(); - - try testing.expectEqualStrings("2024-11-05", init_params.value.protocolVersion); - try testing.expectEqualStrings("test-client", init_params.value.clientInfo.name); - try testing.expectEqualStrings("1.0.0", init_params.value.clientInfo.version); -} - -test "protocol response formatting" { - const response = protocol.Response{ - .id = .{ .integer = 42 }, - .result = .{ .string = "success" }, - }; - - var aw = std.Io.Writer.Allocating.init(testing.allocator); - defer aw.deinit(); - try std.json.Stringify.value(response, .{ .emit_null_optional_fields = false }, &aw.writer); - - try testing.expectEqualStrings("{\"jsonrpc\":\"2.0\",\"id\":42,\"result\":\"success\"}", aw.written()); -} - -test "protocol error formatting" { - const response = protocol.Response{ - .id = .{ .string = "abc" }, - .@"error" = .{ - .code = -32601, - .message = "Method not found", - }, - }; - - var aw = std.Io.Writer.Allocating.init(testing.allocator); - defer aw.deinit(); - try std.json.Stringify.value(response, .{ .emit_null_optional_fields = false }, &aw.writer); - - try testing.expectEqualStrings("{\"jsonrpc\":\"2.0\",\"id\":\"abc\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"}}", aw.written()); -} diff --git a/src/mcp/router.zig b/src/mcp/router.zig index afe496b1..2e901374 100644 --- a/src/mcp/router.zig +++ b/src/mcp/router.zig @@ -1,10 +1,12 @@ const std = @import("std"); + const lp = @import("lightpanda"); +const log = lp.log; + const McpServer = @import("Server.zig").McpServer; const protocol = @import("protocol.zig"); const resources = @import("resources.zig"); const tools = @import("tools.zig"); -const log = lp.log; pub fn processRequests(server: *McpServer) void { while (server.is_running.load(.seq_cst)) { diff --git a/src/mcp/router_tests.zig b/src/mcp/router_tests.zig deleted file mode 100644 index 039dbb9f..00000000 --- a/src/mcp/router_tests.zig +++ /dev/null @@ -1,10 +0,0 @@ -const std = @import("std"); -const testing = std.testing; -const lp = @import("lightpanda"); -const McpServer = lp.mcp.Server; -const router = lp.mcp.router; -const protocol = lp.mcp.protocol; - -test "tools/list includes all gomcp tools" { - try testing.expect(true); -} diff --git a/src/mcp/tests.zig b/src/mcp/tests.zig deleted file mode 100644 index f90c766d..00000000 --- a/src/mcp/tests.zig +++ /dev/null @@ -1,8 +0,0 @@ -const std = @import("std"); - -pub const protocol_tests = @import("protocol_tests.zig"); -pub const router_tests = @import("router_tests.zig"); - -test { - std.testing.refAllDecls(@This()); -} diff --git a/src/mcp/tools.zig b/src/mcp/tools.zig index e4a314de..ec4f6be2 100644 --- a/src/mcp/tools.zig +++ b/src/mcp/tools.zig @@ -1,14 +1,14 @@ const std = @import("std"); -const McpServer = @import("Server.zig").McpServer; -const protocol = @import("protocol.zig"); + const lp = @import("lightpanda"); const log = lp.log; const js = lp.js; -const Node = @import("../browser/webapi/Node.zig"); const Element = @import("../browser/webapi/Element.zig"); const Selector = @import("../browser/webapi/selector/Selector.zig"); const String = @import("../string.zig").String; +const McpServer = @import("Server.zig").McpServer; +const protocol = @import("protocol.zig"); pub fn handleList(server: *McpServer, arena: std.mem.Allocator, req: protocol.Request) !void { const tools = [_]protocol.Tool{