mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
mcp: use declarative static definitions for tools and resources
This commit is contained in:
@@ -16,9 +16,6 @@ browser: *lp.Browser,
|
|||||||
session: *lp.Session,
|
session: *lp.Session,
|
||||||
page: *lp.Page,
|
page: *lp.Page,
|
||||||
|
|
||||||
tools: []const protocol.Tool,
|
|
||||||
resources: []const protocol.Resource,
|
|
||||||
|
|
||||||
is_running: std.atomic.Value(bool) = .init(false),
|
is_running: std.atomic.Value(bool) = .init(false),
|
||||||
|
|
||||||
stdout_mutex: std.Thread.Mutex = .{},
|
stdout_mutex: std.Thread.Mutex = .{},
|
||||||
@@ -44,117 +41,9 @@ pub fn init(allocator: std.mem.Allocator, app: *App) !*Self {
|
|||||||
self.session = try self.browser.newSession(self.notification);
|
self.session = try self.browser.newSession(self.notification);
|
||||||
self.page = try self.session.createPage();
|
self.page = try self.session.createPage();
|
||||||
|
|
||||||
self.tools = try initTools(allocator);
|
|
||||||
self.resources = try initResources(allocator);
|
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initTools(allocator: std.mem.Allocator) ![]const protocol.Tool {
|
|
||||||
const tools = try allocator.alloc(protocol.Tool, 6);
|
|
||||||
errdefer allocator.free(tools);
|
|
||||||
|
|
||||||
tools[0] = .{
|
|
||||||
.name = "goto",
|
|
||||||
.description = "Navigate to a specified URL and load the page in memory so it can be reused later for info extraction.",
|
|
||||||
.inputSchema = try std.json.parseFromSliceLeaky(std.json.Value, allocator,
|
|
||||||
\\{
|
|
||||||
\\ "type": "object",
|
|
||||||
\\ "properties": {
|
|
||||||
\\ "url": { "type": "string", "description": "The URL to navigate to, must be a valid URL." }
|
|
||||||
\\ },
|
|
||||||
\\ "required": ["url"]
|
|
||||||
\\}
|
|
||||||
, .{}),
|
|
||||||
};
|
|
||||||
tools[1] = .{
|
|
||||||
.name = "search",
|
|
||||||
.description = "Use a search engine to look for specific words, terms, sentences. The search page will then be loaded in memory.",
|
|
||||||
.inputSchema = try std.json.parseFromSliceLeaky(std.json.Value, allocator,
|
|
||||||
\\{
|
|
||||||
\\ "type": "object",
|
|
||||||
\\ "properties": {
|
|
||||||
\\ "text": { "type": "string", "description": "The text to search for, must be a valid search query." }
|
|
||||||
\\ },
|
|
||||||
\\ "required": ["text"]
|
|
||||||
\\}
|
|
||||||
, .{}),
|
|
||||||
};
|
|
||||||
tools[2] = .{
|
|
||||||
.name = "markdown",
|
|
||||||
.description = "Get the page content in markdown format. If a url is provided, it navigates to that url first.",
|
|
||||||
.inputSchema = try std.json.parseFromSliceLeaky(std.json.Value, allocator,
|
|
||||||
\\{
|
|
||||||
\\ "type": "object",
|
|
||||||
\\ "properties": {
|
|
||||||
\\ "url": { "type": "string", "description": "Optional URL to navigate to before fetching markdown." }
|
|
||||||
\\ }
|
|
||||||
\\}
|
|
||||||
, .{}),
|
|
||||||
};
|
|
||||||
tools[3] = .{
|
|
||||||
.name = "links",
|
|
||||||
.description = "Extract all links in the opened page. If a url is provided, it navigates to that url first.",
|
|
||||||
.inputSchema = try std.json.parseFromSliceLeaky(std.json.Value, allocator,
|
|
||||||
\\{
|
|
||||||
\\ "type": "object",
|
|
||||||
\\ "properties": {
|
|
||||||
\\ "url": { "type": "string", "description": "Optional URL to navigate to before extracting links." }
|
|
||||||
\\ }
|
|
||||||
\\}
|
|
||||||
, .{}),
|
|
||||||
};
|
|
||||||
tools[4] = .{
|
|
||||||
.name = "evaluate",
|
|
||||||
.description = "Evaluate JavaScript in the current page context. If a url is provided, it navigates to that url first.",
|
|
||||||
.inputSchema = try std.json.parseFromSliceLeaky(std.json.Value, allocator,
|
|
||||||
\\{
|
|
||||||
\\ "type": "object",
|
|
||||||
\\ "properties": {
|
|
||||||
\\ "script": { "type": "string" },
|
|
||||||
\\ "url": { "type": "string", "description": "Optional URL to navigate to before evaluating." }
|
|
||||||
\\ },
|
|
||||||
\\ "required": ["script"]
|
|
||||||
\\}
|
|
||||||
, .{}),
|
|
||||||
};
|
|
||||||
tools[5] = .{
|
|
||||||
.name = "over",
|
|
||||||
.description = "Used to indicate that the task is over and give the final answer if there is any. This is the last tool to be called in a task.",
|
|
||||||
.inputSchema = try std.json.parseFromSliceLeaky(std.json.Value, allocator,
|
|
||||||
\\{
|
|
||||||
\\ "type": "object",
|
|
||||||
\\ "properties": {
|
|
||||||
\\ "result": { "type": "string", "description": "The final result of the task." }
|
|
||||||
\\ },
|
|
||||||
\\ "required": ["result"]
|
|
||||||
\\}
|
|
||||||
, .{}),
|
|
||||||
};
|
|
||||||
|
|
||||||
return tools;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn initResources(allocator: std.mem.Allocator) ![]const protocol.Resource {
|
|
||||||
const resources = try allocator.alloc(protocol.Resource, 2);
|
|
||||||
errdefer allocator.free(resources);
|
|
||||||
|
|
||||||
resources[0] = .{
|
|
||||||
.uri = "mcp://page/html",
|
|
||||||
.name = "Page HTML",
|
|
||||||
.description = "The serialized HTML DOM of the current page",
|
|
||||||
.mimeType = "text/html",
|
|
||||||
};
|
|
||||||
resources[1] = .{
|
|
||||||
.uri = "mcp://page/markdown",
|
|
||||||
.name = "Page Markdown",
|
|
||||||
.description = "The token-efficient markdown representation of the current page",
|
|
||||||
.mimeType = "text/markdown",
|
|
||||||
};
|
|
||||||
|
|
||||||
return resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
self.is_running.store(false, .release);
|
self.is_running.store(false, .release);
|
||||||
|
|
||||||
@@ -163,9 +52,6 @@ pub fn deinit(self: *Self) void {
|
|||||||
self.notification.deinit();
|
self.notification.deinit();
|
||||||
self.http_client.deinit();
|
self.http_client.deinit();
|
||||||
|
|
||||||
self.allocator.free(self.tools);
|
|
||||||
self.allocator.free(self.resources);
|
|
||||||
|
|
||||||
self.allocator.destroy(self);
|
self.allocator.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,17 @@ pub const ToolsCapability = struct {
|
|||||||
pub const Tool = struct {
|
pub const Tool = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
description: ?[]const u8 = null,
|
description: ?[]const u8 = null,
|
||||||
inputSchema: std.json.Value,
|
inputSchema: RawJson,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const RawJson = struct {
|
||||||
|
json: []const u8,
|
||||||
|
|
||||||
|
pub fn jsonStringify(self: @This(), jw: anytype) !void {
|
||||||
|
try jw.beginWriteRaw();
|
||||||
|
try jw.writer.writeAll(self.json);
|
||||||
|
jw.endWriteRaw();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Resource = struct {
|
pub const Resource = struct {
|
||||||
|
|||||||
@@ -6,11 +6,26 @@ const log = lp.log;
|
|||||||
const protocol = @import("protocol.zig");
|
const protocol = @import("protocol.zig");
|
||||||
const Server = @import("Server.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 {
|
pub fn handleList(server: *Server, req: protocol.Request) !void {
|
||||||
const result = struct {
|
const result = struct {
|
||||||
resources: []const protocol.Resource,
|
resources: []const protocol.Resource,
|
||||||
}{
|
}{
|
||||||
.resources = server.resources,
|
.resources = &resource_list,
|
||||||
};
|
};
|
||||||
|
|
||||||
try server.sendResult(req.id.?, result);
|
try server.sendResult(req.id.?, result);
|
||||||
|
|||||||
@@ -9,12 +9,92 @@ const Selector = @import("../browser/webapi/selector/Selector.zig");
|
|||||||
const protocol = @import("protocol.zig");
|
const protocol = @import("protocol.zig");
|
||||||
const Server = @import("Server.zig");
|
const Server = @import("Server.zig");
|
||||||
|
|
||||||
|
pub const tool_list = [_]protocol.Tool{
|
||||||
|
.{
|
||||||
|
.name = "goto",
|
||||||
|
.description = "Navigate to a specified URL and load the page in memory so it can be reused later for info extraction.",
|
||||||
|
.inputSchema = .{ .json =
|
||||||
|
\\{
|
||||||
|
\\ "type": "object",
|
||||||
|
\\ "properties": {
|
||||||
|
\\ "url": { "type": "string", "description": "The URL to navigate to, must be a valid URL." }
|
||||||
|
\\ },
|
||||||
|
\\ "required": ["url"]
|
||||||
|
\\}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.name = "search",
|
||||||
|
.description = "Use a search engine to look for specific words, terms, sentences. The search page will then be loaded in memory.",
|
||||||
|
.inputSchema = .{ .json =
|
||||||
|
\\{
|
||||||
|
\\ "type": "object",
|
||||||
|
\\ "properties": {
|
||||||
|
\\ "text": { "type": "string", "description": "The text to search for, must be a valid search query." }
|
||||||
|
\\ },
|
||||||
|
\\ "required": ["text"]
|
||||||
|
\\}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.name = "markdown",
|
||||||
|
.description = "Get the page content in markdown format. If a url is provided, it navigates to that url first.",
|
||||||
|
.inputSchema = .{ .json =
|
||||||
|
\\{
|
||||||
|
\\ "type": "object",
|
||||||
|
\\ "properties": {
|
||||||
|
\\ "url": { "type": "string", "description": "Optional URL to navigate to before fetching markdown." }
|
||||||
|
\\ }
|
||||||
|
\\}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.name = "links",
|
||||||
|
.description = "Extract all links in the opened page. If a url is provided, it navigates to that url first.",
|
||||||
|
.inputSchema = .{ .json =
|
||||||
|
\\{
|
||||||
|
\\ "type": "object",
|
||||||
|
\\ "properties": {
|
||||||
|
\\ "url": { "type": "string", "description": "Optional URL to navigate to before extracting links." }
|
||||||
|
\\ }
|
||||||
|
\\}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.name = "evaluate",
|
||||||
|
.description = "Evaluate JavaScript in the current page context. If a url is provided, it navigates to that url first.",
|
||||||
|
.inputSchema = .{ .json =
|
||||||
|
\\{
|
||||||
|
\\ "type": "object",
|
||||||
|
\\ "properties": {
|
||||||
|
\\ "script": { "type": "string" },
|
||||||
|
\\ "url": { "type": "string", "description": "Optional URL to navigate to before evaluating." }
|
||||||
|
\\ },
|
||||||
|
\\ "required": ["script"]
|
||||||
|
\\}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.name = "over",
|
||||||
|
.description = "Used to indicate that the task is over and give the final answer if there is any. This is the last tool to be called in a task.",
|
||||||
|
.inputSchema = .{ .json =
|
||||||
|
\\{
|
||||||
|
\\ "type": "object",
|
||||||
|
\\ "properties": {
|
||||||
|
\\ "result": { "type": "string", "description": "The final result of the task." }
|
||||||
|
\\ },
|
||||||
|
\\ "required": ["result"]
|
||||||
|
\\}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
pub fn handleList(server: *Server, arena: std.mem.Allocator, req: protocol.Request) !void {
|
pub fn handleList(server: *Server, arena: std.mem.Allocator, req: protocol.Request) !void {
|
||||||
_ = arena;
|
_ = arena;
|
||||||
const result = struct {
|
const result = struct {
|
||||||
tools: []const protocol.Tool,
|
tools: []const protocol.Tool,
|
||||||
}{
|
}{
|
||||||
.tools = server.tools,
|
.tools = &tool_list,
|
||||||
};
|
};
|
||||||
|
|
||||||
try server.sendResult(req.id.?, result);
|
try server.sendResult(req.id.?, result);
|
||||||
|
|||||||
Reference in New Issue
Block a user