Skip to content

Commit 8cc9288

Browse files
hyperpolymathclaude
andcommitted
feat(adr-0006): migrate 5 cartridges to 5-symbol ABI (Pattern B batch 13)
Pattern B migration (slack-mcp,supabase-mcp,telegram-mcp,todoist-mcp,toolchain-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 8db7e6f commit 8cc9288

10 files changed

Lines changed: 579 additions & 0 deletions

File tree

cartridges/slack-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("slack_mcp", .{
1224
.root_source_file = b.path("slack_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 = "slack_mcp",

cartridges/slack-mcp/ffi/slack_mcp_ffi.zig

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,59 @@ pub export fn slack_mcp_reset() void {
629629
}
630630

631631
// ---------------------------------------------------------------------------
632+
// ═══════════════════════════════════════════════════════════════════════
633+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
634+
// ═══════════════════════════════════════════════════════════════════════
635+
636+
const shim = @import("cartridge_shim");
637+
638+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "slack-mcp";
639+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
640+
641+
export fn boj_cartridge_init() callconv(.c) c_int {
642+
return 0;
643+
}
644+
645+
export fn boj_cartridge_deinit() callconv(.c) void {}
646+
647+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
648+
return CARTRIDGE_NAME_PTR;
649+
}
650+
651+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
652+
return CARTRIDGE_VERSION_PTR;
653+
}
654+
655+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
656+
export fn boj_cartridge_invoke(
657+
tool_name: [*c]const u8,
658+
json_args: [*c]const u8,
659+
out_buf: [*c]u8,
660+
in_out_len: [*c]usize,
661+
) callconv(.c) i32 {
662+
_ = json_args;
663+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
664+
665+
const body: []const u8 = if (shim.toolIs(tool_name, "slack_authenticate"))
666+
"{\"result\":{\"status\":\"stub\"}}"
667+
else if (shim.toolIs(tool_name, "slack_send_message"))
668+
"{\"result\":{\"status\":\"stub\"}}"
669+
else if (shim.toolIs(tool_name, "slack_list_channels"))
670+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
671+
else if (shim.toolIs(tool_name, "slack_read_thread"))
672+
"{\"result\":{\"status\":\"stub\"}}"
673+
else if (shim.toolIs(tool_name, "slack_search"))
674+
"{\"result\":{\"matches\":[],\"status\":\"stub\"}}"
675+
else if (shim.toolIs(tool_name, "slack_get_user"))
676+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
677+
else if (shim.toolIs(tool_name, "slack_deauthenticate"))
678+
"{\"result\":{\"status\":\"stub\"}}"
679+
else
680+
return shim.RC_UNKNOWN_TOOL;
681+
682+
return shim.writeResult(out_buf, in_out_len, body);
683+
}
684+
632685
// Tests
633686
// ---------------------------------------------------------------------------
634687

@@ -751,3 +804,50 @@ test "slot exhaustion" {
751804
const new_slot = slack_mcp_authenticate("xoxb-reuse-slot-ok");
752805
try std.testing.expect(new_slot >= 0);
753806
}
807+
808+
// ═══════════════════════════════════════════════════════════════════════
809+
// ADR-0006 invoke dispatch tests
810+
// ═══════════════════════════════════════════════════════════════════════
811+
812+
test "boj_cartridge_name returns slack-mcp" {
813+
const n = std.mem.span(boj_cartridge_name());
814+
try std.testing.expectEqualStrings("slack-mcp", n);
815+
}
816+
817+
test "boj_cartridge_init returns 0" {
818+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
819+
}
820+
821+
test "invoke: each declared tool succeeds" {
822+
var buf: [256]u8 = undefined;
823+
const tools = [_][]const u8{
824+
"slack_authenticate",
825+
"slack_send_message",
826+
"slack_list_channels",
827+
"slack_read_thread",
828+
"slack_search",
829+
"slack_get_user",
830+
"slack_deauthenticate",
831+
};
832+
for (tools) |t| {
833+
var len: usize = buf.len;
834+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
835+
try std.testing.expectEqual(@as(i32, 0), rc);
836+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
837+
}
838+
}
839+
840+
test "invoke: unknown tool returns -1" {
841+
var buf: [64]u8 = undefined;
842+
var len: usize = buf.len;
843+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
844+
try std.testing.expectEqual(@as(i32, -1), rc);
845+
}
846+
847+
test "invoke: buffer too small returns -3" {
848+
var buf: [4]u8 = undefined;
849+
var len: usize = buf.len;
850+
const rc = boj_cartridge_invoke("slack_authenticate", "{}", &buf, &len);
851+
try std.testing.expectEqual(@as(i32, -3), rc);
852+
try std.testing.expect(len > 4);
853+
}

cartridges/supabase-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("supabase_mcp", .{
1224
.root_source_file = b.path("supabase_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 = "supabase_mcp",

cartridges/supabase-mcp/ffi/supabase_mcp_ffi.zig

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,59 @@ pub export fn supabase_mcp_reset() void {
210210
}
211211

212212
// ---------------------------------------------------------------------------
213+
// ═══════════════════════════════════════════════════════════════════════
214+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
215+
// ═══════════════════════════════════════════════════════════════════════
216+
217+
const shim = @import("cartridge_shim");
218+
219+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "supabase-mcp";
220+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
221+
222+
export fn boj_cartridge_init() callconv(.c) c_int {
223+
return 0;
224+
}
225+
226+
export fn boj_cartridge_deinit() callconv(.c) void {}
227+
228+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
229+
return CARTRIDGE_NAME_PTR;
230+
}
231+
232+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
233+
return CARTRIDGE_VERSION_PTR;
234+
}
235+
236+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
237+
export fn boj_cartridge_invoke(
238+
tool_name: [*c]const u8,
239+
json_args: [*c]const u8,
240+
out_buf: [*c]u8,
241+
in_out_len: [*c]usize,
242+
) callconv(.c) i32 {
243+
_ = json_args;
244+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
245+
246+
const body: []const u8 = if (shim.toolIs(tool_name, "supabase_connect"))
247+
"{\"result\":{\"status\":\"stub\"}}"
248+
else if (shim.toolIs(tool_name, "supabase_query"))
249+
"{\"result\":{\"matches\":[],\"status\":\"stub\"}}"
250+
else if (shim.toolIs(tool_name, "supabase_insert"))
251+
"{\"result\":{\"status\":\"stub\"}}"
252+
else if (shim.toolIs(tool_name, "supabase_update"))
253+
"{\"result\":{\"status\":\"stub\"}}"
254+
else if (shim.toolIs(tool_name, "supabase_delete"))
255+
"{\"result\":{\"status\":\"stub\"}}"
256+
else if (shim.toolIs(tool_name, "supabase_storage_list"))
257+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
258+
else if (shim.toolIs(tool_name, "supabase_disconnect"))
259+
"{\"result\":{\"status\":\"stub\"}}"
260+
else
261+
return shim.RC_UNKNOWN_TOOL;
262+
263+
return shim.writeResult(out_buf, in_out_len, body);
264+
}
265+
213266
// Tests
214267
// ---------------------------------------------------------------------------
215268

@@ -292,3 +345,50 @@ test "action requires connection" {
292345
try std.testing.expectEqual(@as(c_int, 0), supabase_mcp_action_requires_connection(0)); // list_projects
293346
try std.testing.expectEqual(@as(c_int, 0), supabase_mcp_action_requires_connection(99)); // out of range
294347
}
348+
349+
// ═══════════════════════════════════════════════════════════════════════
350+
// ADR-0006 invoke dispatch tests
351+
// ═══════════════════════════════════════════════════════════════════════
352+
353+
test "boj_cartridge_name returns supabase-mcp" {
354+
const n = std.mem.span(boj_cartridge_name());
355+
try std.testing.expectEqualStrings("supabase-mcp", n);
356+
}
357+
358+
test "boj_cartridge_init returns 0" {
359+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
360+
}
361+
362+
test "invoke: each declared tool succeeds" {
363+
var buf: [256]u8 = undefined;
364+
const tools = [_][]const u8{
365+
"supabase_connect",
366+
"supabase_query",
367+
"supabase_insert",
368+
"supabase_update",
369+
"supabase_delete",
370+
"supabase_storage_list",
371+
"supabase_disconnect",
372+
};
373+
for (tools) |t| {
374+
var len: usize = buf.len;
375+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
376+
try std.testing.expectEqual(@as(i32, 0), rc);
377+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
378+
}
379+
}
380+
381+
test "invoke: unknown tool returns -1" {
382+
var buf: [64]u8 = undefined;
383+
var len: usize = buf.len;
384+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
385+
try std.testing.expectEqual(@as(i32, -1), rc);
386+
}
387+
388+
test "invoke: buffer too small returns -3" {
389+
var buf: [4]u8 = undefined;
390+
var len: usize = buf.len;
391+
const rc = boj_cartridge_invoke("supabase_connect", "{}", &buf, &len);
392+
try std.testing.expectEqual(@as(i32, -3), rc);
393+
try std.testing.expect(len > 4);
394+
}

cartridges/telegram-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("telegram_mcp", .{
1224
.root_source_file = b.path("telegram_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 = "telegram_mcp",

0 commit comments

Comments
 (0)