mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
mcp: support notifications and improve error handling
Make Request id optional for JSON-RPC notifications and handle the initialized event. Improve thread safety, logging, and error paths.
This commit is contained in:
@@ -75,7 +75,9 @@ pub const McpServer = struct {
|
|||||||
|
|
||||||
pub fn stop(self: *Self) void {
|
pub fn stop(self: *Self) void {
|
||||||
self.is_running.store(false, .seq_cst);
|
self.is_running.store(false, .seq_cst);
|
||||||
|
self.queue_mutex.lock();
|
||||||
self.queue_condition.signal();
|
self.queue_condition.signal();
|
||||||
|
self.queue_mutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ioWorker(self: *Self) void {
|
fn ioWorker(self: *Self) void {
|
||||||
@@ -93,7 +95,7 @@ pub const McpServer = struct {
|
|||||||
|
|
||||||
self.queue_mutex.lock();
|
self.queue_mutex.lock();
|
||||||
self.message_queue.append(self.allocator, msg) catch |err| {
|
self.message_queue.append(self.allocator, msg) catch |err| {
|
||||||
std.debug.print("MCP Error: Failed to queue message: {}\n", .{err});
|
lp.log.err(.app, "MCP Error: Failed to queue message", .{ .err = err });
|
||||||
self.allocator.free(msg);
|
self.allocator.free(msg);
|
||||||
};
|
};
|
||||||
self.queue_mutex.unlock();
|
self.queue_mutex.unlock();
|
||||||
@@ -103,7 +105,7 @@ pub const McpServer = struct {
|
|||||||
self.stop();
|
self.stop();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std.debug.print("MCP IO Error: {}\n", .{err});
|
lp.log.err(.app, "MCP IO Error", .{ .err = err });
|
||||||
std.Thread.sleep(100 * std.time.ns_per_ms);
|
std.Thread.sleep(100 * std.time.ns_per_ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const std = @import("std");
|
|||||||
|
|
||||||
pub const Request = struct {
|
pub const Request = struct {
|
||||||
jsonrpc: []const u8 = "2.0",
|
jsonrpc: []const u8 = "2.0",
|
||||||
id: std.json.Value,
|
id: ?std.json.Value = null,
|
||||||
method: []const u8,
|
method: []const u8,
|
||||||
params: ?std.json.Value = null,
|
params: ?std.json.Value = null,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ test "protocol request parsing" {
|
|||||||
const req = parsed.value;
|
const req = parsed.value;
|
||||||
try testing.expectEqualStrings("2.0", req.jsonrpc);
|
try testing.expectEqualStrings("2.0", req.jsonrpc);
|
||||||
try testing.expectEqualStrings("initialize", req.method);
|
try testing.expectEqualStrings("initialize", req.method);
|
||||||
try testing.expect(req.id == .integer);
|
try testing.expect(req.id.? == .integer);
|
||||||
try testing.expectEqual(@as(i64, 1), req.id.integer);
|
try testing.expectEqual(@as(i64, 1), req.id.?.integer);
|
||||||
try testing.expect(req.params != null);
|
try testing.expect(req.params != null);
|
||||||
|
|
||||||
// Test nested parsing of InitializeParams
|
// Test nested parsing of InitializeParams
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ pub fn handleList(server: *McpServer, req: protocol.Request) !void {
|
|||||||
.resources = &resources,
|
.resources = &resources,
|
||||||
};
|
};
|
||||||
|
|
||||||
try sendResult(server, req.id, result);
|
try sendResult(server, req.id.?, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReadParams = struct {
|
const ReadParams = struct {
|
||||||
@@ -34,16 +34,18 @@ const ReadParams = struct {
|
|||||||
|
|
||||||
pub fn handleRead(server: *McpServer, arena: std.mem.Allocator, req: protocol.Request) !void {
|
pub fn handleRead(server: *McpServer, 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");
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = std.json.parseFromValueLeaky(ReadParams, arena, req.params.?, .{}) catch {
|
const params = std.json.parseFromValueLeaky(ReadParams, arena, req.params.?, .{}) catch {
|
||||||
return sendError(server, req.id, -32602, "Invalid params");
|
return sendError(server, req.id.?, -32602, "Invalid params");
|
||||||
};
|
};
|
||||||
|
|
||||||
if (std.mem.eql(u8, params.uri, "mcp://page/html")) {
|
if (std.mem.eql(u8, params.uri, "mcp://page/html")) {
|
||||||
var aw = std.Io.Writer.Allocating.init(arena);
|
var aw = std.Io.Writer.Allocating.init(arena);
|
||||||
try lp.dump.root(server.page.window._document, .{}, &aw.writer, server.page);
|
lp.dump.root(server.page.document.asNode(), .{}, &aw.writer, server.page) catch {
|
||||||
|
return sendError(server, req.id.?, -32603, "Internal error reading HTML");
|
||||||
|
};
|
||||||
|
|
||||||
const contents = [_]struct {
|
const contents = [_]struct {
|
||||||
uri: []const u8,
|
uri: []const u8,
|
||||||
@@ -54,10 +56,12 @@ pub fn handleRead(server: *McpServer, arena: std.mem.Allocator, req: protocol.Re
|
|||||||
.mimeType = "text/html",
|
.mimeType = "text/html",
|
||||||
.text = aw.written(),
|
.text = aw.written(),
|
||||||
}};
|
}};
|
||||||
try sendResult(server, req.id, .{ .contents = &contents });
|
try sendResult(server, req.id.?, .{ .contents = &contents });
|
||||||
} else if (std.mem.eql(u8, params.uri, "mcp://page/markdown")) {
|
} else if (std.mem.eql(u8, params.uri, "mcp://page/markdown")) {
|
||||||
var aw = std.Io.Writer.Allocating.init(arena);
|
var aw = std.Io.Writer.Allocating.init(arena);
|
||||||
try lp.markdown.dump(server.page.window._document.asNode(), .{}, &aw.writer, server.page);
|
lp.markdown.dump(server.page.document.asNode(), .{}, &aw.writer, server.page) catch {
|
||||||
|
return sendError(server, req.id.?, -32603, "Internal error reading Markdown");
|
||||||
|
};
|
||||||
|
|
||||||
const contents = [_]struct {
|
const contents = [_]struct {
|
||||||
uri: []const u8,
|
uri: []const u8,
|
||||||
@@ -68,9 +72,9 @@ pub fn handleRead(server: *McpServer, arena: std.mem.Allocator, req: protocol.Re
|
|||||||
.mimeType = "text/markdown",
|
.mimeType = "text/markdown",
|
||||||
.text = aw.written(),
|
.text = aw.written(),
|
||||||
}};
|
}};
|
||||||
try sendResult(server, req.id, .{ .contents = &contents });
|
try sendResult(server, req.id.?, .{ .contents = &contents });
|
||||||
} else {
|
} else {
|
||||||
return sendError(server, req.id, -32602, "Resource not found");
|
return sendError(server, req.id.?, -32602, "Resource not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ fn handleMessage(server: *McpServer, arena: std.mem.Allocator, msg: []const u8)
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (parsed.id == null) {
|
||||||
|
// It's a notification
|
||||||
|
if (std.mem.eql(u8, parsed.method, "notifications/initialized")) {
|
||||||
|
log.info(.app, "MCP Client Initialized", .{});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (std.mem.eql(u8, parsed.method, "initialize")) {
|
if (std.mem.eql(u8, parsed.method, "initialize")) {
|
||||||
try handleInitialize(server, parsed);
|
try handleInitialize(server, parsed);
|
||||||
} else if (std.mem.eql(u8, parsed.method, "resources/list")) {
|
} else if (std.mem.eql(u8, parsed.method, "resources/list")) {
|
||||||
@@ -38,12 +46,12 @@ fn handleMessage(server: *McpServer, arena: std.mem.Allocator, msg: []const u8)
|
|||||||
} else if (std.mem.eql(u8, parsed.method, "resources/read")) {
|
} else if (std.mem.eql(u8, parsed.method, "resources/read")) {
|
||||||
try resources.handleRead(server, arena, parsed);
|
try resources.handleRead(server, arena, parsed);
|
||||||
} else if (std.mem.eql(u8, parsed.method, "tools/list")) {
|
} else if (std.mem.eql(u8, parsed.method, "tools/list")) {
|
||||||
try tools.handleList(server, parsed);
|
try tools.handleList(server, arena, parsed);
|
||||||
} 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.sendResponse(protocol.Response{
|
||||||
.id = parsed.id,
|
.id = parsed.id.?,
|
||||||
.@"error" = protocol.Error{
|
.@"error" = protocol.Error{
|
||||||
.code = -32601,
|
.code = -32601,
|
||||||
.message = "Method not found",
|
.message = "Method not found",
|
||||||
@@ -78,5 +86,5 @@ fn handleInitialize(server: *McpServer, req: protocol.Request) !void {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
try sendResponseGeneric(server, req.id, result);
|
try sendResponseGeneric(server, req.id.?, result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ 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;
|
||||||
|
|
||||||
pub fn handleList(server: *McpServer, req: protocol.Request) !void {
|
pub fn handleList(server: *McpServer, arena: std.mem.Allocator, req: protocol.Request) !void {
|
||||||
const tools = [_]protocol.Tool{
|
const tools = [_]protocol.Tool{
|
||||||
.{
|
.{
|
||||||
.name = "goto",
|
.name = "goto",
|
||||||
.description = "Navigate to a specified URL and load the page in memory so it can be reused later for info extraction.",
|
.description = "Navigate to a specified URL and load the page in memory so it can be reused later for info extraction.",
|
||||||
.inputSchema = std.json.parseFromSliceLeaky(std.json.Value, server.allocator,
|
.inputSchema = std.json.parseFromSliceLeaky(std.json.Value, arena,
|
||||||
\\{
|
\\{
|
||||||
\\ "type": "object",
|
\\ "type": "object",
|
||||||
\\ "properties": {
|
\\ "properties": {
|
||||||
@@ -28,7 +28,7 @@ pub fn handleList(server: *McpServer, req: protocol.Request) !void {
|
|||||||
.{
|
.{
|
||||||
.name = "search",
|
.name = "search",
|
||||||
.description = "Use a search engine to look for specific words, terms, sentences. The search page will then be loaded in memory.",
|
.description = "Use a search engine to look for specific words, terms, sentences. The search page will then be loaded in memory.",
|
||||||
.inputSchema = std.json.parseFromSliceLeaky(std.json.Value, server.allocator,
|
.inputSchema = std.json.parseFromSliceLeaky(std.json.Value, arena,
|
||||||
\\{
|
\\{
|
||||||
\\ "type": "object",
|
\\ "type": "object",
|
||||||
\\ "properties": {
|
\\ "properties": {
|
||||||
@@ -41,17 +41,17 @@ pub fn handleList(server: *McpServer, req: protocol.Request) !void {
|
|||||||
.{
|
.{
|
||||||
.name = "markdown",
|
.name = "markdown",
|
||||||
.description = "Get the page content in markdown format.",
|
.description = "Get the page content in markdown format.",
|
||||||
.inputSchema = std.json.parseFromSliceLeaky(std.json.Value, server.allocator, "{\"type\":\"object\",\"properties\":{}}", .{}) catch unreachable,
|
.inputSchema = std.json.parseFromSliceLeaky(std.json.Value, arena, "{\"type\":\"object\",\"properties\":{}}", .{}) catch unreachable,
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.name = "links",
|
.name = "links",
|
||||||
.description = "Extract all links in the opened page",
|
.description = "Extract all links in the opened page",
|
||||||
.inputSchema = std.json.parseFromSliceLeaky(std.json.Value, server.allocator, "{\"type\":\"object\",\"properties\":{}}", .{}) catch unreachable,
|
.inputSchema = std.json.parseFromSliceLeaky(std.json.Value, arena, "{\"type\":\"object\",\"properties\":{}}", .{}) catch unreachable,
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.name = "evaluate",
|
.name = "evaluate",
|
||||||
.description = "Evaluate JavaScript in the current page context",
|
.description = "Evaluate JavaScript in the current page context",
|
||||||
.inputSchema = std.json.parseFromSliceLeaky(std.json.Value, server.allocator,
|
.inputSchema = std.json.parseFromSliceLeaky(std.json.Value, arena,
|
||||||
\\{
|
\\{
|
||||||
\\ "type": "object",
|
\\ "type": "object",
|
||||||
\\ "properties": {
|
\\ "properties": {
|
||||||
@@ -64,7 +64,7 @@ pub fn handleList(server: *McpServer, req: protocol.Request) !void {
|
|||||||
.{
|
.{
|
||||||
.name = "over",
|
.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.",
|
.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 = std.json.parseFromSliceLeaky(std.json.Value, server.allocator,
|
.inputSchema = std.json.parseFromSliceLeaky(std.json.Value, arena,
|
||||||
\\{
|
\\{
|
||||||
\\ "type": "object",
|
\\ "type": "object",
|
||||||
\\ "properties": {
|
\\ "properties": {
|
||||||
@@ -82,7 +82,7 @@ pub fn handleList(server: *McpServer, req: protocol.Request) !void {
|
|||||||
.tools = &tools,
|
.tools = &tools,
|
||||||
};
|
};
|
||||||
|
|
||||||
try sendResult(server, req.id, result);
|
try sendResult(server, req.id.?, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
const GotoParams = struct {
|
const GotoParams = struct {
|
||||||
@@ -103,7 +103,7 @@ const OverParams = struct {
|
|||||||
|
|
||||||
pub fn handleCall(server: *McpServer, arena: std.mem.Allocator, req: protocol.Request) !void {
|
pub fn handleCall(server: *McpServer, 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");
|
||||||
}
|
}
|
||||||
|
|
||||||
const CallParams = struct {
|
const CallParams = struct {
|
||||||
@@ -112,67 +112,79 @@ pub fn handleCall(server: *McpServer, arena: std.mem.Allocator, req: protocol.Re
|
|||||||
};
|
};
|
||||||
|
|
||||||
const call_params = std.json.parseFromValueLeaky(CallParams, arena, req.params.?, .{}) catch {
|
const call_params = std.json.parseFromValueLeaky(CallParams, arena, req.params.?, .{}) catch {
|
||||||
return sendError(server, req.id, -32602, "Invalid params");
|
return sendError(server, req.id.?, -32602, "Invalid params");
|
||||||
};
|
};
|
||||||
|
|
||||||
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")) {
|
||||||
if (call_params.arguments == null) {
|
if (call_params.arguments == null) {
|
||||||
return sendError(server, req.id, -32602, "Missing arguments for goto");
|
return sendError(server, req.id.?, -32602, "Missing arguments for goto");
|
||||||
}
|
}
|
||||||
const args = std.json.parseFromValueLeaky(GotoParams, arena, call_params.arguments.?, .{}) catch {
|
const args = std.json.parseFromValueLeaky(GotoParams, arena, call_params.arguments.?, .{}) catch {
|
||||||
return sendError(server, req.id, -32602, "Invalid arguments for goto");
|
return sendError(server, req.id.?, -32602, "Invalid arguments for goto");
|
||||||
};
|
};
|
||||||
|
|
||||||
try performGoto(server, arena, args.url);
|
performGoto(server, arena, args.url) catch {
|
||||||
|
return sendError(server, req.id.?, -32603, "Internal error during navigation");
|
||||||
|
};
|
||||||
|
|
||||||
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, req.id, .{ .content = &content });
|
try sendResult(server, req.id.?, .{ .content = &content });
|
||||||
} else if (std.mem.eql(u8, call_params.name, "search")) {
|
} else if (std.mem.eql(u8, call_params.name, "search")) {
|
||||||
if (call_params.arguments == null) {
|
if (call_params.arguments == null) {
|
||||||
return sendError(server, req.id, -32602, "Missing arguments for search");
|
return sendError(server, req.id.?, -32602, "Missing arguments for search");
|
||||||
}
|
}
|
||||||
const args = std.json.parseFromValueLeaky(SearchParams, arena, call_params.arguments.?, .{}) catch {
|
const args = std.json.parseFromValueLeaky(SearchParams, arena, call_params.arguments.?, .{}) catch {
|
||||||
return sendError(server, req.id, -32602, "Invalid arguments for search");
|
return sendError(server, req.id.?, -32602, "Invalid arguments for search");
|
||||||
};
|
};
|
||||||
|
|
||||||
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);
|
||||||
try component.formatQuery(&url_aw.writer);
|
component.formatQuery(&url_aw.writer) catch {
|
||||||
const url = try std.fmt.allocPrint(arena, "https://duckduckgo.com/?q={s}", .{url_aw.written()});
|
return sendError(server, req.id.?, -32603, "Internal error formatting query");
|
||||||
|
};
|
||||||
|
const url = std.fmt.allocPrint(arena, "https://duckduckgo.com/?q={s}", .{url_aw.written()}) catch {
|
||||||
|
return sendError(server, req.id.?, -32603, "Internal error formatting URL");
|
||||||
|
};
|
||||||
|
|
||||||
try performGoto(server, arena, url);
|
performGoto(server, arena, url) catch {
|
||||||
|
return sendError(server, req.id.?, -32603, "Internal error during search navigation");
|
||||||
|
};
|
||||||
|
|
||||||
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, req.id, .{ .content = &content });
|
try sendResult(server, req.id.?, .{ .content = &content });
|
||||||
} else if (std.mem.eql(u8, call_params.name, "markdown")) {
|
} else if (std.mem.eql(u8, call_params.name, "markdown")) {
|
||||||
var aw = std.Io.Writer.Allocating.init(arena);
|
var aw = std.Io.Writer.Allocating.init(arena);
|
||||||
try lp.markdown.dump(server.page.document.asNode(), .{}, &aw.writer, server.page);
|
lp.markdown.dump(server.page.document.asNode(), .{}, &aw.writer, server.page) catch {
|
||||||
|
return sendError(server, req.id.?, -32603, "Internal error parsing markdown");
|
||||||
|
};
|
||||||
|
|
||||||
const content = [_]struct { type: []const u8, text: []const u8 }{.{ .type = "text", .text = aw.written() }};
|
const content = [_]struct { type: []const u8, text: []const u8 }{.{ .type = "text", .text = aw.written() }};
|
||||||
try sendResult(server, req.id, .{ .content = &content });
|
try sendResult(server, req.id.?, .{ .content = &content });
|
||||||
} else if (std.mem.eql(u8, call_params.name, "links")) {
|
} else if (std.mem.eql(u8, call_params.name, "links")) {
|
||||||
const list = try Selector.querySelectorAll(server.page.document.asNode(), "a[href]", server.page);
|
const list = Selector.querySelectorAll(server.page.document.asNode(), "a[href]", server.page) catch {
|
||||||
|
return sendError(server, req.id.?, -32603, "Internal error querying selector");
|
||||||
|
};
|
||||||
|
|
||||||
var aw = std.Io.Writer.Allocating.init(arena);
|
var aw = std.Io.Writer.Allocating.init(arena);
|
||||||
var first = true;
|
var first = true;
|
||||||
for (list._nodes) |node| {
|
for (list._nodes) |node| {
|
||||||
if (node.is(Element)) |el| {
|
if (node.is(Element)) |el| {
|
||||||
if (el.getAttributeSafe(String.wrap("href"))) |href| {
|
if (el.getAttributeSafe(String.wrap("href"))) |href| {
|
||||||
if (!first) try aw.writer.writeByte('\n');
|
if (!first) aw.writer.writeByte('\n') catch continue;
|
||||||
try aw.writer.writeAll(href);
|
aw.writer.writeAll(href) catch continue;
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = [_]struct { type: []const u8, text: []const u8 }{.{ .type = "text", .text = aw.written() }};
|
const content = [_]struct { type: []const u8, text: []const u8 }{.{ .type = "text", .text = aw.written() }};
|
||||||
try sendResult(server, req.id, .{ .content = &content });
|
try sendResult(server, req.id.?, .{ .content = &content });
|
||||||
} else if (std.mem.eql(u8, call_params.name, "evaluate")) {
|
} else if (std.mem.eql(u8, call_params.name, "evaluate")) {
|
||||||
if (call_params.arguments == null) {
|
if (call_params.arguments == null) {
|
||||||
return sendError(server, req.id, -32602, "Missing arguments for evaluate");
|
return sendError(server, req.id.?, -32602, "Missing arguments for evaluate");
|
||||||
}
|
}
|
||||||
const args = std.json.parseFromValueLeaky(EvaluateParams, arena, call_params.arguments.?, .{}) catch {
|
const args = std.json.parseFromValueLeaky(EvaluateParams, arena, call_params.arguments.?, .{}) catch {
|
||||||
return sendError(server, req.id, -32602, "Invalid arguments for evaluate");
|
return sendError(server, req.id.?, -32602, "Invalid arguments for evaluate");
|
||||||
};
|
};
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
var ls: js.Local.Scope = undefined;
|
||||||
@@ -181,25 +193,25 @@ pub fn handleCall(server: *McpServer, arena: std.mem.Allocator, req: protocol.Re
|
|||||||
|
|
||||||
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, req.id, .{ .content = &content, .isError = true });
|
return sendResult(server, req.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, req.id, .{ .content = &content });
|
try sendResult(server, req.id.?, .{ .content = &content });
|
||||||
} else if (std.mem.eql(u8, call_params.name, "over")) {
|
} else if (std.mem.eql(u8, call_params.name, "over")) {
|
||||||
if (call_params.arguments == null) {
|
if (call_params.arguments == null) {
|
||||||
return sendError(server, req.id, -32602, "Missing arguments for over");
|
return sendError(server, req.id.?, -32602, "Missing arguments for over");
|
||||||
}
|
}
|
||||||
const args = std.json.parseFromValueLeaky(OverParams, arena, call_params.arguments.?, .{}) catch {
|
const args = std.json.parseFromValueLeaky(OverParams, arena, call_params.arguments.?, .{}) catch {
|
||||||
return sendError(server, req.id, -32602, "Invalid arguments for over");
|
return sendError(server, req.id.?, -32602, "Invalid arguments for 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, req.id, .{ .content = &content });
|
try sendResult(server, req.id.?, .{ .content = &content });
|
||||||
} else {
|
} else {
|
||||||
return sendError(server, req.id, -32601, "Tool not found");
|
return sendError(server, req.id.?, -32601, "Tool not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user