mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
mcp: promot Server.zig to file struct
This commit is contained in:
@@ -29,11 +29,7 @@ pub const log = @import("log.zig");
|
|||||||
pub const js = @import("browser/js/js.zig");
|
pub const js = @import("browser/js/js.zig");
|
||||||
pub const dump = @import("browser/dump.zig");
|
pub const dump = @import("browser/dump.zig");
|
||||||
pub const markdown = @import("browser/markdown.zig");
|
pub const markdown = @import("browser/markdown.zig");
|
||||||
pub const mcp = struct {
|
pub const mcp = @import("mcp.zig");
|
||||||
pub const Server = @import("mcp/Server.zig").McpServer;
|
|
||||||
pub const protocol = @import("mcp/protocol.zig");
|
|
||||||
pub const router = @import("mcp/router.zig");
|
|
||||||
};
|
|
||||||
pub const build_config = @import("build_config");
|
pub const build_config = @import("build_config");
|
||||||
pub const crash_handler = @import("crash_handler.zig");
|
pub const crash_handler = @import("crash_handler.zig");
|
||||||
|
|
||||||
|
|||||||
3
src/mcp.zig
Normal file
3
src/mcp.zig
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub const Server = @import("mcp/Server.zig");
|
||||||
|
pub const protocol = @import("mcp/protocol.zig");
|
||||||
|
pub const router = @import("mcp/router.zig");
|
||||||
@@ -4,137 +4,134 @@ const lp = @import("lightpanda");
|
|||||||
|
|
||||||
const App = @import("../App.zig");
|
const App = @import("../App.zig");
|
||||||
const HttpClient = @import("../http/Client.zig");
|
const HttpClient = @import("../http/Client.zig");
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
pub const McpServer = struct {
|
allocator: std.mem.Allocator,
|
||||||
allocator: std.mem.Allocator,
|
app: *App,
|
||||||
app: *App,
|
|
||||||
|
|
||||||
http_client: *HttpClient,
|
http_client: *HttpClient,
|
||||||
notification: *lp.Notification,
|
notification: *lp.Notification,
|
||||||
browser: *lp.Browser,
|
browser: *lp.Browser,
|
||||||
session: *lp.Session,
|
session: *lp.Session,
|
||||||
page: *lp.Page,
|
page: *lp.Page,
|
||||||
|
|
||||||
io_thread: ?std.Thread = null,
|
io_thread: ?std.Thread = null,
|
||||||
queue_mutex: std.Thread.Mutex = .{},
|
queue_mutex: std.Thread.Mutex = .{},
|
||||||
queue_condition: std.Thread.Condition = .{},
|
queue_condition: std.Thread.Condition = .{},
|
||||||
message_queue: std.ArrayListUnmanaged([]const u8) = .empty,
|
message_queue: std.ArrayListUnmanaged([]const u8) = .empty,
|
||||||
|
|
||||||
is_running: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
|
is_running: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
|
||||||
|
|
||||||
stdout_mutex: std.Thread.Mutex = .{},
|
stdout_mutex: std.Thread.Mutex = .{},
|
||||||
|
|
||||||
const Self = @This();
|
pub fn init(allocator: std.mem.Allocator, app: *App) !*Self {
|
||||||
|
const self = try allocator.create(Self);
|
||||||
|
errdefer allocator.destroy(self);
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, app: *App) !*Self {
|
self.allocator = allocator;
|
||||||
const self = try allocator.create(Self);
|
self.app = app;
|
||||||
errdefer allocator.destroy(self);
|
self.message_queue = .empty;
|
||||||
|
|
||||||
self.allocator = allocator;
|
self.http_client = try app.http.createClient(allocator);
|
||||||
self.app = app;
|
errdefer self.http_client.deinit();
|
||||||
self.message_queue = .empty;
|
|
||||||
|
|
||||||
self.http_client = try app.http.createClient(allocator);
|
self.notification = try lp.Notification.init(allocator);
|
||||||
errdefer self.http_client.deinit();
|
errdefer self.notification.deinit();
|
||||||
|
|
||||||
self.notification = try lp.Notification.init(allocator);
|
self.browser = try allocator.create(lp.Browser);
|
||||||
errdefer self.notification.deinit();
|
errdefer allocator.destroy(self.browser);
|
||||||
|
self.browser.* = try lp.Browser.init(app, .{ .http_client = self.http_client });
|
||||||
|
errdefer self.browser.deinit();
|
||||||
|
|
||||||
self.browser = try allocator.create(lp.Browser);
|
self.session = try self.browser.newSession(self.notification);
|
||||||
errdefer allocator.destroy(self.browser);
|
self.page = try self.session.createPage();
|
||||||
self.browser.* = try lp.Browser.init(app, .{ .http_client = self.http_client });
|
|
||||||
errdefer self.browser.deinit();
|
|
||||||
|
|
||||||
self.session = try self.browser.newSession(self.notification);
|
return self;
|
||||||
self.page = try self.session.createPage();
|
}
|
||||||
|
|
||||||
return self;
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.stop();
|
||||||
|
if (self.io_thread) |*thread| {
|
||||||
|
thread.join();
|
||||||
}
|
}
|
||||||
|
for (self.message_queue.items) |msg| {
|
||||||
pub fn deinit(self: *Self) void {
|
self.allocator.free(msg);
|
||||||
self.stop();
|
|
||||||
if (self.io_thread) |*thread| {
|
|
||||||
thread.join();
|
|
||||||
}
|
|
||||||
for (self.message_queue.items) |msg| {
|
|
||||||
self.allocator.free(msg);
|
|
||||||
}
|
|
||||||
self.message_queue.deinit(self.allocator);
|
|
||||||
|
|
||||||
self.browser.deinit();
|
|
||||||
self.allocator.destroy(self.browser);
|
|
||||||
self.notification.deinit();
|
|
||||||
self.http_client.deinit();
|
|
||||||
|
|
||||||
self.allocator.destroy(self);
|
|
||||||
}
|
}
|
||||||
|
self.message_queue.deinit(self.allocator);
|
||||||
|
|
||||||
pub fn start(self: *Self) !void {
|
self.browser.deinit();
|
||||||
self.is_running.store(true, .seq_cst);
|
self.allocator.destroy(self.browser);
|
||||||
self.io_thread = try std.Thread.spawn(.{}, ioWorker, .{self});
|
self.notification.deinit();
|
||||||
}
|
self.http_client.deinit();
|
||||||
|
|
||||||
pub fn stop(self: *Self) void {
|
self.allocator.destroy(self);
|
||||||
self.is_running.store(false, .seq_cst);
|
}
|
||||||
self.queue_mutex.lock();
|
|
||||||
self.queue_condition.signal();
|
|
||||||
self.queue_mutex.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ioWorker(self: *Self) void {
|
pub fn start(self: *Self) !void {
|
||||||
var stdin_file = std.fs.File.stdin();
|
self.is_running.store(true, .seq_cst);
|
||||||
var stdin_buf: [8192]u8 = undefined;
|
self.io_thread = try std.Thread.spawn(.{}, ioWorker, .{self});
|
||||||
var stdin = stdin_file.reader(&stdin_buf);
|
}
|
||||||
|
|
||||||
while (self.is_running.load(.seq_cst)) {
|
pub fn stop(self: *Self) void {
|
||||||
const msg_or_err = stdin.interface.adaptToOldInterface().readUntilDelimiterAlloc(self.allocator, '\n', 1024 * 1024 * 10);
|
self.is_running.store(false, .seq_cst);
|
||||||
if (msg_or_err) |msg| {
|
self.queue_mutex.lock();
|
||||||
if (msg.len == 0) {
|
self.queue_condition.signal();
|
||||||
self.allocator.free(msg);
|
self.queue_mutex.unlock();
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
self.queue_mutex.lock();
|
fn ioWorker(self: *Self) void {
|
||||||
self.message_queue.append(self.allocator, msg) catch |err| {
|
var stdin_file = std.fs.File.stdin();
|
||||||
lp.log.err(.app, "MCP Queue failed", .{ .err = err });
|
var stdin_buf: [8192]u8 = undefined;
|
||||||
self.allocator.free(msg);
|
var stdin = stdin_file.reader(&stdin_buf);
|
||||||
};
|
|
||||||
self.queue_mutex.unlock();
|
while (self.is_running.load(.seq_cst)) {
|
||||||
self.queue_condition.signal();
|
const msg_or_err = stdin.interface.adaptToOldInterface().readUntilDelimiterAlloc(self.allocator, '\n', 1024 * 1024 * 10);
|
||||||
} else |err| {
|
if (msg_or_err) |msg| {
|
||||||
if (err == error.EndOfStream) {
|
if (msg.len == 0) {
|
||||||
self.stop();
|
self.allocator.free(msg);
|
||||||
break;
|
continue;
|
||||||
}
|
|
||||||
lp.log.err(.app, "MCP IO Error", .{ .err = err });
|
|
||||||
std.Thread.sleep(100 * std.time.ns_per_ms);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.queue_mutex.lock();
|
||||||
|
self.message_queue.append(self.allocator, msg) catch |err| {
|
||||||
|
lp.log.err(.app, "MCP Queue failed", .{ .err = err });
|
||||||
|
self.allocator.free(msg);
|
||||||
|
};
|
||||||
|
self.queue_mutex.unlock();
|
||||||
|
self.queue_condition.signal();
|
||||||
|
} else |err| {
|
||||||
|
if (err == error.EndOfStream) {
|
||||||
|
self.stop();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lp.log.err(.app, "MCP IO Error", .{ .err = err });
|
||||||
|
std.Thread.sleep(100 * std.time.ns_per_ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getNextMessage(self: *Self) ?[]const u8 {
|
pub fn getNextMessage(self: *Self) ?[]const u8 {
|
||||||
self.queue_mutex.lock();
|
self.queue_mutex.lock();
|
||||||
defer self.queue_mutex.unlock();
|
defer self.queue_mutex.unlock();
|
||||||
|
|
||||||
while (self.message_queue.items.len == 0 and self.is_running.load(.seq_cst)) {
|
while (self.message_queue.items.len == 0 and self.is_running.load(.seq_cst)) {
|
||||||
self.queue_condition.wait(&self.queue_mutex);
|
self.queue_condition.wait(&self.queue_mutex);
|
||||||
}
|
|
||||||
|
|
||||||
if (self.message_queue.items.len > 0) {
|
|
||||||
return self.message_queue.orderedRemove(0);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendResponse(self: *Self, response: anytype) !void {
|
if (self.message_queue.items.len > 0) {
|
||||||
self.stdout_mutex.lock();
|
return self.message_queue.orderedRemove(0);
|
||||||
defer self.stdout_mutex.unlock();
|
|
||||||
|
|
||||||
var stdout_file = std.fs.File.stdout();
|
|
||||||
var stdout_buf: [8192]u8 = undefined;
|
|
||||||
var stdout = stdout_file.writer(&stdout_buf);
|
|
||||||
try std.json.Stringify.value(response, .{ .emit_null_optional_fields = false }, &stdout.interface);
|
|
||||||
try stdout.interface.writeByte('\n');
|
|
||||||
try stdout.interface.flush();
|
|
||||||
}
|
}
|
||||||
};
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sendResponse(self: *Self, response: anytype) !void {
|
||||||
|
self.stdout_mutex.lock();
|
||||||
|
defer self.stdout_mutex.unlock();
|
||||||
|
|
||||||
|
var stdout_file = std.fs.File.stdout();
|
||||||
|
var stdout_buf: [8192]u8 = undefined;
|
||||||
|
var stdout = stdout_file.writer(&stdout_buf);
|
||||||
|
try std.json.Stringify.value(response, .{ .emit_null_optional_fields = false }, &stdout.interface);
|
||||||
|
try stdout.interface.writeByte('\n');
|
||||||
|
try stdout.interface.flush();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const McpServer = @import("Server.zig").McpServer;
|
|
||||||
const protocol = @import("protocol.zig");
|
|
||||||
const lp = @import("lightpanda");
|
const lp = @import("lightpanda");
|
||||||
|
|
||||||
pub fn handleList(server: *McpServer, req: protocol.Request) !void {
|
const protocol = @import("protocol.zig");
|
||||||
|
const Server = @import("Server.zig");
|
||||||
|
|
||||||
|
pub fn handleList(server: *Server, req: protocol.Request) !void {
|
||||||
const resources = [_]protocol.Resource{
|
const resources = [_]protocol.Resource{
|
||||||
.{
|
.{
|
||||||
.uri = "mcp://page/html",
|
.uri = "mcp://page/html",
|
||||||
@@ -32,7 +34,7 @@ const ReadParams = struct {
|
|||||||
uri: []const u8,
|
uri: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn handleRead(server: *McpServer, 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 sendError(server, req.id.?, -32602, "Missing params");
|
||||||
}
|
}
|
||||||
@@ -78,7 +80,7 @@ pub fn handleRead(server: *McpServer, arena: std.mem.Allocator, req: protocol.Re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendResult(server: *McpServer, id: std.json.Value, result: anytype) !void {
|
pub fn sendResult(server: *Server, id: std.json.Value, result: anytype) !void {
|
||||||
const GenericResponse = struct {
|
const GenericResponse = struct {
|
||||||
jsonrpc: []const u8 = "2.0",
|
jsonrpc: []const u8 = "2.0",
|
||||||
id: std.json.Value,
|
id: std.json.Value,
|
||||||
@@ -90,7 +92,7 @@ pub fn sendResult(server: *McpServer, id: std.json.Value, result: anytype) !void
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendError(server: *McpServer, id: std.json.Value, code: i64, message: []const u8) !void {
|
pub fn sendError(server: *Server, id: std.json.Value, code: i64, message: []const u8) !void {
|
||||||
try server.sendResponse(protocol.Response{
|
try server.sendResponse(protocol.Response{
|
||||||
.id = id,
|
.id = id,
|
||||||
.@"error" = protocol.Error{
|
.@"error" = protocol.Error{
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ const std = @import("std");
|
|||||||
const lp = @import("lightpanda");
|
const lp = @import("lightpanda");
|
||||||
const log = lp.log;
|
const log = lp.log;
|
||||||
|
|
||||||
const McpServer = @import("Server.zig").McpServer;
|
|
||||||
const protocol = @import("protocol.zig");
|
const protocol = @import("protocol.zig");
|
||||||
const resources = @import("resources.zig");
|
const resources = @import("resources.zig");
|
||||||
|
const Server = @import("Server.zig");
|
||||||
const tools = @import("tools.zig");
|
const tools = @import("tools.zig");
|
||||||
|
|
||||||
pub fn processRequests(server: *McpServer) void {
|
pub fn processRequests(server: *Server) void {
|
||||||
while (server.is_running.load(.seq_cst)) {
|
while (server.is_running.load(.seq_cst)) {
|
||||||
if (server.getNextMessage()) |msg| {
|
if (server.getNextMessage()) |msg| {
|
||||||
defer server.allocator.free(msg);
|
defer server.allocator.free(msg);
|
||||||
@@ -25,7 +25,7 @@ pub fn processRequests(server: *McpServer) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleMessage(server: *McpServer, arena: std.mem.Allocator, msg: []const u8) !void {
|
fn handleMessage(server: *Server, arena: std.mem.Allocator, msg: []const u8) !void {
|
||||||
const parsed = std.json.parseFromSliceLeaky(protocol.Request, arena, msg, .{
|
const parsed = std.json.parseFromSliceLeaky(protocol.Request, arena, msg, .{
|
||||||
.ignore_unknown_fields = true,
|
.ignore_unknown_fields = true,
|
||||||
}) catch |err| {
|
}) catch |err| {
|
||||||
@@ -62,7 +62,7 @@ fn handleMessage(server: *McpServer, arena: std.mem.Allocator, msg: []const u8)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sendResponseGeneric(server: *McpServer, id: std.json.Value, result: anytype) !void {
|
fn sendResponseGeneric(server: *Server, id: std.json.Value, result: anytype) !void {
|
||||||
const GenericResponse = struct {
|
const GenericResponse = struct {
|
||||||
jsonrpc: []const u8 = "2.0",
|
jsonrpc: []const u8 = "2.0",
|
||||||
id: std.json.Value,
|
id: std.json.Value,
|
||||||
@@ -74,7 +74,7 @@ fn sendResponseGeneric(server: *McpServer, id: std.json.Value, result: anytype)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleInitialize(server: *McpServer, 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",
|
||||||
.capabilities = .{
|
.capabilities = .{
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ const js = lp.js;
|
|||||||
const Element = @import("../browser/webapi/Element.zig");
|
const Element = @import("../browser/webapi/Element.zig");
|
||||||
const Selector = @import("../browser/webapi/selector/Selector.zig");
|
const Selector = @import("../browser/webapi/selector/Selector.zig");
|
||||||
const String = @import("../string.zig").String;
|
const String = @import("../string.zig").String;
|
||||||
const McpServer = @import("Server.zig").McpServer;
|
|
||||||
const protocol = @import("protocol.zig");
|
const protocol = @import("protocol.zig");
|
||||||
|
const Server = @import("Server.zig");
|
||||||
|
|
||||||
pub fn handleList(server: *McpServer, arena: std.mem.Allocator, req: protocol.Request) !void {
|
pub fn handleList(server: *Server, arena: std.mem.Allocator, req: protocol.Request) !void {
|
||||||
const tools = [_]protocol.Tool{
|
const tools = [_]protocol.Tool{
|
||||||
.{
|
.{
|
||||||
.name = "goto",
|
.name = "goto",
|
||||||
@@ -116,7 +116,7 @@ const OverParams = struct {
|
|||||||
result: []const u8,
|
result: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn handleCall(server: *McpServer, 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 sendError(server, req.id.?, -32602, "Missing params");
|
||||||
}
|
}
|
||||||
@@ -266,7 +266,7 @@ pub fn handleCall(server: *McpServer, arena: std.mem.Allocator, req: protocol.Re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn performGoto(server: *McpServer, arena: std.mem.Allocator, url: []const u8) !void {
|
fn performGoto(server: *Server, arena: std.mem.Allocator, url: []const u8) !void {
|
||||||
const url_z = try arena.dupeZ(u8, url);
|
const url_z = try arena.dupeZ(u8, url);
|
||||||
_ = server.page.navigate(url_z, .{
|
_ = server.page.navigate(url_z, .{
|
||||||
.reason = .address_bar,
|
.reason = .address_bar,
|
||||||
@@ -278,7 +278,7 @@ fn performGoto(server: *McpServer, arena: std.mem.Allocator, url: []const u8) !v
|
|||||||
_ = server.session.wait(5000);
|
_ = server.session.wait(5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendResult(server: *McpServer, id: std.json.Value, result: anytype) !void {
|
pub fn sendResult(server: *Server, id: std.json.Value, result: anytype) !void {
|
||||||
const GenericResponse = struct {
|
const GenericResponse = struct {
|
||||||
jsonrpc: []const u8 = "2.0",
|
jsonrpc: []const u8 = "2.0",
|
||||||
id: std.json.Value,
|
id: std.json.Value,
|
||||||
@@ -290,7 +290,7 @@ pub fn sendResult(server: *McpServer, id: std.json.Value, result: anytype) !void
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendError(server: *McpServer, id: std.json.Value, code: i64, message: []const u8) !void {
|
pub fn sendError(server: *Server, id: std.json.Value, code: i64, message: []const u8) !void {
|
||||||
try server.sendResponse(protocol.Response{
|
try server.sendResponse(protocol.Response{
|
||||||
.id = id,
|
.id = id,
|
||||||
.@"error" = protocol.Error{
|
.@"error" = protocol.Error{
|
||||||
|
|||||||
Reference in New Issue
Block a user