Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions src/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -222,6 +223,7 @@ pub const Serve = struct {

pub const Mcp = struct {
common: Common = .{},
version: mcp.Version = .default,
};

pub const DumpFormat = enum {
Expand Down Expand Up @@ -453,6 +455,12 @@ 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.
\\ Defaults to "2024-11-05".
\\
++ common_options ++
\\
\\version command
Expand Down Expand Up @@ -640,18 +648,30 @@ fn parseMcpArgs(
allocator: Allocator,
args: *std.process.ArgIterator,
) !Mcp {
var mcp: Mcp = .{};
var result: Mcp = .{};

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;
};
result.version = std.meta.stringToEnum(mcp.Version, str) orelse {
log.fatal(.mcp, "invalid protocol version", .{ .value = str });
return error.InvalidArgument;
};
continue;
}

if (try parseCommonArg(allocator, opt, args, &result.common)) {
continue;
}

log.fatal(.mcp, "unknown argument", .{ .mode = "mcp", .arg = opt });
return error.UnkownOption;
}

return mcp;
return result;
}

fn parseFetchArgs(
Expand Down
1 change: 1 addition & 0 deletions src/mcp.zig
Original file line number Diff line number Diff line change
@@ -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");

Expand Down
2 changes: 1 addition & 1 deletion src/mcp/Server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "2024-11-05" } }, out_alloc.writer.buffered());
}

test "MCP.Server - Integration: ping request returns an empty result" {
Expand Down
9 changes: 9 additions & 0 deletions src/mcp/protocol.zig
Original file line number Diff line number Diff line change
@@ -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 default: Version = .@"2024-11-05";
};

pub const Request = struct {
jsonrpc: []const u8 = "2.0",
id: ?std.json.Value = null,
Expand Down
10 changes: 7 additions & 3 deletions src/mcp/router.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 result = protocol.InitializeResult{
.protocolVersion = "2025-11-25",
const version: protocol.Version = switch (server.app.config.mode) {
.mcp => |opts| opts.version,
else => .default,
};
const result: protocol.InitializeResult = .{
.protocolVersion = @tagName(version),
.capabilities = .{
.resources = .{},
.tools = .{},
Expand Down Expand Up @@ -121,7 +125,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": "2024-11-05", "capabilities": { "tools": {} } } }
, out_alloc.writer.buffered());
out_alloc.writer.end = 0;

Expand Down
Loading