Skip to content

Commit 47e9c11

Browse files
hyperpolymathclaude
andcommitted
feat(adr-0006): migrate 5 cartridges to 5-symbol ABI (Pattern B batch 2)
Pattern B migration (buildkite-mcp,burble-admin-mcp,circleci-mcp,civic-connect-mcp,claude-ai-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 a100436 commit 47e9c11

10 files changed

Lines changed: 570 additions & 0 deletions

File tree

cartridges/buildkite-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("buildkite_mcp", .{
1123
.root_source_file = b.path("buildkite_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 = "buildkite_mcp",
1832
.root_module = ffi_mod,

cartridges/buildkite-mcp/ffi/buildkite_mcp_ffi.zig

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,65 @@ pub export fn buildkite_mcp_reset() void {
220220
sessions = .{SessionSlot{}} ** MAX_SESSIONS;
221221
}
222222

223+
// ═══════════════════════════════════════════════════════════════════════
224+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
225+
// ═══════════════════════════════════════════════════════════════════════
226+
227+
const shim = @import("cartridge_shim");
228+
229+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "buildkite-mcp";
230+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
231+
232+
export fn boj_cartridge_init() callconv(.c) c_int {
233+
return 0;
234+
}
235+
236+
export fn boj_cartridge_deinit() callconv(.c) void {}
237+
238+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
239+
return CARTRIDGE_NAME_PTR;
240+
}
241+
242+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
243+
return CARTRIDGE_VERSION_PTR;
244+
}
245+
246+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
247+
export fn boj_cartridge_invoke(
248+
tool_name: [*c]const u8,
249+
json_args: [*c]const u8,
250+
out_buf: [*c]u8,
251+
in_out_len: [*c]usize,
252+
) callconv(.c) i32 {
253+
_ = json_args;
254+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
255+
256+
const body: []const u8 = if (shim.toolIs(tool_name, "buildkite_list_pipelines"))
257+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
258+
else if (shim.toolIs(tool_name, "buildkite_get_pipeline"))
259+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
260+
else if (shim.toolIs(tool_name, "buildkite_list_builds"))
261+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
262+
else if (shim.toolIs(tool_name, "buildkite_get_build"))
263+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
264+
else if (shim.toolIs(tool_name, "buildkite_create_build"))
265+
"{\"result\":{\"status\":\"stub\"}}"
266+
else if (shim.toolIs(tool_name, "buildkite_cancel_build"))
267+
"{\"result\":{\"status\":\"stub\"}}"
268+
else if (shim.toolIs(tool_name, "buildkite_list_jobs"))
269+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
270+
else if (shim.toolIs(tool_name, "buildkite_get_job_log"))
271+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
272+
else if (shim.toolIs(tool_name, "buildkite_list_artifacts"))
273+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
274+
else if (shim.toolIs(tool_name, "buildkite_list_agents"))
275+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
276+
else
277+
return shim.RC_UNKNOWN_TOOL;
278+
279+
return shim.writeResult(out_buf, in_out_len, body);
280+
}
281+
223282
test "authenticated session lifecycle" {
224283
buildkite_mcp_reset();
225284
const slot = buildkite_mcp_authenticate(0);
@@ -270,3 +329,53 @@ test "slot exhaustion" {
270329
try std.testing.expectEqual(@as(c_int, 0), buildkite_mcp_close(slots[0]));
271330
try std.testing.expect(buildkite_mcp_authenticate(0) >= 0);
272331
}
332+
333+
// ═══════════════════════════════════════════════════════════════════════
334+
// ADR-0006 invoke dispatch tests
335+
// ═══════════════════════════════════════════════════════════════════════
336+
337+
test "boj_cartridge_name returns buildkite-mcp" {
338+
const n = std.mem.span(boj_cartridge_name());
339+
try std.testing.expectEqualStrings("buildkite-mcp", n);
340+
}
341+
342+
test "boj_cartridge_init returns 0" {
343+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
344+
}
345+
346+
test "invoke: each declared tool succeeds" {
347+
var buf: [256]u8 = undefined;
348+
const tools = [_][]const u8{
349+
"buildkite_list_pipelines",
350+
"buildkite_get_pipeline",
351+
"buildkite_list_builds",
352+
"buildkite_get_build",
353+
"buildkite_create_build",
354+
"buildkite_cancel_build",
355+
"buildkite_list_jobs",
356+
"buildkite_get_job_log",
357+
"buildkite_list_artifacts",
358+
"buildkite_list_agents",
359+
};
360+
for (tools) |t| {
361+
var len: usize = buf.len;
362+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
363+
try std.testing.expectEqual(@as(i32, 0), rc);
364+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
365+
}
366+
}
367+
368+
test "invoke: unknown tool returns -1" {
369+
var buf: [64]u8 = undefined;
370+
var len: usize = buf.len;
371+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
372+
try std.testing.expectEqual(@as(i32, -1), rc);
373+
}
374+
375+
test "invoke: buffer too small returns -3" {
376+
var buf: [4]u8 = undefined;
377+
var len: usize = buf.len;
378+
const rc = boj_cartridge_invoke("buildkite_list_pipelines", "{}", &buf, &len);
379+
try std.testing.expectEqual(@as(i32, -3), rc);
380+
try std.testing.expect(len > 4);
381+
}

cartridges/burble-admin-mcp/ffi/build.zig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,25 @@ 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+
10+
const shim_mod = b.addModule("cartridge_shim", .{
11+
12+
.root_source_file = b.path("../../../ffi/zig/src/cartridge_shim.zig"),
13+
14+
.target = target,
15+
16+
.optimize = optimize,
17+
18+
});
19+
820
const ffi_mod = b.addModule("burble_admin_ffi", .{
921
.root_source_file = b.path("burble_admin_ffi.zig"),
1022
.target = target,
1123
.optimize = optimize,
1224
});
25+
26+
ffi_mod.addImport("cartridge_shim", shim_mod);
1327
const lib = b.addLibrary(.{
1428
.name = "burble_admin_ffi",
1529
.root_module = ffi_mod,

cartridges/burble-admin-mcp/ffi/burble_admin_ffi.zig

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,65 @@ pub export fn burble_admin_clamp_capacity(requested: i32) i32 {
6262
return requested;
6363
}
6464

65+
// ═══════════════════════════════════════════════════════════════════════
66+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
67+
// ═══════════════════════════════════════════════════════════════════════
68+
69+
const shim = @import("cartridge_shim");
70+
71+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "burble-admin-mcp";
72+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
73+
74+
export fn boj_cartridge_init() callconv(.c) c_int {
75+
return 0;
76+
}
77+
78+
export fn boj_cartridge_deinit() callconv(.c) void {}
79+
80+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
81+
return CARTRIDGE_NAME_PTR;
82+
}
83+
84+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
85+
return CARTRIDGE_VERSION_PTR;
86+
}
87+
88+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
89+
export fn boj_cartridge_invoke(
90+
tool_name: [*c]const u8,
91+
json_args: [*c]const u8,
92+
out_buf: [*c]u8,
93+
in_out_len: [*c]usize,
94+
) callconv(.c) i32 {
95+
_ = json_args;
96+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
97+
98+
const body: []const u8 = if (shim.toolIs(tool_name, "burble_check_health"))
99+
"{\"result\":{\"health\":\"healthy\",\"status\":\"stub\"}}"
100+
else if (shim.toolIs(tool_name, "burble_list_rooms"))
101+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
102+
else if (shim.toolIs(tool_name, "burble_create_room"))
103+
"{\"result\":{\"status\":\"stub\"}}"
104+
else if (shim.toolIs(tool_name, "burble_close_room"))
105+
"{\"result\":{\"status\":\"stub\"}}"
106+
else if (shim.toolIs(tool_name, "burble_kick_user"))
107+
"{\"result\":{\"status\":\"stub\"}}"
108+
else if (shim.toolIs(tool_name, "burble_get_config"))
109+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
110+
else if (shim.toolIs(tool_name, "burble_update_config"))
111+
"{\"result\":{\"status\":\"stub\"}}"
112+
else if (shim.toolIs(tool_name, "burble_voice_stats"))
113+
"{\"result\":{\"status\":\"stub\"}}"
114+
else if (shim.toolIs(tool_name, "burble_toggle_recording"))
115+
"{\"result\":{\"status\":\"stub\"}}"
116+
else if (shim.toolIs(tool_name, "burble_node_status"))
117+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
118+
else
119+
return shim.RC_UNKNOWN_TOOL;
120+
121+
return shim.writeResult(out_buf, in_out_len, body);
122+
}
123+
65124
// ═══════════════════════════════════════════════════════════════════════
66125
// Tests
67126
// ═══════════════════════════════════════════════════════════════════════
@@ -95,3 +154,53 @@ test "capacity clamping" {
95154
try std.testing.expectEqual(@as(i32, 50), burble_admin_clamp_capacity(50));
96155
try std.testing.expectEqual(@as(i32, 500), burble_admin_clamp_capacity(999));
97156
}
157+
158+
// ═══════════════════════════════════════════════════════════════════════
159+
// ADR-0006 invoke dispatch tests
160+
// ═══════════════════════════════════════════════════════════════════════
161+
162+
test "boj_cartridge_name returns burble-admin-mcp" {
163+
const n = std.mem.span(boj_cartridge_name());
164+
try std.testing.expectEqualStrings("burble-admin-mcp", n);
165+
}
166+
167+
test "boj_cartridge_init returns 0" {
168+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
169+
}
170+
171+
test "invoke: each declared tool succeeds" {
172+
var buf: [256]u8 = undefined;
173+
const tools = [_][]const u8{
174+
"burble_check_health",
175+
"burble_list_rooms",
176+
"burble_create_room",
177+
"burble_close_room",
178+
"burble_kick_user",
179+
"burble_get_config",
180+
"burble_update_config",
181+
"burble_voice_stats",
182+
"burble_toggle_recording",
183+
"burble_node_status",
184+
};
185+
for (tools) |t| {
186+
var len: usize = buf.len;
187+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
188+
try std.testing.expectEqual(@as(i32, 0), rc);
189+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
190+
}
191+
}
192+
193+
test "invoke: unknown tool returns -1" {
194+
var buf: [64]u8 = undefined;
195+
var len: usize = buf.len;
196+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
197+
try std.testing.expectEqual(@as(i32, -1), rc);
198+
}
199+
200+
test "invoke: buffer too small returns -3" {
201+
var buf: [4]u8 = undefined;
202+
var len: usize = buf.len;
203+
const rc = boj_cartridge_invoke("burble_check_health", "{}", &buf, &len);
204+
try std.testing.expectEqual(@as(i32, -3), rc);
205+
try std.testing.expect(len > 4);
206+
}

cartridges/circleci-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("circleci_mcp", .{
1123
.root_source_file = b.path("circleci_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 = "circleci_mcp",
1832
.root_module = ffi_mod,

0 commit comments

Comments
 (0)