Skip to content

Commit 445535d

Browse files
hyperpolymathclaude
andcommitted
feat(adr-0006): migrate 5 cartridges to 5-symbol ABI (Pattern B batch 10)
Pattern B migration (npm-registry-mcp,obsidian-mcp,opam-mcp,opsm-mcp,panic-attack-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 2135f9b commit 445535d

10 files changed

Lines changed: 597 additions & 0 deletions

File tree

cartridges/npm-registry-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("npm_registry_mcp", .{
1426
.root_source_file = b.path("npm_registry_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 = "npm_registry_mcp",

cartridges/npm-registry-mcp/ffi/npm_registry_mcp_ffi.zig

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,69 @@ pub export fn npm_registry_mcp_reset() void {
296296
}
297297

298298
// ---------------------------------------------------------------------------
299+
// ═══════════════════════════════════════════════════════════════════════
300+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
301+
// ═══════════════════════════════════════════════════════════════════════
302+
303+
const shim = @import("cartridge_shim");
304+
305+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "npm-registry-mcp";
306+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
307+
308+
export fn boj_cartridge_init() callconv(.c) c_int {
309+
return 0;
310+
}
311+
312+
export fn boj_cartridge_deinit() callconv(.c) void {}
313+
314+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
315+
return CARTRIDGE_NAME_PTR;
316+
}
317+
318+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
319+
return CARTRIDGE_VERSION_PTR;
320+
}
321+
322+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
323+
export fn boj_cartridge_invoke(
324+
tool_name: [*c]const u8,
325+
json_args: [*c]const u8,
326+
out_buf: [*c]u8,
327+
in_out_len: [*c]usize,
328+
) callconv(.c) i32 {
329+
_ = json_args;
330+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
331+
332+
const body: []const u8 = if (shim.toolIs(tool_name, "npm_search_packages"))
333+
"{\"result\":{\"matches\":[],\"status\":\"stub\"}}"
334+
else if (shim.toolIs(tool_name, "npm_get_package"))
335+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
336+
else if (shim.toolIs(tool_name, "npm_get_package_version"))
337+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
338+
else if (shim.toolIs(tool_name, "npm_list_versions"))
339+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
340+
else if (shim.toolIs(tool_name, "npm_get_downloads"))
341+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
342+
else if (shim.toolIs(tool_name, "npm_get_downloads_range"))
343+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
344+
else if (shim.toolIs(tool_name, "npm_get_dependencies"))
345+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
346+
else if (shim.toolIs(tool_name, "npm_get_maintainers"))
347+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
348+
else if (shim.toolIs(tool_name, "npm_get_dist_tags"))
349+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
350+
else if (shim.toolIs(tool_name, "npm_get_audit_advisories"))
351+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
352+
else if (shim.toolIs(tool_name, "npm_get_provenance"))
353+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
354+
else if (shim.toolIs(tool_name, "npm_get_packument"))
355+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
356+
else
357+
return shim.RC_UNKNOWN_TOOL;
358+
359+
return shim.writeResult(out_buf, in_out_len, body);
360+
}
361+
299362
// Tests
300363
// ---------------------------------------------------------------------------
301364

@@ -425,3 +488,55 @@ test "slot exhaustion" {
425488
const new_slot = npm_registry_mcp_authenticate(0);
426489
try std.testing.expect(new_slot >= 0);
427490
}
491+
492+
// ═══════════════════════════════════════════════════════════════════════
493+
// ADR-0006 invoke dispatch tests
494+
// ═══════════════════════════════════════════════════════════════════════
495+
496+
test "boj_cartridge_name returns npm-registry-mcp" {
497+
const n = std.mem.span(boj_cartridge_name());
498+
try std.testing.expectEqualStrings("npm-registry-mcp", n);
499+
}
500+
501+
test "boj_cartridge_init returns 0" {
502+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
503+
}
504+
505+
test "invoke: each declared tool succeeds" {
506+
var buf: [256]u8 = undefined;
507+
const tools = [_][]const u8{
508+
"npm_search_packages",
509+
"npm_get_package",
510+
"npm_get_package_version",
511+
"npm_list_versions",
512+
"npm_get_downloads",
513+
"npm_get_downloads_range",
514+
"npm_get_dependencies",
515+
"npm_get_maintainers",
516+
"npm_get_dist_tags",
517+
"npm_get_audit_advisories",
518+
"npm_get_provenance",
519+
"npm_get_packument",
520+
};
521+
for (tools) |t| {
522+
var len: usize = buf.len;
523+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
524+
try std.testing.expectEqual(@as(i32, 0), rc);
525+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
526+
}
527+
}
528+
529+
test "invoke: unknown tool returns -1" {
530+
var buf: [64]u8 = undefined;
531+
var len: usize = buf.len;
532+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
533+
try std.testing.expectEqual(@as(i32, -1), rc);
534+
}
535+
536+
test "invoke: buffer too small returns -3" {
537+
var buf: [4]u8 = undefined;
538+
var len: usize = buf.len;
539+
const rc = boj_cartridge_invoke("npm_search_packages", "{}", &buf, &len);
540+
try std.testing.expectEqual(@as(i32, -3), rc);
541+
try std.testing.expect(len > 4);
542+
}

cartridges/obsidian-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("obsidian_mcp", .{
1123
.root_source_file = b.path("obsidian_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 = "obsidian_mcp",
1832
.root_module = ffi_mod,

cartridges/obsidian-mcp/ffi/obsidian_mcp_ffi.zig

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,69 @@ pub export fn obsidian_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 = "obsidian-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, "obsidian_search_notes"))
319+
"{\"result\":{\"matches\":[],\"status\":\"stub\"}}"
320+
else if (shim.toolIs(tool_name, "obsidian_get_note"))
321+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
322+
else if (shim.toolIs(tool_name, "obsidian_list_notes"))
323+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
324+
else if (shim.toolIs(tool_name, "obsidian_get_backlinks"))
325+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
326+
else if (shim.toolIs(tool_name, "obsidian_get_outgoing_links"))
327+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
328+
else if (shim.toolIs(tool_name, "obsidian_list_tags"))
329+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
330+
else if (shim.toolIs(tool_name, "obsidian_get_notes_by_tag"))
331+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
332+
else if (shim.toolIs(tool_name, "obsidian_get_frontmatter"))
333+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
334+
else if (shim.toolIs(tool_name, "obsidian_get_daily_note"))
335+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
336+
else if (shim.toolIs(tool_name, "obsidian_vault_stats"))
337+
"{\"result\":{\"status\":\"stub\"}}"
338+
else if (shim.toolIs(tool_name, "obsidian_dataview_query"))
339+
"{\"result\":{\"matches\":[],\"status\":\"stub\"}}"
340+
else if (shim.toolIs(tool_name, "obsidian_list_templates"))
341+
"{\"result\":{\"items\":[],\"count\":0,\"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

@@ -371,3 +434,55 @@ test "slot exhaustion" {
371434
const new_slot = obsidian_mcp_connect(0);
372435
try std.testing.expect(new_slot >= 0);
373436
}
437+
438+
// ═══════════════════════════════════════════════════════════════════════
439+
// ADR-0006 invoke dispatch tests
440+
// ═══════════════════════════════════════════════════════════════════════
441+
442+
test "boj_cartridge_name returns obsidian-mcp" {
443+
const n = std.mem.span(boj_cartridge_name());
444+
try std.testing.expectEqualStrings("obsidian-mcp", n);
445+
}
446+
447+
test "boj_cartridge_init returns 0" {
448+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
449+
}
450+
451+
test "invoke: each declared tool succeeds" {
452+
var buf: [256]u8 = undefined;
453+
const tools = [_][]const u8{
454+
"obsidian_search_notes",
455+
"obsidian_get_note",
456+
"obsidian_list_notes",
457+
"obsidian_get_backlinks",
458+
"obsidian_get_outgoing_links",
459+
"obsidian_list_tags",
460+
"obsidian_get_notes_by_tag",
461+
"obsidian_get_frontmatter",
462+
"obsidian_get_daily_note",
463+
"obsidian_vault_stats",
464+
"obsidian_dataview_query",
465+
"obsidian_list_templates",
466+
};
467+
for (tools) |t| {
468+
var len: usize = buf.len;
469+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
470+
try std.testing.expectEqual(@as(i32, 0), rc);
471+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
472+
}
473+
}
474+
475+
test "invoke: unknown tool returns -1" {
476+
var buf: [64]u8 = undefined;
477+
var len: usize = buf.len;
478+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
479+
try std.testing.expectEqual(@as(i32, -1), rc);
480+
}
481+
482+
test "invoke: buffer too small returns -3" {
483+
var buf: [4]u8 = undefined;
484+
var len: usize = buf.len;
485+
const rc = boj_cartridge_invoke("obsidian_search_notes", "{}", &buf, &len);
486+
try std.testing.expectEqual(@as(i32, -3), rc);
487+
try std.testing.expect(len > 4);
488+
}

cartridges/opam-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("opam_mcp", .{
1123
.root_source_file = b.path("opam_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 = "opam_mcp",
1832
.root_module = ffi_mod,

0 commit comments

Comments
 (0)