Skip to content

Commit 2b229c3

Browse files
hyperpolymathclaude
andcommitted
feat(adr-0006): migrate 5 cartridges to 5-symbol ABI (Pattern B batch 5)
Pattern B migration (fly-mcp,game-admin-mcp,gcp-mcp,github-actions-mcp,github-api-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 c38bd89 commit 2b229c3

10 files changed

Lines changed: 630 additions & 0 deletions

File tree

cartridges/fly-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("fly_mcp", .{
1224
.root_source_file = b.path("fly_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 = "fly_mcp",

cartridges/fly-mcp/ffi/fly_mcp_ffi.zig

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,87 @@ pub export fn fly_mcp_reset() void {
257257
}
258258

259259
// ---------------------------------------------------------------------------
260+
// ═══════════════════════════════════════════════════════════════════════
261+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
262+
// ═══════════════════════════════════════════════════════════════════════
263+
264+
const shim = @import("cartridge_shim");
265+
266+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "fly-mcp";
267+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
268+
269+
export fn boj_cartridge_init() callconv(.c) c_int {
270+
return 0;
271+
}
272+
273+
export fn boj_cartridge_deinit() callconv(.c) void {}
274+
275+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
276+
return CARTRIDGE_NAME_PTR;
277+
}
278+
279+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
280+
return CARTRIDGE_VERSION_PTR;
281+
}
282+
283+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
284+
export fn boj_cartridge_invoke(
285+
tool_name: [*c]const u8,
286+
json_args: [*c]const u8,
287+
out_buf: [*c]u8,
288+
in_out_len: [*c]usize,
289+
) callconv(.c) i32 {
290+
_ = json_args;
291+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
292+
293+
const body: []const u8 = if (shim.toolIs(tool_name, "fly_list_apps"))
294+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
295+
else if (shim.toolIs(tool_name, "fly_get_app"))
296+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
297+
else if (shim.toolIs(tool_name, "fly_create_app"))
298+
"{\"result\":{\"status\":\"stub\"}}"
299+
else if (shim.toolIs(tool_name, "fly_destroy_app"))
300+
"{\"result\":{\"status\":\"stub\"}}"
301+
else if (shim.toolIs(tool_name, "fly_list_machines"))
302+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
303+
else if (shim.toolIs(tool_name, "fly_get_machine"))
304+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
305+
else if (shim.toolIs(tool_name, "fly_create_machine"))
306+
"{\"result\":{\"status\":\"stub\"}}"
307+
else if (shim.toolIs(tool_name, "fly_start_machine"))
308+
"{\"result\":{\"status\":\"stub\"}}"
309+
else if (shim.toolIs(tool_name, "fly_stop_machine"))
310+
"{\"result\":{\"status\":\"stub\"}}"
311+
else if (shim.toolIs(tool_name, "fly_restart_machine"))
312+
"{\"result\":{\"status\":\"stub\"}}"
313+
else if (shim.toolIs(tool_name, "fly_destroy_machine"))
314+
"{\"result\":{\"status\":\"stub\"}}"
315+
else if (shim.toolIs(tool_name, "fly_list_volumes"))
316+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
317+
else if (shim.toolIs(tool_name, "fly_create_volume"))
318+
"{\"result\":{\"status\":\"stub\"}}"
319+
else if (shim.toolIs(tool_name, "fly_list_secrets"))
320+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
321+
else if (shim.toolIs(tool_name, "fly_set_secrets"))
322+
"{\"result\":{\"status\":\"stub\"}}"
323+
else if (shim.toolIs(tool_name, "fly_delete_secret"))
324+
"{\"result\":{\"status\":\"stub\"}}"
325+
else if (shim.toolIs(tool_name, "fly_list_certificates"))
326+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
327+
else if (shim.toolIs(tool_name, "fly_add_certificate"))
328+
"{\"result\":{\"status\":\"stub\"}}"
329+
else if (shim.toolIs(tool_name, "fly_list_regions"))
330+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
331+
else if (shim.toolIs(tool_name, "fly_allocate_ip"))
332+
"{\"result\":{\"status\":\"stub\"}}"
333+
else if (shim.toolIs(tool_name, "fly_release_ip"))
334+
"{\"result\":{\"status\":\"stub\"}}"
335+
else
336+
return shim.RC_UNKNOWN_TOOL;
337+
338+
return shim.writeResult(out_buf, in_out_len, body);
339+
}
340+
260341
// Tests
261342
// ---------------------------------------------------------------------------
262343

@@ -346,3 +427,64 @@ test "app and machine counters" {
346427
try std.testing.expectEqual(@as(c_int, 0), fly_mcp_set_machine_count(slot, 12));
347428
try std.testing.expectEqual(@as(c_int, 12), fly_mcp_machine_count(slot));
348429
}
430+
431+
// ═══════════════════════════════════════════════════════════════════════
432+
// ADR-0006 invoke dispatch tests
433+
// ═══════════════════════════════════════════════════════════════════════
434+
435+
test "boj_cartridge_name returns fly-mcp" {
436+
const n = std.mem.span(boj_cartridge_name());
437+
try std.testing.expectEqualStrings("fly-mcp", n);
438+
}
439+
440+
test "boj_cartridge_init returns 0" {
441+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
442+
}
443+
444+
test "invoke: each declared tool succeeds" {
445+
var buf: [256]u8 = undefined;
446+
const tools = [_][]const u8{
447+
"fly_list_apps",
448+
"fly_get_app",
449+
"fly_create_app",
450+
"fly_destroy_app",
451+
"fly_list_machines",
452+
"fly_get_machine",
453+
"fly_create_machine",
454+
"fly_start_machine",
455+
"fly_stop_machine",
456+
"fly_restart_machine",
457+
"fly_destroy_machine",
458+
"fly_list_volumes",
459+
"fly_create_volume",
460+
"fly_list_secrets",
461+
"fly_set_secrets",
462+
"fly_delete_secret",
463+
"fly_list_certificates",
464+
"fly_add_certificate",
465+
"fly_list_regions",
466+
"fly_allocate_ip",
467+
"fly_release_ip",
468+
};
469+
for (tools) |t| {
470+
var len: usize = buf.len;
471+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
472+
try std.testing.expectEqual(@as(i32, 0), rc);
473+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
474+
}
475+
}
476+
477+
test "invoke: unknown tool returns -1" {
478+
var buf: [64]u8 = undefined;
479+
var len: usize = buf.len;
480+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
481+
try std.testing.expectEqual(@as(i32, -1), rc);
482+
}
483+
484+
test "invoke: buffer too small returns -3" {
485+
var buf: [4]u8 = undefined;
486+
var len: usize = buf.len;
487+
const rc = boj_cartridge_invoke("fly_list_apps", "{}", &buf, &len);
488+
try std.testing.expectEqual(@as(i32, -3), rc);
489+
try std.testing.expect(len > 4);
490+
}

cartridges/game-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("game_admin_ffi", .{
921
.root_source_file = b.path("game_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 = "game_admin_ffi",
1529
.root_module = ffi_mod,

cartridges/game-admin-mcp/ffi/game_admin_ffi.zig

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,59 @@ pub export fn game_admin_is_readonly(op: i32) i32 {
4747
};
4848
}
4949

50+
// ═══════════════════════════════════════════════════════════════════════
51+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
52+
// ═══════════════════════════════════════════════════════════════════════
53+
54+
const shim = @import("cartridge_shim");
55+
56+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "game-admin-mcp";
57+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
58+
59+
export fn boj_cartridge_init() callconv(.c) c_int {
60+
return 0;
61+
}
62+
63+
export fn boj_cartridge_deinit() callconv(.c) void {}
64+
65+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
66+
return CARTRIDGE_NAME_PTR;
67+
}
68+
69+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
70+
return CARTRIDGE_VERSION_PTR;
71+
}
72+
73+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
74+
export fn boj_cartridge_invoke(
75+
tool_name: [*c]const u8,
76+
json_args: [*c]const u8,
77+
out_buf: [*c]u8,
78+
in_out_len: [*c]usize,
79+
) callconv(.c) i32 {
80+
_ = json_args;
81+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
82+
83+
const body: []const u8 = if (shim.toolIs(tool_name, "game_probe_server"))
84+
"{\"result\":{\"status\":\"stub\"}}"
85+
else if (shim.toolIs(tool_name, "game_list_servers"))
86+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
87+
else if (shim.toolIs(tool_name, "game_get_config"))
88+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
89+
else if (shim.toolIs(tool_name, "game_set_config"))
90+
"{\"result\":{\"status\":\"stub\"}}"
91+
else if (shim.toolIs(tool_name, "game_server_action"))
92+
"{\"result\":{\"status\":\"stub\"}}"
93+
else if (shim.toolIs(tool_name, "game_drift_status"))
94+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
95+
else if (shim.toolIs(tool_name, "game_list_profiles"))
96+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
97+
else
98+
return shim.RC_UNKNOWN_TOOL;
99+
100+
return shim.writeResult(out_buf, in_out_len, body);
101+
}
102+
50103
test "permission levels match ABI" {
51104
try std.testing.expectEqual(@as(i32, 0), game_admin_min_perm(0));
52105
try std.testing.expectEqual(@as(i32, 1), game_admin_min_perm(2));
@@ -60,3 +113,50 @@ test "readonly operations" {
60113
try std.testing.expectEqual(@as(i32, 0), game_admin_is_readonly(5));
61114
try std.testing.expectEqual(@as(i32, 1), game_admin_is_readonly(7));
62115
}
116+
117+
// ═══════════════════════════════════════════════════════════════════════
118+
// ADR-0006 invoke dispatch tests
119+
// ═══════════════════════════════════════════════════════════════════════
120+
121+
test "boj_cartridge_name returns game-admin-mcp" {
122+
const n = std.mem.span(boj_cartridge_name());
123+
try std.testing.expectEqualStrings("game-admin-mcp", n);
124+
}
125+
126+
test "boj_cartridge_init returns 0" {
127+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
128+
}
129+
130+
test "invoke: each declared tool succeeds" {
131+
var buf: [256]u8 = undefined;
132+
const tools = [_][]const u8{
133+
"game_probe_server",
134+
"game_list_servers",
135+
"game_get_config",
136+
"game_set_config",
137+
"game_server_action",
138+
"game_drift_status",
139+
"game_list_profiles",
140+
};
141+
for (tools) |t| {
142+
var len: usize = buf.len;
143+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
144+
try std.testing.expectEqual(@as(i32, 0), rc);
145+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
146+
}
147+
}
148+
149+
test "invoke: unknown tool returns -1" {
150+
var buf: [64]u8 = undefined;
151+
var len: usize = buf.len;
152+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
153+
try std.testing.expectEqual(@as(i32, -1), rc);
154+
}
155+
156+
test "invoke: buffer too small returns -3" {
157+
var buf: [4]u8 = undefined;
158+
var len: usize = buf.len;
159+
const rc = boj_cartridge_invoke("game_probe_server", "{}", &buf, &len);
160+
try std.testing.expectEqual(@as(i32, -3), rc);
161+
try std.testing.expect(len > 4);
162+
}

cartridges/gcp-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("gcp_mcp", .{
1426
.root_source_file = b.path("gcp_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 = "gcp_mcp",

0 commit comments

Comments
 (0)