Skip to content

Commit 07fdb29

Browse files
hyperpolymathclaude
andcommitted
feat(adr-0006): migrate 5 cartridges to 5-symbol ABI (Pattern B batch 7)
Pattern B migration (hackage-mcp,hetzner-mcp,hex-mcp,idaptik-admin-mcp,jira-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 05074c7 commit 07fdb29

10 files changed

Lines changed: 645 additions & 0 deletions

File tree

cartridges/hackage-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("hackage_mcp", .{
1123
.root_source_file = b.path("hackage_mcp_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 = "hackage_mcp",
1832
.root_module = ffi_mod,

cartridges/hackage-mcp/ffi/hackage_mcp_ffi.zig

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,69 @@ pub export fn hackage_mcp_reset() void {
304304
}
305305

306306
// ---------------------------------------------------------------------------
307+
// ═══════════════════════════════════════════════════════════════════════
308+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
309+
// ═══════════════════════════════════════════════════════════════════════
310+
311+
const shim = @import("cartridge_shim");
312+
313+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "hackage-mcp";
314+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
315+
316+
export fn boj_cartridge_init() callconv(.c) c_int {
317+
return 0;
318+
}
319+
320+
export fn boj_cartridge_deinit() callconv(.c) void {}
321+
322+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
323+
return CARTRIDGE_NAME_PTR;
324+
}
325+
326+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
327+
return CARTRIDGE_VERSION_PTR;
328+
}
329+
330+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
331+
export fn boj_cartridge_invoke(
332+
tool_name: [*c]const u8,
333+
json_args: [*c]const u8,
334+
out_buf: [*c]u8,
335+
in_out_len: [*c]usize,
336+
) callconv(.c) i32 {
337+
_ = json_args;
338+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
339+
340+
const body: []const u8 = if (shim.toolIs(tool_name, "hackage_search_packages"))
341+
"{\"result\":{\"matches\":[],\"status\":\"stub\"}}"
342+
else if (shim.toolIs(tool_name, "hackage_get_package"))
343+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
344+
else if (shim.toolIs(tool_name, "hackage_get_version"))
345+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
346+
else if (shim.toolIs(tool_name, "hackage_list_versions"))
347+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
348+
else if (shim.toolIs(tool_name, "hackage_get_downloads"))
349+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
350+
else if (shim.toolIs(tool_name, "hackage_get_dependencies"))
351+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
352+
else if (shim.toolIs(tool_name, "hackage_get_reverse_dependencies"))
353+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
354+
else if (shim.toolIs(tool_name, "hackage_get_maintainers"))
355+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
356+
else if (shim.toolIs(tool_name, "hackage_get_deprecated"))
357+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
358+
else if (shim.toolIs(tool_name, "hackage_get_cabal_file"))
359+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
360+
else if (shim.toolIs(tool_name, "hackage_list_all_packages"))
361+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
362+
else if (shim.toolIs(tool_name, "hackage_get_user"))
363+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
364+
else
365+
return shim.RC_UNKNOWN_TOOL;
366+
367+
return shim.writeResult(out_buf, in_out_len, body);
368+
}
369+
307370
// Tests
308371
// ---------------------------------------------------------------------------
309372

@@ -403,3 +466,55 @@ test "slot exhaustion" {
403466
const new_slot = hackage_mcp_authenticate(0);
404467
try std.testing.expect(new_slot >= 0);
405468
}
469+
470+
// ═══════════════════════════════════════════════════════════════════════
471+
// ADR-0006 invoke dispatch tests
472+
// ═══════════════════════════════════════════════════════════════════════
473+
474+
test "boj_cartridge_name returns hackage-mcp" {
475+
const n = std.mem.span(boj_cartridge_name());
476+
try std.testing.expectEqualStrings("hackage-mcp", n);
477+
}
478+
479+
test "boj_cartridge_init returns 0" {
480+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
481+
}
482+
483+
test "invoke: each declared tool succeeds" {
484+
var buf: [256]u8 = undefined;
485+
const tools = [_][]const u8{
486+
"hackage_search_packages",
487+
"hackage_get_package",
488+
"hackage_get_version",
489+
"hackage_list_versions",
490+
"hackage_get_downloads",
491+
"hackage_get_dependencies",
492+
"hackage_get_reverse_dependencies",
493+
"hackage_get_maintainers",
494+
"hackage_get_deprecated",
495+
"hackage_get_cabal_file",
496+
"hackage_list_all_packages",
497+
"hackage_get_user",
498+
};
499+
for (tools) |t| {
500+
var len: usize = buf.len;
501+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
502+
try std.testing.expectEqual(@as(i32, 0), rc);
503+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
504+
}
505+
}
506+
507+
test "invoke: unknown tool returns -1" {
508+
var buf: [64]u8 = undefined;
509+
var len: usize = buf.len;
510+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
511+
try std.testing.expectEqual(@as(i32, -1), rc);
512+
}
513+
514+
test "invoke: buffer too small returns -3" {
515+
var buf: [4]u8 = undefined;
516+
var len: usize = buf.len;
517+
const rc = boj_cartridge_invoke("hackage_search_packages", "{}", &buf, &len);
518+
try std.testing.expectEqual(@as(i32, -3), rc);
519+
try std.testing.expect(len > 4);
520+
}

cartridges/hetzner-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("hetzner_mcp", .{
1426
.root_source_file = b.path("hetzner_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 = "hetzner_mcp",

cartridges/hetzner-mcp/ffi/hetzner_mcp_ffi.zig

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,85 @@ pub export fn hetzner_mcp_reset() void {
324324
}
325325

326326
// ---------------------------------------------------------------------------
327+
// ═══════════════════════════════════════════════════════════════════════
328+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
329+
// ═══════════════════════════════════════════════════════════════════════
330+
331+
const shim = @import("cartridge_shim");
332+
333+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "hetzner-mcp";
334+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
335+
336+
export fn boj_cartridge_init() callconv(.c) c_int {
337+
return 0;
338+
}
339+
340+
export fn boj_cartridge_deinit() callconv(.c) void {}
341+
342+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
343+
return CARTRIDGE_NAME_PTR;
344+
}
345+
346+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
347+
return CARTRIDGE_VERSION_PTR;
348+
}
349+
350+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
351+
export fn boj_cartridge_invoke(
352+
tool_name: [*c]const u8,
353+
json_args: [*c]const u8,
354+
out_buf: [*c]u8,
355+
in_out_len: [*c]usize,
356+
) callconv(.c) i32 {
357+
_ = json_args;
358+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
359+
360+
const body: []const u8 = if (shim.toolIs(tool_name, "hetzner_list_servers"))
361+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
362+
else if (shim.toolIs(tool_name, "hetzner_get_server"))
363+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
364+
else if (shim.toolIs(tool_name, "hetzner_create_server"))
365+
"{\"result\":{\"status\":\"stub\"}}"
366+
else if (shim.toolIs(tool_name, "hetzner_delete_server"))
367+
"{\"result\":{\"status\":\"stub\"}}"
368+
else if (shim.toolIs(tool_name, "hetzner_server_action"))
369+
"{\"result\":{\"status\":\"stub\"}}"
370+
else if (shim.toolIs(tool_name, "hetzner_resize_server"))
371+
"{\"result\":{\"status\":\"stub\"}}"
372+
else if (shim.toolIs(tool_name, "hetzner_list_floating_ips"))
373+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
374+
else if (shim.toolIs(tool_name, "hetzner_create_floating_ip"))
375+
"{\"result\":{\"status\":\"stub\"}}"
376+
else if (shim.toolIs(tool_name, "hetzner_list_volumes"))
377+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
378+
else if (shim.toolIs(tool_name, "hetzner_create_volume"))
379+
"{\"result\":{\"status\":\"stub\"}}"
380+
else if (shim.toolIs(tool_name, "hetzner_list_firewalls"))
381+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
382+
else if (shim.toolIs(tool_name, "hetzner_create_firewall"))
383+
"{\"result\":{\"status\":\"stub\"}}"
384+
else if (shim.toolIs(tool_name, "hetzner_list_ssh_keys"))
385+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
386+
else if (shim.toolIs(tool_name, "hetzner_create_ssh_key"))
387+
"{\"result\":{\"status\":\"stub\"}}"
388+
else if (shim.toolIs(tool_name, "hetzner_list_images"))
389+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
390+
else if (shim.toolIs(tool_name, "hetzner_create_snapshot"))
391+
"{\"result\":{\"status\":\"stub\"}}"
392+
else if (shim.toolIs(tool_name, "hetzner_list_networks"))
393+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
394+
else if (shim.toolIs(tool_name, "hetzner_create_network"))
395+
"{\"result\":{\"status\":\"stub\"}}"
396+
else if (shim.toolIs(tool_name, "hetzner_list_load_balancers"))
397+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
398+
else if (shim.toolIs(tool_name, "hetzner_create_load_balancer"))
399+
"{\"result\":{\"status\":\"stub\"}}"
400+
else
401+
return shim.RC_UNKNOWN_TOOL;
402+
403+
return shim.writeResult(out_buf, in_out_len, body);
404+
}
405+
327406
// Tests
328407
// ---------------------------------------------------------------------------
329408

@@ -443,3 +522,63 @@ test "slot exhaustion" {
443522
const new_slot = hetzner_mcp_authenticate(3600);
444523
try std.testing.expect(new_slot >= 0);
445524
}
525+
526+
// ═══════════════════════════════════════════════════════════════════════
527+
// ADR-0006 invoke dispatch tests
528+
// ═══════════════════════════════════════════════════════════════════════
529+
530+
test "boj_cartridge_name returns hetzner-mcp" {
531+
const n = std.mem.span(boj_cartridge_name());
532+
try std.testing.expectEqualStrings("hetzner-mcp", n);
533+
}
534+
535+
test "boj_cartridge_init returns 0" {
536+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
537+
}
538+
539+
test "invoke: each declared tool succeeds" {
540+
var buf: [256]u8 = undefined;
541+
const tools = [_][]const u8{
542+
"hetzner_list_servers",
543+
"hetzner_get_server",
544+
"hetzner_create_server",
545+
"hetzner_delete_server",
546+
"hetzner_server_action",
547+
"hetzner_resize_server",
548+
"hetzner_list_floating_ips",
549+
"hetzner_create_floating_ip",
550+
"hetzner_list_volumes",
551+
"hetzner_create_volume",
552+
"hetzner_list_firewalls",
553+
"hetzner_create_firewall",
554+
"hetzner_list_ssh_keys",
555+
"hetzner_create_ssh_key",
556+
"hetzner_list_images",
557+
"hetzner_create_snapshot",
558+
"hetzner_list_networks",
559+
"hetzner_create_network",
560+
"hetzner_list_load_balancers",
561+
"hetzner_create_load_balancer",
562+
};
563+
for (tools) |t| {
564+
var len: usize = buf.len;
565+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
566+
try std.testing.expectEqual(@as(i32, 0), rc);
567+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
568+
}
569+
}
570+
571+
test "invoke: unknown tool returns -1" {
572+
var buf: [64]u8 = undefined;
573+
var len: usize = buf.len;
574+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
575+
try std.testing.expectEqual(@as(i32, -1), rc);
576+
}
577+
578+
test "invoke: buffer too small returns -3" {
579+
var buf: [4]u8 = undefined;
580+
var len: usize = buf.len;
581+
const rc = boj_cartridge_invoke("hetzner_list_servers", "{}", &buf, &len);
582+
try std.testing.expectEqual(@as(i32, -3), rc);
583+
try std.testing.expect(len > 4);
584+
}

cartridges/hex-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("hex_mcp", .{
1123
.root_source_file = b.path("hex_mcp_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 = "hex_mcp",
1832
.root_module = ffi_mod,

0 commit comments

Comments
 (0)