mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-30 00:20:05 +00:00
Compare commits
1 Commits
mcp-protoc
...
http_clien
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f60e5cce6d |
@@ -24,7 +24,6 @@ const log = @import("log.zig");
|
|||||||
const dump = @import("browser/dump.zig");
|
const dump = @import("browser/dump.zig");
|
||||||
|
|
||||||
const WebBotAuthConfig = @import("network/WebBotAuth.zig").Config;
|
const WebBotAuthConfig = @import("network/WebBotAuth.zig").Config;
|
||||||
const mcp = @import("mcp.zig");
|
|
||||||
|
|
||||||
pub const RunMode = enum {
|
pub const RunMode = enum {
|
||||||
help,
|
help,
|
||||||
@@ -189,13 +188,6 @@ pub fn webBotAuth(self: *const Config) ?WebBotAuthConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mcpVersion(self: *const Config) []const u8 {
|
|
||||||
return switch (self.mode) {
|
|
||||||
.mcp => |opts| @tagName(opts.version),
|
|
||||||
else => @tagName(mcp.Version.default),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn maxConnections(self: *const Config) u16 {
|
pub fn maxConnections(self: *const Config) u16 {
|
||||||
return switch (self.mode) {
|
return switch (self.mode) {
|
||||||
.serve => |opts| opts.cdp_max_connections,
|
.serve => |opts| opts.cdp_max_connections,
|
||||||
@@ -230,7 +222,6 @@ pub const Serve = struct {
|
|||||||
|
|
||||||
pub const Mcp = struct {
|
pub const Mcp = struct {
|
||||||
common: Common = .{},
|
common: Common = .{},
|
||||||
version: mcp.Version = .default,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const DumpFormat = enum {
|
pub const DumpFormat = enum {
|
||||||
@@ -462,13 +453,6 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void {
|
|||||||
\\Starts an MCP (Model Context Protocol) server over stdio
|
\\Starts an MCP (Model Context Protocol) server over stdio
|
||||||
\\Example: {s} mcp
|
\\Example: {s} mcp
|
||||||
\\
|
\\
|
||||||
\\Options:
|
|
||||||
\\--version
|
|
||||||
\\ Override the reported MCP version.
|
|
||||||
\\ Valid: 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25.
|
|
||||||
\\ Can also be set via LIGHTPANDA_MCP_VERSION env var.
|
|
||||||
\\ Defaults to "2024-11-05".
|
|
||||||
\\
|
|
||||||
++ common_options ++
|
++ common_options ++
|
||||||
\\
|
\\
|
||||||
\\version command
|
\\version command
|
||||||
@@ -656,25 +640,10 @@ fn parseMcpArgs(
|
|||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
args: *std.process.ArgIterator,
|
args: *std.process.ArgIterator,
|
||||||
) !Mcp {
|
) !Mcp {
|
||||||
var result: Mcp = .{};
|
var mcp: Mcp = .{};
|
||||||
var env_ver: ?[]const u8 = null;
|
|
||||||
var arg_ver: ?[]const u8 = null;
|
|
||||||
|
|
||||||
if (std.posix.getenv("LIGHTPANDA_MCP_VERSION")) |ver| {
|
|
||||||
env_ver = ver;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (args.next()) |opt| {
|
while (args.next()) |opt| {
|
||||||
if (std.mem.eql(u8, "--version", opt)) {
|
if (try parseCommonArg(allocator, opt, args, &mcp.common)) {
|
||||||
const str = args.next() orelse {
|
|
||||||
log.fatal(.mcp, "missing argument value", .{ .arg = opt });
|
|
||||||
return error.InvalidArgument;
|
|
||||||
};
|
|
||||||
arg_ver = str;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (try parseCommonArg(allocator, opt, args, &result.common)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,15 +651,7 @@ fn parseMcpArgs(
|
|||||||
return error.UnkownOption;
|
return error.UnkownOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
const final_ver = arg_ver orelse env_ver;
|
return mcp;
|
||||||
if (final_ver) |ver| {
|
|
||||||
result.version = std.meta.stringToEnum(mcp.Version, ver) orelse {
|
|
||||||
log.fatal(.mcp, "invalid protocol version", .{ .value = ver });
|
|
||||||
return error.InvalidArgument;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseFetchArgs(
|
fn parseFetchArgs(
|
||||||
|
|||||||
@@ -821,16 +821,16 @@ fn processOneMessage(self: *Client, msg: http.Handles.MultiMessage, transfer: *T
|
|||||||
break :blk std.ascii.eqlIgnoreCase(hdr.value, "close");
|
break :blk std.ascii.eqlIgnoreCase(hdr.value, "close");
|
||||||
};
|
};
|
||||||
|
|
||||||
if (msg.err != null and !is_conn_close_recv) {
|
|
||||||
transfer.requestFailed(transfer._callback_error orelse msg.err.?, true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the transfer can't be immediately aborted from a callback
|
// make sure the transfer can't be immediately aborted from a callback
|
||||||
// since we still need it here.
|
// since we still need it here.
|
||||||
transfer._performing = true;
|
transfer._performing = true;
|
||||||
defer transfer._performing = false;
|
defer transfer._performing = false;
|
||||||
|
|
||||||
|
if (msg.err != null and !is_conn_close_recv) {
|
||||||
|
transfer.requestFailed(transfer._callback_error orelse msg.err.?, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!transfer._header_done_called) {
|
if (!transfer._header_done_called) {
|
||||||
// In case of request w/o data, we need to call the header done
|
// In case of request w/o data, we need to call the header done
|
||||||
// callback now.
|
// callback now.
|
||||||
@@ -873,7 +873,6 @@ fn processMessages(self: *Client) !bool {
|
|||||||
var processed = false;
|
var processed = false;
|
||||||
while (self.handles.readMessage()) |msg| {
|
while (self.handles.readMessage()) |msg| {
|
||||||
const transfer = try Transfer.fromConnection(&msg.conn);
|
const transfer = try Transfer.fromConnection(&msg.conn);
|
||||||
|
|
||||||
const done = self.processOneMessage(msg, transfer) catch |err| blk: {
|
const done = self.processOneMessage(msg, transfer) catch |err| blk: {
|
||||||
log.err(.http, "process_messages", .{ .err = err, .req = transfer });
|
log.err(.http, "process_messages", .{ .err = err, .req = transfer });
|
||||||
transfer.requestFailed(err, true);
|
transfer.requestFailed(err, true);
|
||||||
@@ -1068,6 +1067,24 @@ pub const Transfer = struct {
|
|||||||
if (self.req.shutdown_callback) |cb| {
|
if (self.req.shutdown_callback) |cb| {
|
||||||
cb(self.ctx);
|
cb(self.ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self._performing or self.client.performing) {
|
||||||
|
// We're currently inside of a callback. This client, and libcurl
|
||||||
|
// generally don't expect a transfer to become deinitialized during
|
||||||
|
// a callback. We can flag the transfer as aborted (which is what
|
||||||
|
// we do when transfer.abort() is called in this condition) AND,
|
||||||
|
// since this "kill()"should prevent any future callbacks, the best
|
||||||
|
// we can do is null/noop them.
|
||||||
|
self.aborted = true;
|
||||||
|
self.req.start_callback = null;
|
||||||
|
self.req.shutdown_callback = null;
|
||||||
|
self.req.header_callback = Noop.headerCallback;
|
||||||
|
self.req.data_callback = Noop.dataCallback;
|
||||||
|
self.req.done_callback = Noop.doneCallback;
|
||||||
|
self.req.error_callback = Noop.errorCallback;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.deinit();
|
self.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1492,3 +1509,12 @@ pub const Transfer = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Noop = struct {
|
||||||
|
fn headerCallback(_: *Transfer) !bool {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
fn dataCallback(_: *Transfer, _: []const u8) !void {}
|
||||||
|
fn doneCallback(_: *anyopaque) !void {}
|
||||||
|
fn errorCallback(_: *anyopaque, _: anyerror) void {}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const protocol = @import("mcp/protocol.zig");
|
pub const protocol = @import("mcp/protocol.zig");
|
||||||
pub const Version = protocol.Version;
|
|
||||||
pub const router = @import("mcp/router.zig");
|
pub const router = @import("mcp/router.zig");
|
||||||
pub const Server = @import("mcp/Server.zig");
|
pub const Server = @import("mcp/Server.zig");
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ test "MCP.Server - Integration: synchronous smoke test" {
|
|||||||
|
|
||||||
try router.processRequests(server, &in_reader);
|
try router.processRequests(server, &in_reader);
|
||||||
|
|
||||||
try testing.expectJson(.{ .jsonrpc = "2.0", .id = 1, .result = .{ .protocolVersion = "2024-11-05" } }, out_alloc.writer.buffered());
|
try testing.expectJson(.{ .jsonrpc = "2.0", .id = 1 }, out_alloc.writer.buffered());
|
||||||
}
|
}
|
||||||
|
|
||||||
test "MCP.Server - Integration: ping request returns an empty result" {
|
test "MCP.Server - Integration: ping request returns an empty result" {
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const Version = enum {
|
|
||||||
@"2024-11-05",
|
|
||||||
@"2025-03-26",
|
|
||||||
@"2025-06-18",
|
|
||||||
@"2025-11-25",
|
|
||||||
|
|
||||||
pub const default: Version = .@"2024-11-05";
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Request = struct {
|
pub const Request = struct {
|
||||||
jsonrpc: []const u8 = "2.0",
|
jsonrpc: []const u8 = "2.0",
|
||||||
id: ?std.json.Value = null,
|
id: ?std.json.Value = null,
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ pub fn handleMessage(server: *Server, arena: std.mem.Allocator, msg: []const u8)
|
|||||||
|
|
||||||
fn handleInitialize(server: *Server, req: protocol.Request) !void {
|
fn handleInitialize(server: *Server, req: protocol.Request) !void {
|
||||||
const id = req.id orelse return;
|
const id = req.id orelse return;
|
||||||
const result: protocol.InitializeResult = .{
|
const result = protocol.InitializeResult{
|
||||||
.protocolVersion = server.app.config.mcpVersion(),
|
.protocolVersion = "2025-11-25",
|
||||||
.capabilities = .{
|
.capabilities = .{
|
||||||
.resources = .{},
|
.resources = .{},
|
||||||
.tools = .{},
|
.tools = .{},
|
||||||
@@ -121,7 +121,7 @@ test "MCP.router - handleMessage - synchronous unit tests" {
|
|||||||
\\{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test-client","version":"1.0.0"}}}
|
\\{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test-client","version":"1.0.0"}}}
|
||||||
);
|
);
|
||||||
try testing.expectJson(
|
try testing.expectJson(
|
||||||
\\{ "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2024-11-05", "capabilities": { "tools": {} } } }
|
\\{ "jsonrpc": "2.0", "id": 1, "result": { "capabilities": { "tools": {} } } }
|
||||||
, out_alloc.writer.buffered());
|
, out_alloc.writer.buffered());
|
||||||
out_alloc.writer.end = 0;
|
out_alloc.writer.end = 0;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user