mcp: optimize dispatching and simplify test harness

- Use StaticStringMap and enums for method, tool, and resource lookups.
- Implement comptime JSON minification for tool schemas.
- Refactor router and harness to use more efficient buffered polling.
- Consolidate integration tests and add synchronous unit tests.
This commit is contained in:
Adrià Arrufat
2026-03-02 20:53:14 +09:00
parent a7872aa054
commit 73565c4493
6 changed files with 357 additions and 350 deletions

View File

@@ -85,162 +85,64 @@ pub fn sendError(self: *Self, id: std.json.Value, code: protocol.ErrorCode, mess
const testing = @import("../testing.zig");
const McpHarness = @import("testing.zig").McpHarness;
test "MCP Integration: handshake and tools/list" {
test "MCP Integration: smoke test" {
const harness = try McpHarness.init(testing.allocator, testing.test_app);
defer harness.deinit();
harness.thread = try std.Thread.spawn(.{}, wrapTest, .{ testHandshakeAndToolsInternal, harness });
harness.thread = try std.Thread.spawn(.{}, testIntegrationSmokeInternal, .{harness});
try harness.runServer();
}
fn wrapTest(comptime func: fn (*McpHarness) anyerror!void, harness: *McpHarness) void {
const res = func(harness);
if (res) |_| {
harness.test_error = null;
} else |err| {
fn testIntegrationSmokeInternal(harness: *McpHarness) void {
const aa = harness.allocator;
var arena = std.heap.ArenaAllocator.init(aa);
defer arena.deinit();
const allocator = arena.allocator();
harness.sendRequest(
\\{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test-client","version":"1.0.0"}}}
) catch |err| {
harness.test_error = err;
}
return;
};
const response1 = harness.readResponse(allocator) catch |err| {
harness.test_error = err;
return;
};
testing.expect(std.mem.indexOf(u8, response1, "\"id\":1") != null) catch |err| {
harness.test_error = err;
return;
};
testing.expect(std.mem.indexOf(u8, response1, "\"tools\":{}") != null) catch |err| {
harness.test_error = err;
return;
};
testing.expect(std.mem.indexOf(u8, response1, "\"resources\":{}") != null) catch |err| {
harness.test_error = err;
return;
};
harness.sendRequest(
\\{"jsonrpc":"2.0","id":2,"method":"tools/list"}
) catch |err| {
harness.test_error = err;
return;
};
const response2 = harness.readResponse(allocator) catch |err| {
harness.test_error = err;
return;
};
testing.expect(std.mem.indexOf(u8, response2, "\"id\":2") != null) catch |err| {
harness.test_error = err;
return;
};
testing.expect(std.mem.indexOf(u8, response2, "\"name\":\"goto\"") != null) catch |err| {
harness.test_error = err;
return;
};
harness.server.is_running.store(false, .release);
// Ensure we trigger a poll wake up if needed
_ = harness.client_out.writeAll("\n") catch {};
}
fn testHandshakeAndToolsInternal(harness: *McpHarness) !void {
// 1. Initialize
try harness.sendRequest(
\\{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test-client","version":"1.0.0"}}}
);
var arena = std.heap.ArenaAllocator.init(harness.allocator);
defer arena.deinit();
const response1 = try harness.readResponse(arena.allocator());
try testing.expect(std.mem.indexOf(u8, response1, "\"id\":1") != null);
try testing.expect(std.mem.indexOf(u8, response1, "\"protocolVersion\":\"2025-11-25\"") != null);
// 2. Initialized notification
try harness.sendRequest(
\\{"jsonrpc":"2.0","method":"notifications/initialized"}
);
// 3. List tools
try harness.sendRequest(
\\{"jsonrpc":"2.0","id":2,"method":"tools/list"}
);
const response2 = try harness.readResponse(arena.allocator());
try testing.expect(std.mem.indexOf(u8, response2, "\"id\":2") != null);
try testing.expect(std.mem.indexOf(u8, response2, "\"name\":\"goto\"") != null);
}
test "MCP Integration: tools/call evaluate" {
const harness = try McpHarness.init(testing.allocator, testing.test_app);
defer harness.deinit();
harness.thread = try std.Thread.spawn(.{}, wrapTest, .{ testEvaluateInternal, harness });
try harness.runServer();
}
fn testEvaluateInternal(harness: *McpHarness) !void {
try harness.sendRequest(
\\{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"evaluate","arguments":{"script":"1 + 1"}}}
);
var arena = std.heap.ArenaAllocator.init(harness.allocator);
defer arena.deinit();
const response = try harness.readResponse(arena.allocator());
try testing.expect(std.mem.indexOf(u8, response, "\"id\":1") != null);
try testing.expect(std.mem.indexOf(u8, response, "\"text\":\"2\"") != null);
}
test "MCP Integration: error handling" {
const harness = try McpHarness.init(testing.allocator, testing.test_app);
defer harness.deinit();
harness.thread = try std.Thread.spawn(.{}, wrapTest, .{ testErrorHandlingInternal, harness });
try harness.runServer();
}
fn testErrorHandlingInternal(harness: *McpHarness) !void {
var arena = std.heap.ArenaAllocator.init(harness.allocator);
defer arena.deinit();
// 1. Tool not found
try harness.sendRequest(
\\{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"non_existent_tool"}}
);
const response1 = try harness.readResponse(arena.allocator());
try testing.expect(std.mem.indexOf(u8, response1, "\"id\":1") != null);
try testing.expect(std.mem.indexOf(u8, response1, "\"code\":-32601") != null);
// 2. Invalid params (missing script for evaluate)
try harness.sendRequest(
\\{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"evaluate","arguments":{}}}
);
const response2 = try harness.readResponse(arena.allocator());
try testing.expect(std.mem.indexOf(u8, response2, "\"id\":2") != null);
try testing.expect(std.mem.indexOf(u8, response2, "\"code\":-32602") != null);
}
test "MCP Integration: resources" {
const harness = try McpHarness.init(testing.allocator, testing.test_app);
defer harness.deinit();
harness.thread = try std.Thread.spawn(.{}, wrapTest, .{ testResourcesInternal, harness });
try harness.runServer();
}
fn testResourcesInternal(harness: *McpHarness) !void {
var arena = std.heap.ArenaAllocator.init(harness.allocator);
defer arena.deinit();
// 1. List resources
try harness.sendRequest(
\\{"jsonrpc":"2.0","id":1,"method":"resources/list"}
);
const response1 = try harness.readResponse(arena.allocator());
try testing.expect(std.mem.indexOf(u8, response1, "\"uri\":\"mcp://page/html\"") != null);
// 2. Read resource
try harness.sendRequest(
\\{"jsonrpc":"2.0","id":2,"method":"resources/read","params":{"uri":"mcp://page/html"}}
);
const response2 = try harness.readResponse(arena.allocator());
try testing.expect(std.mem.indexOf(u8, response2, "\"id\":2") != null);
// Just check for 'html' to be case-insensitive and robust
try testing.expect(std.mem.indexOf(u8, response2, "html") != null);
}
test "MCP Integration: tools markdown and links" {
const harness = try McpHarness.init(testing.allocator, testing.test_app);
defer harness.deinit();
harness.thread = try std.Thread.spawn(.{}, wrapTest, .{ testMarkdownAndLinksInternal, harness });
try harness.runServer();
}
fn testMarkdownAndLinksInternal(harness: *McpHarness) !void {
var arena = std.heap.ArenaAllocator.init(harness.allocator);
defer arena.deinit();
// 1. Test markdown
try harness.sendRequest(
\\{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"markdown"}}
);
const response1 = try harness.readResponse(arena.allocator());
try testing.expect(std.mem.indexOf(u8, response1, "\"id\":1") != null);
// 2. Test links
try harness.sendRequest(
\\{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"links"}}
);
const response2 = try harness.readResponse(arena.allocator());
try testing.expect(std.mem.indexOf(u8, response2, "\"id\":2") != null);
}