mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 12:44:43 +00:00
mcp: unify error reporting and use named error codes
This commit is contained in:
@@ -180,3 +180,25 @@ pub fn sendResponse(self: *Self, response: anytype) !void {
|
|||||||
try stdout.interface.writeByte('\n');
|
try stdout.interface.writeByte('\n');
|
||||||
try stdout.interface.flush();
|
try stdout.interface.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sendResult(self: *Self, id: std.json.Value, result: anytype) !void {
|
||||||
|
const GenericResponse = struct {
|
||||||
|
jsonrpc: []const u8 = "2.0",
|
||||||
|
id: std.json.Value,
|
||||||
|
result: @TypeOf(result),
|
||||||
|
};
|
||||||
|
try self.sendResponse(GenericResponse{
|
||||||
|
.id = id,
|
||||||
|
.result = result,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sendError(self: *Self, id: std.json.Value, code: protocol.ErrorCode, message: []const u8) !void {
|
||||||
|
try self.sendResponse(protocol.Response{
|
||||||
|
.id = id,
|
||||||
|
.@"error" = protocol.Error{
|
||||||
|
.code = @intFromEnum(code),
|
||||||
|
.message = message,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,14 @@ pub const Error = struct {
|
|||||||
data: ?std.json.Value = null,
|
data: ?std.json.Value = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const ErrorCode = enum(i64) {
|
||||||
|
ParseError = -32700,
|
||||||
|
InvalidRequest = -32600,
|
||||||
|
MethodNotFound = -32601,
|
||||||
|
InvalidParams = -32602,
|
||||||
|
InternalError = -32603,
|
||||||
|
};
|
||||||
|
|
||||||
pub const Notification = struct {
|
pub const Notification = struct {
|
||||||
jsonrpc: []const u8 = "2.0",
|
jsonrpc: []const u8 = "2.0",
|
||||||
method: []const u8,
|
method: []const u8,
|
||||||
@@ -185,7 +193,7 @@ test "protocol error formatting" {
|
|||||||
const response = Response{
|
const response = Response{
|
||||||
.id = .{ .string = "abc" },
|
.id = .{ .string = "abc" },
|
||||||
.@"error" = .{
|
.@"error" = .{
|
||||||
.code = -32601,
|
.code = @intFromEnum(ErrorCode.MethodNotFound),
|
||||||
.message = "Method not found",
|
.message = "Method not found",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ pub fn handleList(server: *Server, req: protocol.Request) !void {
|
|||||||
.resources = server.resources,
|
.resources = server.resources,
|
||||||
};
|
};
|
||||||
|
|
||||||
try sendResult(server, req.id.?, result);
|
try server.sendResult(req.id.?, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReadParams = struct {
|
const ReadParams = struct {
|
||||||
@@ -61,11 +61,11 @@ const ResourceStreamingResult = struct {
|
|||||||
|
|
||||||
pub fn handleRead(server: *Server, arena: std.mem.Allocator, req: protocol.Request) !void {
|
pub fn handleRead(server: *Server, arena: std.mem.Allocator, req: protocol.Request) !void {
|
||||||
if (req.params == null) {
|
if (req.params == null) {
|
||||||
return sendError(server, req.id.?, -32602, "Missing params");
|
return server.sendError(req.id.?, .InvalidParams, "Missing params");
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = std.json.parseFromValueLeaky(ReadParams, arena, req.params.?, .{ .ignore_unknown_fields = true }) catch {
|
const params = std.json.parseFromValueLeaky(ReadParams, arena, req.params.?, .{ .ignore_unknown_fields = true }) catch {
|
||||||
return sendError(server, req.id.?, -32602, "Invalid params");
|
return server.sendError(req.id.?, .InvalidParams, "Invalid params");
|
||||||
};
|
};
|
||||||
|
|
||||||
if (std.mem.eql(u8, params.uri, "mcp://page/html")) {
|
if (std.mem.eql(u8, params.uri, "mcp://page/html")) {
|
||||||
@@ -76,7 +76,7 @@ pub fn handleRead(server: *Server, arena: std.mem.Allocator, req: protocol.Reque
|
|||||||
.text = .{ .server = server, .uri = params.uri, .format = .html },
|
.text = .{ .server = server, .uri = params.uri, .format = .html },
|
||||||
}},
|
}},
|
||||||
};
|
};
|
||||||
try sendResult(server, req.id.?, result);
|
try server.sendResult(req.id.?, result);
|
||||||
} else if (std.mem.eql(u8, params.uri, "mcp://page/markdown")) {
|
} else if (std.mem.eql(u8, params.uri, "mcp://page/markdown")) {
|
||||||
const result: ResourceStreamingResult = .{
|
const result: ResourceStreamingResult = .{
|
||||||
.contents = &.{.{
|
.contents = &.{.{
|
||||||
@@ -85,30 +85,8 @@ pub fn handleRead(server: *Server, arena: std.mem.Allocator, req: protocol.Reque
|
|||||||
.text = .{ .server = server, .uri = params.uri, .format = .markdown },
|
.text = .{ .server = server, .uri = params.uri, .format = .markdown },
|
||||||
}},
|
}},
|
||||||
};
|
};
|
||||||
try sendResult(server, req.id.?, result);
|
try server.sendResult(req.id.?, result);
|
||||||
} else {
|
} else {
|
||||||
return sendError(server, req.id.?, -32602, "Resource not found");
|
return server.sendError(req.id.?, .InvalidRequest, "Resource not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendResult(server: *Server, id: std.json.Value, result: anytype) !void {
|
|
||||||
const GenericResponse = struct {
|
|
||||||
jsonrpc: []const u8 = "2.0",
|
|
||||||
id: std.json.Value,
|
|
||||||
result: @TypeOf(result),
|
|
||||||
};
|
|
||||||
try server.sendResponse(GenericResponse{
|
|
||||||
.id = id,
|
|
||||||
.result = result,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sendError(server: *Server, id: std.json.Value, code: i64, message: []const u8) !void {
|
|
||||||
try server.sendResponse(protocol.Response{
|
|
||||||
.id = id,
|
|
||||||
.@"error" = protocol.Error{
|
|
||||||
.code = code,
|
|
||||||
.message = message,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -64,28 +64,10 @@ fn handleMessage(server: *Server, arena: std.mem.Allocator, msg: []const u8) !vo
|
|||||||
} else if (std.mem.eql(u8, parsed.method, "tools/call")) {
|
} else if (std.mem.eql(u8, parsed.method, "tools/call")) {
|
||||||
try tools.handleCall(server, arena, parsed);
|
try tools.handleCall(server, arena, parsed);
|
||||||
} else {
|
} else {
|
||||||
try server.sendResponse(protocol.Response{
|
try server.sendError(parsed.id.?, .MethodNotFound, "Method not found");
|
||||||
.id = parsed.id.?,
|
|
||||||
.@"error" = protocol.Error{
|
|
||||||
.code = -32601,
|
|
||||||
.message = "Method not found",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sendResponseGeneric(server: *Server, id: std.json.Value, result: anytype) !void {
|
|
||||||
const GenericResponse = struct {
|
|
||||||
jsonrpc: []const u8 = "2.0",
|
|
||||||
id: std.json.Value,
|
|
||||||
result: @TypeOf(result),
|
|
||||||
};
|
|
||||||
try server.sendResponse(GenericResponse{
|
|
||||||
.id = id,
|
|
||||||
.result = result,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handleInitialize(server: *Server, req: protocol.Request) !void {
|
fn handleInitialize(server: *Server, req: protocol.Request) !void {
|
||||||
const result = protocol.InitializeResult{
|
const result = protocol.InitializeResult{
|
||||||
.protocolVersion = "2024-11-05",
|
.protocolVersion = "2024-11-05",
|
||||||
@@ -100,5 +82,5 @@ fn handleInitialize(server: *Server, req: protocol.Request) !void {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
try sendResponseGeneric(server, req.id.?, result);
|
try server.sendResult(req.id.?, result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub fn handleList(server: *Server, arena: std.mem.Allocator, req: protocol.Reque
|
|||||||
.tools = server.tools,
|
.tools = server.tools,
|
||||||
};
|
};
|
||||||
|
|
||||||
try sendResult(server, req.id.?, result);
|
try server.sendResult(req.id.?, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
const GotoParams = struct {
|
const GotoParams = struct {
|
||||||
@@ -79,7 +79,7 @@ const ToolStreamingText = struct {
|
|||||||
|
|
||||||
pub fn handleCall(server: *Server, arena: std.mem.Allocator, req: protocol.Request) !void {
|
pub fn handleCall(server: *Server, arena: std.mem.Allocator, req: protocol.Request) !void {
|
||||||
if (req.params == null) {
|
if (req.params == null) {
|
||||||
return sendError(server, req.id.?, -32602, "Missing params");
|
return server.sendError(req.id.?, .InvalidParams, "Missing params");
|
||||||
}
|
}
|
||||||
|
|
||||||
const CallParams = struct {
|
const CallParams = struct {
|
||||||
@@ -91,7 +91,7 @@ pub fn handleCall(server: *Server, arena: std.mem.Allocator, req: protocol.Reque
|
|||||||
var aw: std.Io.Writer.Allocating = .init(arena);
|
var aw: std.Io.Writer.Allocating = .init(arena);
|
||||||
std.json.Stringify.value(req.params.?, .{}, &aw.writer) catch {};
|
std.json.Stringify.value(req.params.?, .{}, &aw.writer) catch {};
|
||||||
const msg = std.fmt.allocPrint(arena, "Invalid params: {s}", .{aw.written()}) catch "Invalid params";
|
const msg = std.fmt.allocPrint(arena, "Invalid params: {s}", .{aw.written()}) catch "Invalid params";
|
||||||
return sendError(server, req.id.?, -32602, msg);
|
return server.sendError(req.id.?, .InvalidParams, msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (std.mem.eql(u8, call_params.name, "goto") or std.mem.eql(u8, call_params.name, "navigate")) {
|
if (std.mem.eql(u8, call_params.name, "goto") or std.mem.eql(u8, call_params.name, "navigate")) {
|
||||||
@@ -107,7 +107,7 @@ pub fn handleCall(server: *Server, arena: std.mem.Allocator, req: protocol.Reque
|
|||||||
} else if (std.mem.eql(u8, call_params.name, "over")) {
|
} else if (std.mem.eql(u8, call_params.name, "over")) {
|
||||||
try handleOver(server, arena, req.id.?, call_params.arguments);
|
try handleOver(server, arena, req.id.?, call_params.arguments);
|
||||||
} else {
|
} else {
|
||||||
return sendError(server, req.id.?, -32601, "Tool not found");
|
return server.sendError(req.id.?, .MethodNotFound, "Tool not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ fn handleGoto(server: *Server, arena: std.mem.Allocator, id: std.json.Value, arg
|
|||||||
try performGoto(server, args.url, id);
|
try performGoto(server, args.url, id);
|
||||||
|
|
||||||
const content = [_]struct { type: []const u8, text: []const u8 }{.{ .type = "text", .text = "Navigated successfully." }};
|
const content = [_]struct { type: []const u8, text: []const u8 }{.{ .type = "text", .text = "Navigated successfully." }};
|
||||||
try sendResult(server, id, .{ .content = &content });
|
try server.sendResult(id, .{ .content = &content });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleSearch(server: *Server, arena: std.mem.Allocator, id: std.json.Value, arguments: ?std.json.Value) !void {
|
fn handleSearch(server: *Server, arena: std.mem.Allocator, id: std.json.Value, arguments: ?std.json.Value) !void {
|
||||||
@@ -125,16 +125,16 @@ fn handleSearch(server: *Server, arena: std.mem.Allocator, id: std.json.Value, a
|
|||||||
const component: std.Uri.Component = .{ .raw = args.text };
|
const component: std.Uri.Component = .{ .raw = args.text };
|
||||||
var url_aw = std.Io.Writer.Allocating.init(arena);
|
var url_aw = std.Io.Writer.Allocating.init(arena);
|
||||||
component.formatQuery(&url_aw.writer) catch {
|
component.formatQuery(&url_aw.writer) catch {
|
||||||
return sendError(server, id, -32603, "Internal error formatting query");
|
return server.sendError(id, .InternalError, "Internal error formatting query");
|
||||||
};
|
};
|
||||||
const url = std.fmt.allocPrintSentinel(arena, "https://duckduckgo.com/?q={s}", .{url_aw.written()}, 0) catch {
|
const url = std.fmt.allocPrintSentinel(arena, "https://duckduckgo.com/?q={s}", .{url_aw.written()}, 0) catch {
|
||||||
return sendError(server, id, -32603, "Internal error formatting URL");
|
return server.sendError(id, .InternalError, "Internal error formatting URL");
|
||||||
};
|
};
|
||||||
|
|
||||||
try performGoto(server, url, id);
|
try performGoto(server, url, id);
|
||||||
|
|
||||||
const content = [_]struct { type: []const u8, text: []const u8 }{.{ .type = "text", .text = "Search performed successfully." }};
|
const content = [_]struct { type: []const u8, text: []const u8 }{.{ .type = "text", .text = "Search performed successfully." }};
|
||||||
try sendResult(server, id, .{ .content = &content });
|
try server.sendResult(id, .{ .content = &content });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleMarkdown(server: *Server, arena: std.mem.Allocator, id: std.json.Value, arguments: ?std.json.Value) !void {
|
fn handleMarkdown(server: *Server, arena: std.mem.Allocator, id: std.json.Value, arguments: ?std.json.Value) !void {
|
||||||
@@ -155,7 +155,7 @@ fn handleMarkdown(server: *Server, arena: std.mem.Allocator, id: std.json.Value,
|
|||||||
.text = .{ .server = server, .action = .markdown },
|
.text = .{ .server = server, .action = .markdown },
|
||||||
}},
|
}},
|
||||||
};
|
};
|
||||||
try sendResult(server, id, result);
|
try server.sendResult(id, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleLinks(server: *Server, arena: std.mem.Allocator, id: std.json.Value, arguments: ?std.json.Value) !void {
|
fn handleLinks(server: *Server, arena: std.mem.Allocator, id: std.json.Value, arguments: ?std.json.Value) !void {
|
||||||
@@ -176,7 +176,7 @@ fn handleLinks(server: *Server, arena: std.mem.Allocator, id: std.json.Value, ar
|
|||||||
.text = .{ .server = server, .action = .links },
|
.text = .{ .server = server, .action = .links },
|
||||||
}},
|
}},
|
||||||
};
|
};
|
||||||
try sendResult(server, id, result);
|
try server.sendResult(id, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleEvaluate(server: *Server, arena: std.mem.Allocator, id: std.json.Value, arguments: ?std.json.Value) !void {
|
fn handleEvaluate(server: *Server, arena: std.mem.Allocator, id: std.json.Value, arguments: ?std.json.Value) !void {
|
||||||
@@ -192,37 +192,33 @@ fn handleEvaluate(server: *Server, arena: std.mem.Allocator, id: std.json.Value,
|
|||||||
|
|
||||||
const js_result = ls.local.compileAndRun(args.script, null) catch {
|
const js_result = ls.local.compileAndRun(args.script, null) catch {
|
||||||
const content = [_]struct { type: []const u8, text: []const u8 }{.{ .type = "text", .text = "Script evaluation failed." }};
|
const content = [_]struct { type: []const u8, text: []const u8 }{.{ .type = "text", .text = "Script evaluation failed." }};
|
||||||
return sendResult(server, id, .{ .content = &content, .isError = true });
|
return server.sendResult(id, .{ .content = &content, .isError = true });
|
||||||
};
|
};
|
||||||
|
|
||||||
const str_result = js_result.toStringSliceWithAlloc(arena) catch "undefined";
|
const str_result = js_result.toStringSliceWithAlloc(arena) catch "undefined";
|
||||||
|
|
||||||
const content = [_]struct { type: []const u8, text: []const u8 }{.{ .type = "text", .text = str_result }};
|
const content = [_]struct { type: []const u8, text: []const u8 }{.{ .type = "text", .text = str_result }};
|
||||||
try sendResult(server, id, .{ .content = &content });
|
try server.sendResult(id, .{ .content = &content });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleOver(server: *Server, arena: std.mem.Allocator, id: std.json.Value, arguments: ?std.json.Value) !void {
|
fn handleOver(server: *Server, arena: std.mem.Allocator, id: std.json.Value, arguments: ?std.json.Value) !void {
|
||||||
const args = try parseParams(OverParams, arena, arguments, server, id, "over");
|
const args = try parseParams(OverParams, arena, arguments, server, id, "over");
|
||||||
|
|
||||||
const content = [_]struct { type: []const u8, text: []const u8 }{.{ .type = "text", .text = args.result }};
|
const content = [_]struct { type: []const u8, text: []const u8 }{.{ .type = "text", .text = args.result }};
|
||||||
try sendResult(server, id, .{ .content = &content });
|
try server.sendResult(id, .{ .content = &content });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseParams(comptime T: type, arena: std.mem.Allocator, arguments: ?std.json.Value, server: *Server, id: std.json.Value, tool_name: []const u8) !T {
|
fn parseParams(comptime T: type, arena: std.mem.Allocator, arguments: ?std.json.Value, server: *Server, id: std.json.Value, tool_name: []const u8) !T {
|
||||||
if (arguments == null) {
|
if (arguments == null) {
|
||||||
// We need to print the error message, so we need an allocator.
|
|
||||||
// But we are in a helper, we should probably just return the error.
|
|
||||||
// However, sendError sends the response.
|
|
||||||
// Let's use a fixed buffer for the error message to avoid complex error handling.
|
|
||||||
var buf: [64]u8 = undefined;
|
var buf: [64]u8 = undefined;
|
||||||
const msg = std.fmt.bufPrint(&buf, "Missing arguments for {s}", .{tool_name}) catch "Missing arguments";
|
const msg = std.fmt.bufPrint(&buf, "Missing arguments for {s}", .{tool_name}) catch "Missing arguments";
|
||||||
try sendError(server, id, -32602, msg);
|
try server.sendError(id, .InvalidParams, msg);
|
||||||
return error.InvalidParams;
|
return error.InvalidParams;
|
||||||
}
|
}
|
||||||
return std.json.parseFromValueLeaky(T, arena, arguments.?, .{ .ignore_unknown_fields = true }) catch {
|
return std.json.parseFromValueLeaky(T, arena, arguments.?, .{ .ignore_unknown_fields = true }) catch {
|
||||||
var buf: [64]u8 = undefined;
|
var buf: [64]u8 = undefined;
|
||||||
const msg = std.fmt.bufPrint(&buf, "Invalid arguments for {s}", .{tool_name}) catch "Invalid arguments";
|
const msg = std.fmt.bufPrint(&buf, "Invalid arguments for {s}", .{tool_name}) catch "Invalid arguments";
|
||||||
try sendError(server, id, -32602, msg);
|
try server.sendError(id, .InvalidParams, msg);
|
||||||
return error.InvalidParams;
|
return error.InvalidParams;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -241,31 +237,9 @@ fn performGoto(server: *Server, url: [:0]const u8, id: std.json.Value) !void {
|
|||||||
.reason = .address_bar,
|
.reason = .address_bar,
|
||||||
.kind = .{ .push = null },
|
.kind = .{ .push = null },
|
||||||
}) catch {
|
}) catch {
|
||||||
try sendError(server, id, -32603, "Internal error during navigation");
|
try server.sendError(id, .InternalError, "Internal error during navigation");
|
||||||
return error.NavigationFailed;
|
return error.NavigationFailed;
|
||||||
};
|
};
|
||||||
|
|
||||||
_ = server.session.wait(5000);
|
_ = server.session.wait(5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendResult(server: *Server, id: std.json.Value, result: anytype) !void {
|
|
||||||
const GenericResponse = struct {
|
|
||||||
jsonrpc: []const u8 = "2.0",
|
|
||||||
id: std.json.Value,
|
|
||||||
result: @TypeOf(result),
|
|
||||||
};
|
|
||||||
try server.sendResponse(GenericResponse{
|
|
||||||
.id = id,
|
|
||||||
.result = result,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sendError(server: *Server, id: std.json.Value, code: i64, message: []const u8) !void {
|
|
||||||
try server.sendResponse(protocol.Response{
|
|
||||||
.id = id,
|
|
||||||
.@"error" = protocol.Error{
|
|
||||||
.code = code,
|
|
||||||
.message = message,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user