From cf5e4d7d1efad2b93449db08c2622b44046dd666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Sun, 29 Mar 2026 08:29:04 +0200 Subject: [PATCH 1/5] mcp: allow configuring protocol version Closes #2023 --- src/Config.zig | 45 +++++++++++++++++++++++++++++++++++++++++--- src/mcp.zig | 1 + src/mcp/Server.zig | 2 +- src/mcp/protocol.zig | 9 +++++++++ src/mcp/router.zig | 6 +++--- 5 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/Config.zig b/src/Config.zig index b0abddaf..9194ec12 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -24,6 +24,7 @@ const log = @import("log.zig"); const dump = @import("browser/dump.zig"); const WebBotAuthConfig = @import("network/WebBotAuth.zig").Config; +const mcp = @import("mcp.zig"); pub const RunMode = enum { help, @@ -188,6 +189,13 @@ 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.latest), + }; +} + pub fn maxConnections(self: *const Config) u16 { return switch (self.mode) { .serve => |opts| opts.cdp_max_connections, @@ -222,6 +230,7 @@ pub const Serve = struct { pub const Mcp = struct { common: Common = .{}, + version: mcp.Version = .latest, }; pub const DumpFormat = enum { @@ -453,6 +462,13 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void { \\Starts an MCP (Model Context Protocol) server over stdio \\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 "2025-11-25". + \\ ++ common_options ++ \\ \\version command @@ -640,10 +656,25 @@ fn parseMcpArgs( allocator: Allocator, args: *std.process.ArgIterator, ) !Mcp { - var mcp: Mcp = .{}; + var result: 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| { - if (try parseCommonArg(allocator, opt, args, &mcp.common)) { + if (std.mem.eql(u8, "--version", opt)) { + 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; } @@ -651,7 +682,15 @@ fn parseMcpArgs( return error.UnkownOption; } - return mcp; + const final_ver = arg_ver orelse env_ver; + 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( diff --git a/src/mcp.zig b/src/mcp.zig index ca92206c..de013bc9 100644 --- a/src/mcp.zig +++ b/src/mcp.zig @@ -1,6 +1,7 @@ const std = @import("std"); pub const protocol = @import("mcp/protocol.zig"); +pub const Version = protocol.Version; pub const router = @import("mcp/router.zig"); pub const Server = @import("mcp/Server.zig"); diff --git a/src/mcp/Server.zig b/src/mcp/Server.zig index ffba7396..165b344d 100644 --- a/src/mcp/Server.zig +++ b/src/mcp/Server.zig @@ -114,7 +114,7 @@ test "MCP.Server - Integration: synchronous smoke test" { try router.processRequests(server, &in_reader); - try testing.expectJson(.{ .jsonrpc = "2.0", .id = 1 }, out_alloc.writer.buffered()); + try testing.expectJson(.{ .jsonrpc = "2.0", .id = 1, .result = .{ .protocolVersion = "2025-11-25" } }, out_alloc.writer.buffered()); } test "MCP.Server - Integration: ping request returns an empty result" { diff --git a/src/mcp/protocol.zig b/src/mcp/protocol.zig index 1375cd00..fb02ad8b 100644 --- a/src/mcp/protocol.zig +++ b/src/mcp/protocol.zig @@ -1,5 +1,14 @@ const std = @import("std"); +pub const Version = enum { + @"2024-11-05", + @"2025-03-26", + @"2025-06-18", + @"2025-11-25", + + pub const latest: Version = .@"2025-11-25"; +}; + pub const Request = struct { jsonrpc: []const u8 = "2.0", id: ?std.json.Value = null, diff --git a/src/mcp/router.zig b/src/mcp/router.zig index 02581776..76e1b8fe 100644 --- a/src/mcp/router.zig +++ b/src/mcp/router.zig @@ -81,8 +81,8 @@ pub fn handleMessage(server: *Server, arena: std.mem.Allocator, msg: []const u8) fn handleInitialize(server: *Server, req: protocol.Request) !void { const id = req.id orelse return; - const result = protocol.InitializeResult{ - .protocolVersion = "2025-11-25", + const result: protocol.InitializeResult = .{ + .protocolVersion = server.app.config.mcpVersion(), .capabilities = .{ .resources = .{}, .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"}}} ); try testing.expectJson( - \\{ "jsonrpc": "2.0", "id": 1, "result": { "capabilities": { "tools": {} } } } + \\{ "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2025-11-25", "capabilities": { "tools": {} } } } , out_alloc.writer.buffered()); out_alloc.writer.end = 0; From 81d4bdb157fa8b7947fd4ac3aa862a568b81e593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Sun, 29 Mar 2026 08:34:24 +0200 Subject: [PATCH 2/5] mcp: change default protocol version to 2024-11-05 --- src/Config.zig | 6 +++--- src/mcp/Server.zig | 2 +- src/mcp/protocol.zig | 2 +- src/mcp/router.zig | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Config.zig b/src/Config.zig index 9194ec12..8f133b15 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -192,7 +192,7 @@ 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.latest), + else => @tagName(mcp.Version.default), }; } @@ -230,7 +230,7 @@ pub const Serve = struct { pub const Mcp = struct { common: Common = .{}, - version: mcp.Version = .latest, + version: mcp.Version = .default, }; pub const DumpFormat = enum { @@ -467,7 +467,7 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void { \\ 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 "2025-11-25". + \\ Defaults to "2024-11-05". \\ ++ common_options ++ \\ diff --git a/src/mcp/Server.zig b/src/mcp/Server.zig index 165b344d..a334bc38 100644 --- a/src/mcp/Server.zig +++ b/src/mcp/Server.zig @@ -114,7 +114,7 @@ test "MCP.Server - Integration: synchronous smoke test" { try router.processRequests(server, &in_reader); - try testing.expectJson(.{ .jsonrpc = "2.0", .id = 1, .result = .{ .protocolVersion = "2025-11-25" } }, out_alloc.writer.buffered()); + try testing.expectJson(.{ .jsonrpc = "2.0", .id = 1, .result = .{ .protocolVersion = "2024-11-05" } }, out_alloc.writer.buffered()); } test "MCP.Server - Integration: ping request returns an empty result" { diff --git a/src/mcp/protocol.zig b/src/mcp/protocol.zig index fb02ad8b..02927278 100644 --- a/src/mcp/protocol.zig +++ b/src/mcp/protocol.zig @@ -6,7 +6,7 @@ pub const Version = enum { @"2025-06-18", @"2025-11-25", - pub const latest: Version = .@"2025-11-25"; + pub const default: Version = .@"2024-11-05"; }; pub const Request = struct { diff --git a/src/mcp/router.zig b/src/mcp/router.zig index 76e1b8fe..e3b08c68 100644 --- a/src/mcp/router.zig +++ b/src/mcp/router.zig @@ -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"}}} ); try testing.expectJson( - \\{ "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2025-11-25", "capabilities": { "tools": {} } } } + \\{ "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2024-11-05", "capabilities": { "tools": {} } } } , out_alloc.writer.buffered()); out_alloc.writer.end = 0; From e083d4a3d136e12cad9fb110785607a47cdc8fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Mon, 30 Mar 2026 07:07:23 +0200 Subject: [PATCH 3/5] Config: remove LIGHTPANDA_MCP_VERSION env var --- src/Config.zig | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/Config.zig b/src/Config.zig index 8f133b15..de413e35 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -466,7 +466,6 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void { \\--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 ++ @@ -657,12 +656,6 @@ fn parseMcpArgs( args: *std.process.ArgIterator, ) !Mcp { var result: 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| { if (std.mem.eql(u8, "--version", opt)) { @@ -670,7 +663,10 @@ fn parseMcpArgs( log.fatal(.mcp, "missing argument value", .{ .arg = opt }); return error.InvalidArgument; }; - arg_ver = str; + result.version = std.meta.stringToEnum(mcp.Version, str) orelse { + log.fatal(.mcp, "invalid protocol version", .{ .value = str }); + return error.InvalidArgument; + }; continue; } @@ -682,14 +678,6 @@ fn parseMcpArgs( return error.UnkownOption; } - const final_ver = arg_ver orelse env_ver; - 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; } From 20e62a5551295f2d8b295eb24a47ffbef82c3ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Mon, 30 Mar 2026 07:13:45 +0200 Subject: [PATCH 4/5] mcp: inline mcpVersion helper from Config --- src/Config.zig | 6 ------ src/mcp/router.zig | 6 +++++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Config.zig b/src/Config.zig index de413e35..ae5a2667 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -189,12 +189,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 { return switch (self.mode) { diff --git a/src/mcp/router.zig b/src/mcp/router.zig index e3b08c68..63945947 100644 --- a/src/mcp/router.zig +++ b/src/mcp/router.zig @@ -81,8 +81,12 @@ pub fn handleMessage(server: *Server, arena: std.mem.Allocator, msg: []const u8) fn handleInitialize(server: *Server, req: protocol.Request) !void { const id = req.id orelse return; + const version: protocol.Version = switch (server.app.config.mode) { + .mcp => |opts| opts.version, + else => .default, + }; const result: protocol.InitializeResult = .{ - .protocolVersion = server.app.config.mcpVersion(), + .protocolVersion = @tagName(version), .capabilities = .{ .resources = .{}, .tools = .{}, From d99599fa212c8537e0fab2053a761fbd5992dbc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Mon, 30 Mar 2026 07:24:08 +0200 Subject: [PATCH 5/5] zig fmt --- src/Config.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Config.zig b/src/Config.zig index ae5a2667..20730fa3 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -189,7 +189,6 @@ pub fn webBotAuth(self: *const Config) ?WebBotAuthConfig { }; } - pub fn maxConnections(self: *const Config) u16 { return switch (self.mode) { .serve => |opts| opts.cdp_max_connections,