Skip to content

Commit c38bd89

Browse files
hyperpolymathclaude
andcommitted
feat(adr-0006): migrate 5 cartridges to 5-symbol ABI (Pattern B batch 4)
Pattern B migration (discord-mcp,dns-shield-mcp,docker-hub-mcp,duckdb-mcp,echidna-llm-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 b50477b commit c38bd89

10 files changed

Lines changed: 592 additions & 12 deletions

File tree

cartridges/discord-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("discord_mcp", .{
1224
.root_source_file = b.path("discord_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 = "discord_mcp",

cartridges/discord-mcp/ffi/discord_mcp_ffi.zig

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,59 @@ pub export fn discord_mcp_reset() void {
267267
}
268268

269269
// ---------------------------------------------------------------------------
270+
// ═══════════════════════════════════════════════════════════════════════
271+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
272+
// ═══════════════════════════════════════════════════════════════════════
273+
274+
const shim = @import("cartridge_shim");
275+
276+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "discord-mcp";
277+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
278+
279+
export fn boj_cartridge_init() callconv(.c) c_int {
280+
return 0;
281+
}
282+
283+
export fn boj_cartridge_deinit() callconv(.c) void {}
284+
285+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
286+
return CARTRIDGE_NAME_PTR;
287+
}
288+
289+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
290+
return CARTRIDGE_VERSION_PTR;
291+
}
292+
293+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
294+
export fn boj_cartridge_invoke(
295+
tool_name: [*c]const u8,
296+
json_args: [*c]const u8,
297+
out_buf: [*c]u8,
298+
in_out_len: [*c]usize,
299+
) callconv(.c) i32 {
300+
_ = json_args;
301+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
302+
303+
const body: []const u8 = if (shim.toolIs(tool_name, "discord_authenticate"))
304+
"{\"result\":{\"status\":\"stub\"}}"
305+
else if (shim.toolIs(tool_name, "discord_send_message"))
306+
"{\"result\":{\"status\":\"stub\"}}"
307+
else if (shim.toolIs(tool_name, "discord_list_guilds"))
308+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
309+
else if (shim.toolIs(tool_name, "discord_list_channels"))
310+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
311+
else if (shim.toolIs(tool_name, "discord_read_messages"))
312+
"{\"result\":{\"status\":\"stub\"}}"
313+
else if (shim.toolIs(tool_name, "discord_add_reaction"))
314+
"{\"result\":{\"status\":\"stub\"}}"
315+
else if (shim.toolIs(tool_name, "discord_deauthenticate"))
316+
"{\"result\":{\"status\":\"stub\"}}"
317+
else
318+
return shim.RC_UNKNOWN_TOOL;
319+
320+
return shim.writeResult(out_buf, in_out_len, body);
321+
}
322+
270323
// Tests
271324
// ---------------------------------------------------------------------------
272325

@@ -372,3 +425,50 @@ test "slot exhaustion" {
372425
const new_slot = discord_mcp_session_open();
373426
try std.testing.expect(new_slot >= 0);
374427
}
428+
429+
// ═══════════════════════════════════════════════════════════════════════
430+
// ADR-0006 invoke dispatch tests
431+
// ═══════════════════════════════════════════════════════════════════════
432+
433+
test "boj_cartridge_name returns discord-mcp" {
434+
const n = std.mem.span(boj_cartridge_name());
435+
try std.testing.expectEqualStrings("discord-mcp", n);
436+
}
437+
438+
test "boj_cartridge_init returns 0" {
439+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
440+
}
441+
442+
test "invoke: each declared tool succeeds" {
443+
var buf: [256]u8 = undefined;
444+
const tools = [_][]const u8{
445+
"discord_authenticate",
446+
"discord_send_message",
447+
"discord_list_guilds",
448+
"discord_list_channels",
449+
"discord_read_messages",
450+
"discord_add_reaction",
451+
"discord_deauthenticate",
452+
};
453+
for (tools) |t| {
454+
var len: usize = buf.len;
455+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
456+
try std.testing.expectEqual(@as(i32, 0), rc);
457+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
458+
}
459+
}
460+
461+
test "invoke: unknown tool returns -1" {
462+
var buf: [64]u8 = undefined;
463+
var len: usize = buf.len;
464+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
465+
try std.testing.expectEqual(@as(i32, -1), rc);
466+
}
467+
468+
test "invoke: buffer too small returns -3" {
469+
var buf: [4]u8 = undefined;
470+
var len: usize = buf.len;
471+
const rc = boj_cartridge_invoke("discord_authenticate", "{}", &buf, &len);
472+
try std.testing.expectEqual(@as(i32, -3), rc);
473+
try std.testing.expect(len > 4);
474+
}

cartridges/dns-shield-mcp/ffi/build.zig

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

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

28+
ffi_mod.addImport("cartridge_shim", shim_mod);
29+
1630
const lib = b.addLibrary(.{
1731
.name = "dns_shield_mcp",
1832
.root_module = ffi_mod,

cartridges/dns-shield-mcp/ffi/dns_shield_ffi.zig

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,55 @@ fn resolve_encrypted(
133133
}
134134

135135
// ═══════════════════════════════════════════════════════════════════════════
136+
// ═══════════════════════════════════════════════════════════════════════
137+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
138+
// ═══════════════════════════════════════════════════════════════════════
139+
140+
const shim = @import("cartridge_shim");
141+
142+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "dns-shield-mcp";
143+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
144+
145+
export fn boj_cartridge_init() callconv(.c) c_int {
146+
return 0;
147+
}
148+
149+
export fn boj_cartridge_deinit() callconv(.c) void {}
150+
151+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
152+
return CARTRIDGE_NAME_PTR;
153+
}
154+
155+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
156+
return CARTRIDGE_VERSION_PTR;
157+
}
158+
159+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
160+
export fn boj_cartridge_invoke(
161+
tool_name: [*c]const u8,
162+
json_args: [*c]const u8,
163+
out_buf: [*c]u8,
164+
in_out_len: [*c]usize,
165+
) callconv(.c) i32 {
166+
_ = json_args;
167+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
168+
169+
const body: []const u8 = if (shim.toolIs(tool_name, "dns_resolve_doq"))
170+
"{\"result\":{\"status\":\"stub\"}}"
171+
else if (shim.toolIs(tool_name, "dns_resolve_doh"))
172+
"{\"result\":{\"status\":\"stub\"}}"
173+
else if (shim.toolIs(tool_name, "dns_check_caa"))
174+
"{\"result\":{\"status\":\"stub\"}}"
175+
else if (shim.toolIs(tool_name, "dns_validate_dnssec"))
176+
"{\"result\":{\"status\":\"stub\"}}"
177+
else if (shim.toolIs(tool_name, "dns_flush_cache"))
178+
"{\"result\":{\"status\":\"stub\"}}"
179+
else
180+
return shim.RC_UNKNOWN_TOOL;
181+
182+
return shim.writeResult(out_buf, in_out_len, body);
183+
}
184+
136185
// Tests
137186
// ═══════════════════════════════════════════════════════════════════════════
138187

@@ -158,3 +207,48 @@ test "version returns 0.5.0" {
158207
const ver = dns_shield_version();
159208
try std.testing.expectEqualStrings("0.5.0", std.mem.span(ver));
160209
}
210+
211+
// ═══════════════════════════════════════════════════════════════════════
212+
// ADR-0006 invoke dispatch tests
213+
// ═══════════════════════════════════════════════════════════════════════
214+
215+
test "boj_cartridge_name returns dns-shield-mcp" {
216+
const n = std.mem.span(boj_cartridge_name());
217+
try std.testing.expectEqualStrings("dns-shield-mcp", n);
218+
}
219+
220+
test "boj_cartridge_init returns 0" {
221+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
222+
}
223+
224+
test "invoke: each declared tool succeeds" {
225+
var buf: [256]u8 = undefined;
226+
const tools = [_][]const u8{
227+
"dns_resolve_doq",
228+
"dns_resolve_doh",
229+
"dns_check_caa",
230+
"dns_validate_dnssec",
231+
"dns_flush_cache",
232+
};
233+
for (tools) |t| {
234+
var len: usize = buf.len;
235+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
236+
try std.testing.expectEqual(@as(i32, 0), rc);
237+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
238+
}
239+
}
240+
241+
test "invoke: unknown tool returns -1" {
242+
var buf: [64]u8 = undefined;
243+
var len: usize = buf.len;
244+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
245+
try std.testing.expectEqual(@as(i32, -1), rc);
246+
}
247+
248+
test "invoke: buffer too small returns -3" {
249+
var buf: [4]u8 = undefined;
250+
var len: usize = buf.len;
251+
const rc = boj_cartridge_invoke("dns_resolve_doq", "{}", &buf, &len);
252+
try std.testing.expectEqual(@as(i32, -3), rc);
253+
try std.testing.expect(len > 4);
254+
}

cartridges/docker-hub-mcp/ffi/build.zig

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

1212
// Module
13+
// Shared ADR-0006 invoke-shim module (relative path up to boj-server trunk).
14+
15+
const shim_mod = b.addModule("cartridge_shim", .{
16+
17+
.root_source_file = b.path("../../../ffi/zig/src/cartridge_shim.zig"),
18+
19+
.target = target,
20+
21+
.optimize = optimize,
22+
23+
});
24+
1325
const ffi_mod = b.addModule("docker_hub_mcp", .{
1426
.root_source_file = b.path("docker_hub_mcp_ffi.zig"),
1527
.target = target,
1628
.optimize = optimize,
1729
});
1830

31+
ffi_mod.addImport("cartridge_shim", shim_mod);
32+
1933
// Shared library
2034
const lib = b.addLibrary(.{
2135
.name = "docker_hub_mcp",

0 commit comments

Comments
 (0)