From 5f9a7a53814a31f561da287b35798b22d1ed002d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Sat, 28 Feb 2026 22:18:37 +0900 Subject: [PATCH] mcp: ignore unknown json fields and improve error reporting --- src/mcp/resources.zig | 2 +- src/mcp/router.zig | 3 +-- src/mcp/tools.zig | 17 ++++++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/mcp/resources.zig b/src/mcp/resources.zig index cfb77add..e9553167 100644 --- a/src/mcp/resources.zig +++ b/src/mcp/resources.zig @@ -39,7 +39,7 @@ pub fn handleRead(server: *Server, arena: std.mem.Allocator, req: protocol.Reque 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.?, .{ .ignore_unknown_fields = true }) catch { return sendError(server, req.id.?, -32602, "Invalid params"); }; diff --git a/src/mcp/router.zig b/src/mcp/router.zig index b07be916..213db4fe 100644 --- a/src/mcp/router.zig +++ b/src/mcp/router.zig @@ -24,8 +24,7 @@ pub fn processRequests(server: *Server) !void { if (msg.len == 0) continue; - // Critical: Per-request Arena - var arena = std.heap.ArenaAllocator.init(server.allocator); + var arena: std.heap.ArenaAllocator = .init(server.allocator); defer arena.deinit(); handleMessage(server, arena.allocator(), msg) catch |err| { diff --git a/src/mcp/tools.zig b/src/mcp/tools.zig index 06453ae5..5f1e3cf5 100644 --- a/src/mcp/tools.zig +++ b/src/mcp/tools.zig @@ -126,15 +126,18 @@ pub fn handleCall(server: *Server, arena: std.mem.Allocator, req: protocol.Reque arguments: ?std.json.Value = null, }; - const call_params = std.json.parseFromValueLeaky(CallParams, arena, req.params.?, .{}) catch { - return sendError(server, req.id.?, -32602, "Invalid params"); + const call_params = std.json.parseFromValueLeaky(CallParams, arena, req.params.?, .{ .ignore_unknown_fields = true }) catch { + var aw: std.Io.Writer.Allocating = .init(arena); + std.json.Stringify.value(req.params.?, .{}, &aw.writer) catch {}; + const msg = std.fmt.allocPrint(arena, "Invalid params: {s}", .{aw.written()}) catch "Invalid params"; + return sendError(server, req.id.?, -32602, msg); }; if (std.mem.eql(u8, call_params.name, "goto") or std.mem.eql(u8, call_params.name, "navigate")) { if (call_params.arguments == null) { 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.?, .{ .ignore_unknown_fields = true }) catch { return sendError(server, req.id.?, -32602, "Invalid arguments for goto"); }; @@ -148,7 +151,7 @@ pub fn handleCall(server: *Server, arena: std.mem.Allocator, req: protocol.Reque if (call_params.arguments == null) { 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.?, .{ .ignore_unknown_fields = true }) catch { return sendError(server, req.id.?, -32602, "Invalid arguments for search"); }; @@ -172,7 +175,7 @@ pub fn handleCall(server: *Server, arena: std.mem.Allocator, req: protocol.Reque url: ?[]const u8 = null, }; if (call_params.arguments) |args_raw| { - if (std.json.parseFromValueLeaky(MarkdownParams, arena, args_raw, .{})) |args| { + if (std.json.parseFromValueLeaky(MarkdownParams, arena, args_raw, .{ .ignore_unknown_fields = true })) |args| { if (args.url) |u| { performGoto(server, arena, u) catch { return sendError(server, req.id.?, -32603, "Internal error during navigation"); @@ -192,7 +195,7 @@ pub fn handleCall(server: *Server, arena: std.mem.Allocator, req: protocol.Reque url: ?[]const u8 = null, }; if (call_params.arguments) |args_raw| { - if (std.json.parseFromValueLeaky(LinksParams, arena, args_raw, .{})) |args| { + if (std.json.parseFromValueLeaky(LinksParams, arena, args_raw, .{ .ignore_unknown_fields = true })) |args| { if (args.url) |u| { performGoto(server, arena, u) catch { return sendError(server, req.id.?, -32603, "Internal error during navigation"); @@ -228,7 +231,7 @@ pub fn handleCall(server: *Server, arena: std.mem.Allocator, req: protocol.Reque url: ?[]const u8 = null, }; - const args = std.json.parseFromValueLeaky(EvaluateParamsEx, arena, call_params.arguments.?, .{}) catch { + const args = std.json.parseFromValueLeaky(EvaluateParamsEx, arena, call_params.arguments.?, .{ .ignore_unknown_fields = true }) catch { return sendError(server, req.id.?, -32602, "Invalid arguments for evaluate"); };