mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-28 15:40:04 +00:00
115 lines
3.6 KiB
Zig
115 lines
3.6 KiB
Zig
const std = @import("std");
|
|
|
|
const lp = @import("lightpanda");
|
|
const log = lp.log;
|
|
|
|
const protocol = @import("protocol.zig");
|
|
const Server = @import("Server.zig");
|
|
|
|
pub const resource_list = [_]protocol.Resource{
|
|
.{
|
|
.uri = "mcp://page/html",
|
|
.name = "Page HTML",
|
|
.description = "The serialized HTML DOM of the current page",
|
|
.mimeType = "text/html",
|
|
},
|
|
.{
|
|
.uri = "mcp://page/markdown",
|
|
.name = "Page Markdown",
|
|
.description = "The token-efficient markdown representation of the current page",
|
|
.mimeType = "text/markdown",
|
|
},
|
|
};
|
|
|
|
pub fn handleList(server: *Server, req: protocol.Request) !void {
|
|
const id = req.id orelse return;
|
|
try server.sendResult(id, .{ .resources = &resource_list });
|
|
}
|
|
|
|
const ReadParams = struct {
|
|
uri: []const u8,
|
|
};
|
|
const Format = enum { html, markdown };
|
|
|
|
const ResourceStreamingResult = struct {
|
|
contents: []const struct {
|
|
uri: []const u8,
|
|
mimeType: []const u8,
|
|
text: StreamingText,
|
|
},
|
|
|
|
const StreamingText = struct {
|
|
page: *lp.Page,
|
|
format: Format,
|
|
|
|
pub fn jsonStringify(self: @This(), jw: *std.json.Stringify) !void {
|
|
try jw.beginWriteRaw();
|
|
try jw.writer.writeByte('"');
|
|
var escaped = protocol.JsonEscapingWriter.init(jw.writer);
|
|
switch (self.format) {
|
|
.html => lp.dump.root(self.page.document, .{}, &escaped.writer, self.page) catch |err| {
|
|
log.err(.mcp, "html dump failed", .{ .err = err });
|
|
return error.WriteFailed;
|
|
},
|
|
.markdown => lp.markdown.dump(self.page.document.asNode(), .{}, &escaped.writer, self.page) catch |err| {
|
|
log.err(.mcp, "markdown dump failed", .{ .err = err });
|
|
return error.WriteFailed;
|
|
},
|
|
}
|
|
try jw.writer.writeByte('"');
|
|
jw.endWriteRaw();
|
|
}
|
|
};
|
|
};
|
|
|
|
const ResourceUri = enum {
|
|
@"mcp://page/html",
|
|
@"mcp://page/markdown",
|
|
};
|
|
|
|
const resource_map = std.StaticStringMap(ResourceUri).initComptime(.{
|
|
.{ "mcp://page/html", .@"mcp://page/html" },
|
|
.{ "mcp://page/markdown", .@"mcp://page/markdown" },
|
|
});
|
|
|
|
pub fn handleRead(server: *Server, arena: std.mem.Allocator, req: protocol.Request) !void {
|
|
if (req.params == null or req.id == null) {
|
|
return server.sendError(req.id orelse .{ .integer = -1 }, .InvalidParams, "Missing params");
|
|
}
|
|
const req_id = req.id.?;
|
|
|
|
const params = std.json.parseFromValueLeaky(ReadParams, arena, req.params.?, .{ .ignore_unknown_fields = true }) catch {
|
|
return server.sendError(req_id, .InvalidParams, "Invalid params");
|
|
};
|
|
|
|
const uri = resource_map.get(params.uri) orelse {
|
|
return server.sendError(req_id, .InvalidRequest, "Resource not found");
|
|
};
|
|
|
|
const page = server.session.currentPage() orelse {
|
|
return server.sendError(req_id, .PageNotLoaded, "Page not loaded");
|
|
};
|
|
|
|
const format: Format = switch (uri) {
|
|
.@"mcp://page/html" => .html,
|
|
.@"mcp://page/markdown" => .markdown,
|
|
};
|
|
const mime_type: []const u8 = switch (uri) {
|
|
.@"mcp://page/html" => "text/html",
|
|
.@"mcp://page/markdown" => "text/markdown",
|
|
};
|
|
|
|
const result: ResourceStreamingResult = .{
|
|
.contents = &.{.{
|
|
.uri = params.uri,
|
|
.mimeType = mime_type,
|
|
.text = .{ .page = page, .format = format },
|
|
}},
|
|
};
|
|
server.sendResult(req_id, result) catch {
|
|
return server.sendError(req_id, .InternalError, "Failed to serialize resource content");
|
|
};
|
|
}
|
|
|
|
const testing = @import("../testing.zig");
|