Skip to content

Commit a100436

Browse files
hyperpolymathclaude
andcommitted
feat(adr-0006): migrate 5 cartridges to 5-symbol ABI (Pattern B batch 1)
Pattern B migration (affinescript-mcp, airtable-mcp, arango-mcp, aws-mcp, browser-mcp) — cartridges previously exported zero standard symbols; this batch adds all 5 ADR-0005+0006 exports and dispatches cartridge.json tools to Grade-D-Alpha stub bodies via cartridge_shim. Tests passing per cartridge (browser-mcp has one pre-existing failing state-machine test unrelated to this migration). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d585a59 commit a100436

10 files changed

Lines changed: 609 additions & 0 deletions

File tree

cartridges/affinescript-mcp/ffi/affinescript_mcp_ffi.zig

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,69 @@ pub export fn afs_mcp_reset() void {
282282
}
283283

284284
// ---------------------------------------------------------------------------
285+
// ═══════════════════════════════════════════════════════════════════════
286+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
287+
// ═══════════════════════════════════════════════════════════════════════
288+
289+
const shim = @import("cartridge_shim");
290+
291+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "affinescript-mcp";
292+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
293+
294+
export fn boj_cartridge_init() callconv(.c) c_int {
295+
return 0;
296+
}
297+
298+
export fn boj_cartridge_deinit() callconv(.c) void {}
299+
300+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
301+
return CARTRIDGE_NAME_PTR;
302+
}
303+
304+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
305+
return CARTRIDGE_VERSION_PTR;
306+
}
307+
308+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
309+
export fn boj_cartridge_invoke(
310+
tool_name: [*c]const u8,
311+
json_args: [*c]const u8,
312+
out_buf: [*c]u8,
313+
in_out_len: [*c]usize,
314+
) callconv(.c) i32 {
315+
_ = json_args;
316+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
317+
318+
const body: []const u8 = if (shim.toolIs(tool_name, "affinescript_check"))
319+
"{\"result\":{\"status\":\"stub\"}}"
320+
else if (shim.toolIs(tool_name, "affinescript_parse"))
321+
"{\"result\":{\"status\":\"stub\"}}"
322+
else if (shim.toolIs(tool_name, "affinescript_format"))
323+
"{\"result\":{\"status\":\"stub\"}}"
324+
else if (shim.toolIs(tool_name, "affinescript_explain_error"))
325+
"{\"result\":{\"status\":\"stub\"}}"
326+
else if (shim.toolIs(tool_name, "affinescript_stdlib"))
327+
"{\"result\":{\"status\":\"stub\"}}"
328+
else if (shim.toolIs(tool_name, "affinescript_syntax_ref"))
329+
"{\"result\":{\"status\":\"stub\"}}"
330+
else if (shim.toolIs(tool_name, "affinescript_snippet"))
331+
"{\"result\":{\"status\":\"stub\"}}"
332+
else if (shim.toolIs(tool_name, "affinescript_lint"))
333+
"{\"result\":{\"status\":\"stub\"}}"
334+
else if (shim.toolIs(tool_name, "affinescript_compile"))
335+
"{\"result\":{\"status\":\"stub\"}}"
336+
else if (shim.toolIs(tool_name, "affinescript_hover"))
337+
"{\"result\":{\"status\":\"stub\"}}"
338+
else if (shim.toolIs(tool_name, "affinescript_goto_def"))
339+
"{\"result\":{\"status\":\"stub\"}}"
340+
else if (shim.toolIs(tool_name, "affinescript_complete"))
341+
"{\"result\":{\"status\":\"stub\"}}"
342+
else
343+
return shim.RC_UNKNOWN_TOOL;
344+
345+
return shim.writeResult(out_buf, in_out_len, body);
346+
}
347+
285348
// Tests
286349
// ---------------------------------------------------------------------------
287350

@@ -367,3 +430,55 @@ test "slot exhaustion" {
367430
const new_slot = afs_mcp_open(0);
368431
try std.testing.expect(new_slot >= 0);
369432
}
433+
434+
// ═══════════════════════════════════════════════════════════════════════
435+
// ADR-0006 invoke dispatch tests
436+
// ═══════════════════════════════════════════════════════════════════════
437+
438+
test "boj_cartridge_name returns affinescript-mcp" {
439+
const n = std.mem.span(boj_cartridge_name());
440+
try std.testing.expectEqualStrings("affinescript-mcp", n);
441+
}
442+
443+
test "boj_cartridge_init returns 0" {
444+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
445+
}
446+
447+
test "invoke: each declared tool succeeds" {
448+
var buf: [256]u8 = undefined;
449+
const tools = [_][]const u8{
450+
"affinescript_check",
451+
"affinescript_parse",
452+
"affinescript_format",
453+
"affinescript_explain_error",
454+
"affinescript_stdlib",
455+
"affinescript_syntax_ref",
456+
"affinescript_snippet",
457+
"affinescript_lint",
458+
"affinescript_compile",
459+
"affinescript_hover",
460+
"affinescript_goto_def",
461+
"affinescript_complete",
462+
};
463+
for (tools) |t| {
464+
var len: usize = buf.len;
465+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
466+
try std.testing.expectEqual(@as(i32, 0), rc);
467+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
468+
}
469+
}
470+
471+
test "invoke: unknown tool returns -1" {
472+
var buf: [64]u8 = undefined;
473+
var len: usize = buf.len;
474+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
475+
try std.testing.expectEqual(@as(i32, -1), rc);
476+
}
477+
478+
test "invoke: buffer too small returns -3" {
479+
var buf: [4]u8 = undefined;
480+
var len: usize = buf.len;
481+
const rc = boj_cartridge_invoke("affinescript_check", "{}", &buf, &len);
482+
try std.testing.expectEqual(@as(i32, -3), rc);
483+
try std.testing.expect(len > 4);
484+
}

