Skip to content

Commit 2135f9b

Browse files
hyperpolymathclaude
andcommitted
feat(adr-0006): migrate 5 cartridges to 5-symbol ABI (Pattern B batch 9)
Pattern B migration (model-router-mcp,mongodb-mcp,neo4j-mcp,neon-mcp,notion-mcp). All 5 ADR-0005+0006 exports added; boj_cartridge_invoke dispatches cartridge.json tools to Grade-D-Alpha stub bodies via cartridge_shim. Tests passing per cartridge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 13b772f commit 2135f9b

10 files changed

Lines changed: 552 additions & 0 deletions

File tree

cartridges/model-router-mcp/ffi/build.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,19 @@ pub fn build(b: *std.Build) void {
55
const target = b.standardTargetOptions(.{});
66
const optimize = b.standardOptimizeOption(.{});
77

8+
// Shared ADR-0006 invoke-shim module (relative path up to boj-server trunk).
9+
const shim_mod = b.addModule("cartridge_shim", .{
10+
.root_source_file = b.path("../../../ffi/zig/src/cartridge_shim.zig"),
11+
.target = target,
12+
.optimize = optimize,
13+
});
14+
815
const ffi_mod = b.addModule("model_router_ffi", .{
916
.root_source_file = b.path("model_router_ffi.zig"),
1017
.target = target,
1118
.optimize = optimize,
1219
});
20+
ffi_mod.addImport("cartridge_shim", shim_mod);
1321
const lib = b.addLibrary(.{
1422
.name = "model_router_ffi",
1523
.root_module = ffi_mod,

cartridges/model-router-mcp/ffi/model_router_ffi.zig

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,56 @@ pub export fn router_fallback(tier: i32) i32 {
2727
};
2828
}
2929

30+
// ═══════════════════════════════════════════════════════════════════════
31+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
32+
// ═══════════════════════════════════════════════════════════════════════
33+
//
34+
// Note: model-router-mcp has no cartridge.json; the 4 MCP tool names
35+
// below come from README.adoc.
36+
37+
const shim = @import("cartridge_shim");
38+
39+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "model-router-mcp";
40+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
41+
42+
export fn boj_cartridge_init() callconv(.c) c_int {
43+
return 0;
44+
}
45+
46+
export fn boj_cartridge_deinit() callconv(.c) void {}
47+
48+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
49+
return CARTRIDGE_NAME_PTR;
50+
}
51+
52+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
53+
return CARTRIDGE_VERSION_PTR;
54+
}
55+
56+
/// Dispatch the 4 MCP tools declared in README.adoc. Grade D Alpha.
57+
export fn boj_cartridge_invoke(
58+
tool_name: [*c]const u8,
59+
json_args: [*c]const u8,
60+
out_buf: [*c]u8,
61+
in_out_len: [*c]usize,
62+
) callconv(.c) i32 {
63+
_ = json_args;
64+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
65+
66+
const body: []const u8 = if (shim.toolIs(tool_name, "classify_task"))
67+
"{\"result\":{\"tier\":\"haiku\",\"status\":\"stub\"}}"
68+
else if (shim.toolIs(tool_name, "plan_delegation"))
69+
"{\"result\":{\"plan\":[],\"status\":\"stub\"}}"
70+
else if (shim.toolIs(tool_name, "review_output"))
71+
"{\"result\":{\"approved\":false,\"status\":\"stub\"}}"
72+
else if (shim.toolIs(tool_name, "estimate_cost"))
73+
"{\"result\":{\"usd\":0.0,\"status\":\"stub\"}}"
74+
else
75+
return shim.RC_UNKNOWN_TOOL;
76+
77+
return shim.writeResult(out_buf, in_out_len, body);
78+
}
79+
3080
test "model selection" {
3181
try std.testing.expectEqual(@as(i32, 0), router_select(0));
3282
try std.testing.expectEqual(@as(i32, 1), router_select(50));
@@ -38,3 +88,44 @@ test "fallback chain terminates" {
3888
try std.testing.expectEqual(@as(i32, 0), router_fallback(1));
3989
try std.testing.expectEqual(@as(i32, -1), router_fallback(0));
4090
}
91+
92+
// ═══════════════════════════════════════════════════════════════════════
93+
// ADR-0006 invoke dispatch tests
94+
// ═══════════════════════════════════════════════════════════════════════
95+
96+
test "boj_cartridge_name returns model-router-mcp" {
97+
const n = std.mem.span(boj_cartridge_name());
98+
try std.testing.expectEqualStrings("model-router-mcp", n);
99+
}
100+
101+
test "boj_cartridge_init returns 0" {
102+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
103+
}
104+
105+
test "invoke: each declared tool succeeds" {
106+
var buf: [256]u8 = undefined;
107+
const tools = [_][]const u8{
108+
"classify_task", "plan_delegation", "review_output", "estimate_cost",
109+
};
110+
for (tools) |t| {
111+
var len: usize = buf.len;
112+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
113+
try std.testing.expectEqual(@as(i32, 0), rc);
114+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
115+
}
116+
}
117+
118+
test "invoke: unknown tool returns -1" {
119+
var buf: [64]u8 = undefined;
120+
var len: usize = buf.len;
121+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
122+
try std.testing.expectEqual(@as(i32, -1), rc);
123+
}
124+
125+
test "invoke: buffer too small returns -3" {
126+
var buf: [4]u8 = undefined;
127+
var len: usize = buf.len;
128+
const rc = boj_cartridge_invoke("classify_task", "{}", &buf, &len);
129+
try std.testing.expectEqual(@as(i32, -3), rc);
130+
try std.testing.expect(len > 4);
131+
}

cartridges/mongodb-mcp/ffi/build.zig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,26 @@ pub fn build(b: *std.Build) void {
1313
const optimize = b.standardOptimizeOption(.{});
1414

1515
// Module
16+
// Shared ADR-0006 invoke-shim module (relative path up to boj-server trunk).
17+
18+
const shim_mod = b.addModule("cartridge_shim", .{
19+
20+
.root_source_file = b.path("../../../ffi/zig/src/cartridge_shim.zig"),
21+
22+
.target = target,
23+
24+
.optimize = optimize,
25+
26+
});
27+
1628
const ffi_mod = b.addModule("mongodb_mcp", .{
1729
.root_source_file = b.path("mongodb_mcp_ffi.zig"),
1830
.target = target,
1931
.optimize = optimize,
2032
});
2133

34+
ffi_mod.addImport("cartridge_shim", shim_mod);
35+
2236
// Shared library
2337
const lib = b.addLibrary(.{
2438
.name = "mongodb_mcp",

cartridges/mongodb-mcp/ffi/mongodb_mcp_ffi.zig

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,61 @@ pub export fn mongodb_mcp_reset() void {
250250
}
251251

252252
// ---------------------------------------------------------------------------
253+
// ═══════════════════════════════════════════════════════════════════════
254+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
255+
// ═══════════════════════════════════════════════════════════════════════
256+
257+
const shim = @import("cartridge_shim");
258+
259+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "mongodb-mcp";
260+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
261+
262+
export fn boj_cartridge_init() callconv(.c) c_int {
263+
return 0;
264+
}
265+
266+
export fn boj_cartridge_deinit() callconv(.c) void {}
267+
268+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
269+
return CARTRIDGE_NAME_PTR;
270+
}
271+
272+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
273+
return CARTRIDGE_VERSION_PTR;
274+
}
275+
276+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
277+
export fn boj_cartridge_invoke(
278+
tool_name: [*c]const u8,
279+
json_args: [*c]const u8,
280+
out_buf: [*c]u8,
281+
in_out_len: [*c]usize,
282+
) callconv(.c) i32 {
283+
_ = json_args;
284+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
285+
286+
const body: []const u8 = if (shim.toolIs(tool_name, "mongo_connect"))
287+
"{\"result\":{\"status\":\"stub\"}}"
288+
else if (shim.toolIs(tool_name, "mongo_find"))
289+
"{\"result\":{\"status\":\"stub\"}}"
290+
else if (shim.toolIs(tool_name, "mongo_insert"))
291+
"{\"result\":{\"status\":\"stub\"}}"
292+
else if (shim.toolIs(tool_name, "mongo_update"))
293+
"{\"result\":{\"status\":\"stub\"}}"
294+
else if (shim.toolIs(tool_name, "mongo_delete"))
295+
"{\"result\":{\"status\":\"stub\"}}"
296+
else if (shim.toolIs(tool_name, "mongo_aggregate"))
297+
"{\"result\":{\"status\":\"stub\"}}"
298+
else if (shim.toolIs(tool_name, "mongo_list_collections"))
299+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
300+
else if (shim.toolIs(tool_name, "mongo_disconnect"))
301+
"{\"result\":{\"status\":\"stub\"}}"
302+
else
303+
return shim.RC_UNKNOWN_TOOL;
304+
305+
return shim.writeResult(out_buf, in_out_len, body);
306+
}
307+
253308
// Tests
254309
// ---------------------------------------------------------------------------
255310

@@ -351,3 +406,51 @@ test "operation counting" {
351406

352407
try std.testing.expectEqual(@as(c_int, 0), mongodb_mcp_disconnect(slot));
353408
}
409+
410+
// ═══════════════════════════════════════════════════════════════════════
411+
// ADR-0006 invoke dispatch tests
412+
// ═══════════════════════════════════════════════════════════════════════
413+
414+
test "boj_cartridge_name returns mongodb-mcp" {
415+
const n = std.mem.span(boj_cartridge_name());
416+
try std.testing.expectEqualStrings("mongodb-mcp", n);
417+
}
418+
419+
test "boj_cartridge_init returns 0" {
420+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
421+
}
422+
423+
test "invoke: each declared tool succeeds" {
424+
var buf: [256]u8 = undefined;
425+
const tools = [_][]const u8{
426+
"mongo_connect",
427+
"mongo_find",
428+
"mongo_insert",
429+
"mongo_update",
430+
"mongo_delete",
431+
"mongo_aggregate",
432+
"mongo_list_collections",
433+
"mongo_disconnect",
434+
};
435+
for (tools) |t| {
436+
var len: usize = buf.len;
437+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
438+
try std.testing.expectEqual(@as(i32, 0), rc);
439+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
440+
}
441+
}
442+
443+
test "invoke: unknown tool returns -1" {
444+
var buf: [64]u8 = undefined;
445+
var len: usize = buf.len;
446+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
447+
try std.testing.expectEqual(@as(i32, -1), rc);
448+
}
449+
450+
test "invoke: buffer too small returns -3" {
451+
var buf: [4]u8 = undefined;
452+
var len: usize = buf.len;
453+
const rc = boj_cartridge_invoke("mongo_connect", "{}", &buf, &len);
454+
try std.testing.expectEqual(@as(i32, -3), rc);
455+
try std.testing.expect(len > 4);
456+
}

cartridges/neo4j-mcp/ffi/build.zig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,26 @@ pub fn build(b: *std.Build) void {
88
const optimize = b.standardOptimizeOption(.{});
99

1010
// Module
11+
// Shared ADR-0006 invoke-shim module (relative path up to boj-server trunk).
12+
13+
const shim_mod = b.addModule("cartridge_shim", .{
14+
15+
.root_source_file = b.path("../../../ffi/zig/src/cartridge_shim.zig"),
16+
17+
.target = target,
18+
19+
.optimize = optimize,
20+
21+
});
22+
1123
const ffi_mod = b.addModule("neo4j_mcp", .{
1224
.root_source_file = b.path("neo4j_mcp_ffi.zig"),
1325
.target = target,
1426
.optimize = optimize,
1527
});
1628

29+
ffi_mod.addImport("cartridge_shim", shim_mod);
30+
1731
// Shared library
1832
const lib = b.addLibrary(.{
1933
.name = "neo4j_mcp",

cartridges/neo4j-mcp/ffi/neo4j_mcp_ffi.zig

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,55 @@ pub export fn neo4j_mcp_reset() void {
238238
}
239239

240240
// ---------------------------------------------------------------------------
241+
// ═══════════════════════════════════════════════════════════════════════
242+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
243+
// ═══════════════════════════════════════════════════════════════════════
244+
245+
const shim = @import("cartridge_shim");
246+
247+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "neo4j-mcp";
248+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
249+
250+
export fn boj_cartridge_init() callconv(.c) c_int {
251+
return 0;
252+
}
253+
254+
export fn boj_cartridge_deinit() callconv(.c) void {}
255+
256+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
257+
return CARTRIDGE_NAME_PTR;
258+
}
259+
260+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
261+
return CARTRIDGE_VERSION_PTR;
262+
}
263+
264+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
265+
export fn boj_cartridge_invoke(
266+
tool_name: [*c]const u8,
267+
json_args: [*c]const u8,
268+
out_buf: [*c]u8,
269+
in_out_len: [*c]usize,
270+
) callconv(.c) i32 {
271+
_ = json_args;
272+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
273+
274+
const body: []const u8 = if (shim.toolIs(tool_name, "neo4j_connect"))
275+
"{\"result\":{\"status\":\"stub\"}}"
276+
else if (shim.toolIs(tool_name, "neo4j_disconnect"))
277+
"{\"result\":{\"status\":\"stub\"}}"
278+
else if (shim.toolIs(tool_name, "neo4j_query"))
279+
"{\"result\":{\"matches\":[],\"status\":\"stub\"}}"
280+
else if (shim.toolIs(tool_name, "neo4j_write"))
281+
"{\"result\":{\"status\":\"stub\"}}"
282+
else if (shim.toolIs(tool_name, "neo4j_schema"))
283+
"{\"result\":{\"status\":\"stub\"}}"
284+
else
285+
return shim.RC_UNKNOWN_TOOL;
286+
287+
return shim.writeResult(out_buf, in_out_len, body);
288+
}
289+
241290
// Tests
242291
// ---------------------------------------------------------------------------
243292

@@ -348,3 +397,48 @@ test "action requires connection" {
348397
try std.testing.expectEqual(@as(c_int, 0), neo4j_mcp_action_requires_connection(2)); // drop_database
349398
try std.testing.expectEqual(@as(c_int, 0), neo4j_mcp_action_requires_connection(99)); // out of range
350399
}
400+
401+
// ═══════════════════════════════════════════════════════════════════════
402+
// ADR-0006 invoke dispatch tests
403+
// ═══════════════════════════════════════════════════════════════════════
404+
405+
test "boj_cartridge_name returns neo4j-mcp" {
406+
const n = std.mem.span(boj_cartridge_name());
407+
try std.testing.expectEqualStrings("neo4j-mcp", n);
408+
}
409+
410+
test "boj_cartridge_init returns 0" {
411+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
412+
}
413+
414+
test "invoke: each declared tool succeeds" {
415+
var buf: [256]u8 = undefined;
416+
const tools = [_][]const u8{
417+
"neo4j_connect",
418+
"neo4j_disconnect",
419+
"neo4j_query",
420+
"neo4j_write",
421+
"neo4j_schema",
422+
};
423+
for (tools) |t| {
424+
var len: usize = buf.len;
425+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
426+
try std.testing.expectEqual(@as(i32, 0), rc);
427+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
428+
}
429+
}
430+
431+
test "invoke: unknown tool returns -1" {
432+
var buf: [64]u8 = undefined;
433+
var len: usize = buf.len;
434+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
435+
try std.testing.expectEqual(@as(i32, -1), rc);
436+
}
437+
438+
test "invoke: buffer too small returns -3" {
439+
var buf: [4]u8 = undefined;
440+
var len: usize = buf.len;
441+
const rc = boj_cartridge_invoke("neo4j_connect", "{}", &buf, &len);
442+
try std.testing.expectEqual(@as(i32, -3), rc);
443+
try std.testing.expect(len > 4);
444+
}

0 commit comments

Comments
 (0)