Problem
Direct MCP tools/call currently only dispatches params.arguments. If a client sends arguments: {} but also puts the real tool fields inline in params, the direct handler dispatches an empty argument object and reports missing 'path' / received keys: [] even though path was present on the request.
This is inconsistent with codedb_bundle, which already accepts the inline fallback shape for sub-operations. The MCP spec and Rust SDK still advertise canonical params.arguments; the bug is that our direct-call fallback and diagnostic path are less tolerant than the bundled path, making client wrapper issues look like dropped fields.
Failing Test
test "issue-512: direct tools call accepts inline args when arguments is empty" {
var explorer = Explorer.init(testing.allocator, Explorer.DEFAULT_CONTENT_CACHE_CAPACITY);
defer explorer.deinit();
try explorer.indexFile("src/main.zig", "pub fn main() void {}\n");
var store = Store.init(testing.allocator);
defer store.deinit();
var agents = AgentRegistry.init(testing.allocator);
defer agents.deinit();
_ = try agents.register("__filesystem__");
var bench_ctx = mcp_mod.BenchContext.init(testing.allocator, ".", Explorer.DEFAULT_CONTENT_CACHE_CAPACITY);
defer bench_ctx.deinit();
var telem = telemetry_mod.Telemetry.init(io, ".", testing.allocator, true);
defer telem.deinit();
const call_json =
\\{"params":{"name":"codedb_outline","arguments":{},"path":"src/main.zig"}}
;
const parsed = try std.json.parseFromSlice(std.json.Value, testing.allocator, call_json, .{});
defer parsed.deinit();
const pipe = try cio.makePipe();
defer _ = std.c.close(pipe[0]);
defer _ = std.c.close(pipe[1]);
bench_ctx.runHandleCall(
io,
testing.allocator,
&parsed.value.object,
.{ .handle = pipe[1] },
std.json.Value{ .integer = 1 },
&store,
&explorer,
&agents,
&telem,
);
var response_buf: [16 * 1024]u8 = undefined;
const n = try std.posix.read(pipe[0], &response_buf);
const response = response_buf[0..n];
try testing.expect(std.mem.indexOf(u8, response, "src/main.zig") != null);
try testing.expect(std.mem.indexOf(u8, response, "missing 'path'") == null);
}
Current failure:
zig build test -Dtest-filter=issue-512
error: 'test_mcp.test.issue-512: direct tools call accepts inline args when arguments is empty' failed
src/test_mcp.zig:1295:5: expected response to include src/main.zig
Expected
Direct tools/call should keep canonical MCP behavior (params.arguments) and also recover when arguments is empty and real fields are present inline on params, matching codedb_bundle's fallback behavior.
Fix
Normalize direct call arguments before dispatch:
- Prefer non-empty
params.arguments.
- If
params.arguments is empty or absent, accept inline non-administrative fields from params.
- Optionally accept
params.args as a compatibility alias only when canonical arguments is absent/empty.
- Keep malformed canonical
arguments as a protocol error.
- Update diagnostics so direct-call wrapper problems do not say "sub-op".
Problem
Direct MCP
tools/callcurrently only dispatchesparams.arguments. If a client sendsarguments: {}but also puts the real tool fields inline inparams, the direct handler dispatches an empty argument object and reportsmissing 'path'/received keys: []even thoughpathwas present on the request.This is inconsistent with
codedb_bundle, which already accepts the inline fallback shape for sub-operations. The MCP spec and Rust SDK still advertise canonicalparams.arguments; the bug is that our direct-call fallback and diagnostic path are less tolerant than the bundled path, making client wrapper issues look like dropped fields.Failing Test
Current failure:
Expected
Direct
tools/callshould keep canonical MCP behavior (params.arguments) and also recover whenargumentsis empty and real fields are present inline onparams, matchingcodedb_bundle's fallback behavior.Fix
Normalize direct call arguments before dispatch:
params.arguments.params.argumentsis empty or absent, accept inline non-administrative fields fromparams.params.argsas a compatibility alias only when canonicalargumentsis absent/empty.argumentsas a protocol error.