cartridges/affinescript-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("affinescript_mcp", .{
1123
.root_source_file = b.path("affinescript_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 = "affinescript_mcp",
1832
.root_module = ffi_mod,

cartridges/airtable-mcp/ffi/airtable_mcp_ffi.zig

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,65 @@ pub export fn airtable_mcp_reset() void {
259259
}
260260

261261
// ---------------------------------------------------------------------------
262+
// ═══════════════════════════════════════════════════════════════════════
263+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
264+
// ═══════════════════════════════════════════════════════════════════════
265+
266+
const shim = @import("cartridge_shim");
267+
268+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "airtable-mcp";
269+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
270+
271+
export fn boj_cartridge_init() callconv(.c) c_int {
272+
return 0;
273+
}
274+
275+
export fn boj_cartridge_deinit() callconv(.c) void {}
276+
277+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
278+
return CARTRIDGE_NAME_PTR;
279+
}
280+
281+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
282+
return CARTRIDGE_VERSION_PTR;
283+
}
284+
285+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
286+
export fn boj_cartridge_invoke(
287+
tool_name: [*c]const u8,
288+
json_args: [*c]const u8,
289+
out_buf: [*c]u8,
290+
in_out_len: [*c]usize,
291+
) callconv(.c) i32 {
292+
_ = json_args;
293+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
294+
295+
const body: []const u8 = if (shim.toolIs(tool_name, "airtable_list_bases"))
296+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
297+
else if (shim.toolIs(tool_name, "airtable_get_base_schema"))
298+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
299+
else if (shim.toolIs(tool_name, "airtable_list_records"))
300+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
301+
else if (shim.toolIs(tool_name, "airtable_get_record"))
302+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
303+
else if (shim.toolIs(tool_name, "airtable_create_record"))
304+
"{\"result\":{\"status\":\"stub\"}}"
305+
else if (shim.toolIs(tool_name, "airtable_update_record"))
306+
"{\"result\":{\"status\":\"stub\"}}"
307+
else if (shim.toolIs(tool_name, "airtable_list_fields"))
308+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
309+
else if (shim.toolIs(tool_name, "airtable_list_views"))
310+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
311+
else if (shim.toolIs(tool_name, "airtable_list_webhooks"))
312+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
313+
else if (shim.toolIs(tool_name, "airtable_get_comments"))
314+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
315+
else
316+
return shim.RC_UNKNOWN_TOOL;
317+
318+
return shim.writeResult(out_buf, in_out_len, body);
319+
}
320+
262321
// Tests
263322
// ---------------------------------------------------------------------------
264323

@@ -331,3 +390,53 @@ test "slot exhaustion" {
331390
const new_slot = airtable_mcp_connect(0);
332391
try std.testing.expect(new_slot >= 0);
333392
}
393+
394+
// ═══════════════════════════════════════════════════════════════════════
395+
// ADR-0006 invoke dispatch tests
396+
// ═══════════════════════════════════════════════════════════════════════
397+
398+
test "boj_cartridge_name returns airtable-mcp" {
399+
const n = std.mem.span(boj_cartridge_name());
400+
try std.testing.expectEqualStrings("airtable-mcp", n);
401+
}
402+
403+
test "boj_cartridge_init returns 0" {
404+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
405+
}
406+
407+
test "invoke: each declared tool succeeds" {
408+
var buf: [256]u8 = undefined;
409+
const tools = [_][]const u8{
410+
"airtable_list_bases",
411+
"airtable_get_base_schema",
412+
"airtable_list_records",
413+
"airtable_get_record",
414+
"airtable_create_record",
415+
"airtable_update_record",
416+
"airtable_list_fields",
417+
"airtable_list_views",
418+
"airtable_list_webhooks",
419+
"airtable_get_comments",
420+
};
421+
for (tools) |t| {
422+
var len: usize = buf.len;
423+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
424+
try std.testing.expectEqual(@as(i32, 0), rc);
425+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
426+
}
427+
}
428+
429+
test "invoke: unknown tool returns -1" {
430+
var buf: [64]u8 = undefined;
431+
var len: usize = buf.len;
432+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
433+
try std.testing.expectEqual(@as(i32, -1), rc);
434+
}
435+
436+
test "invoke: buffer too small returns -3" {
437+
var buf: [4]u8 = undefined;
438+
var len: usize = buf.len;
439+
const rc = boj_cartridge_invoke("airtable_list_bases", "{}", &buf, &len);
440+
try std.testing.expectEqual(@as(i32, -3), rc);
441+
try std.testing.expect(len > 4);
442+
}

cartridges/airtable-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("airtable_mcp", .{
1123
.root_source_file = b.path("airtable_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 = "airtable_mcp",
1832
.root_module = ffi_mod,

0 commit comments

Comments
 (0)