Skip to content

Commit b50477b

Browse files
hyperpolymathclaude
andcommitted
feat(adr-0006): migrate 5 cartridges to 5-symbol ABI (Pattern B batch 3)
Pattern B migration (clickhouse-mcp,cloudflare-mcp,conflow-mcp,crates-mcp,digitalocean-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 47e9c11 commit b50477b

10 files changed

Lines changed: 624 additions & 0 deletions

File tree

cartridges/clickhouse-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("clickhouse_mcp", .{
1224
.root_source_file = b.path("clickhouse_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 = "clickhouse_mcp",

cartridges/clickhouse-mcp/ffi/clickhouse_mcp_ffi.zig

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,59 @@ pub export fn clickhouse_mcp_reset() void {
248248
}
249249

250250
// ---------------------------------------------------------------------------
251+
// ═══════════════════════════════════════════════════════════════════════
252+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
253+
// ═══════════════════════════════════════════════════════════════════════
254+
255+
const shim = @import("cartridge_shim");
256+
257+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "clickhouse-mcp";
258+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
259+
260+
export fn boj_cartridge_init() callconv(.c) c_int {
261+
return 0;
262+
}
263+
264+
export fn boj_cartridge_deinit() callconv(.c) void {}
265+
266+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
267+
return CARTRIDGE_NAME_PTR;
268+
}
269+
270+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
271+
return CARTRIDGE_VERSION_PTR;
272+
}
273+
274+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
275+
export fn boj_cartridge_invoke(
276+
tool_name: [*c]const u8,
277+
json_args: [*c]const u8,
278+
out_buf: [*c]u8,
279+
in_out_len: [*c]usize,
280+
) callconv(.c) i32 {
281+
_ = json_args;
282+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
283+
284+
const body: []const u8 = if (shim.toolIs(tool_name, "clickhouse_connect"))
285+
"{\"result\":{\"status\":\"stub\"}}"
286+
else if (shim.toolIs(tool_name, "clickhouse_query"))
287+
"{\"result\":{\"matches\":[],\"status\":\"stub\"}}"
288+
else if (shim.toolIs(tool_name, "clickhouse_insert"))
289+
"{\"result\":{\"status\":\"stub\"}}"
290+
else if (shim.toolIs(tool_name, "clickhouse_ddl"))
291+
"{\"result\":{\"status\":\"stub\"}}"
292+
else if (shim.toolIs(tool_name, "clickhouse_list_tables"))
293+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
294+
else if (shim.toolIs(tool_name, "clickhouse_describe"))
295+
"{\"result\":{\"status\":\"stub\"}}"
296+
else if (shim.toolIs(tool_name, "clickhouse_disconnect"))
297+
"{\"result\":{\"status\":\"stub\"}}"
298+
else
299+
return shim.RC_UNKNOWN_TOOL;
300+
301+
return shim.writeResult(out_buf, in_out_len, body);
302+
}
303+
251304
// Tests
252305
// ---------------------------------------------------------------------------
253306

@@ -370,3 +423,50 @@ test "error recovery cycle" {
370423
// Error state — can only go to disconnected (close)
371424
try std.testing.expectEqual(@as(c_int, 0), clickhouse_mcp_session_close(slot));
372425
}
426+
427+
// ═══════════════════════════════════════════════════════════════════════
428+
// ADR-0006 invoke dispatch tests
429+
// ═══════════════════════════════════════════════════════════════════════
430+
431+
test "boj_cartridge_name returns clickhouse-mcp" {
432+
const n = std.mem.span(boj_cartridge_name());
433+
try std.testing.expectEqualStrings("clickhouse-mcp", n);
434+
}
435+
436+
test "boj_cartridge_init returns 0" {
437+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
438+
}
439+
440+
test "invoke: each declared tool succeeds" {
441+
var buf: [256]u8 = undefined;
442+
const tools = [_][]const u8{
443+
"clickhouse_connect",
444+
"clickhouse_query",
445+
"clickhouse_insert",
446+
"clickhouse_ddl",
447+
"clickhouse_list_tables",
448+
"clickhouse_describe",
449+
"clickhouse_disconnect",
450+
};
451+
for (tools) |t| {
452+
var len: usize = buf.len;
453+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
454+
try std.testing.expectEqual(@as(i32, 0), rc);
455+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
456+
}
457+
}
458+
459+
test "invoke: unknown tool returns -1" {
460+
var buf: [64]u8 = undefined;
461+
var len: usize = buf.len;
462+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
463+
try std.testing.expectEqual(@as(i32, -1), rc);
464+
}
465+
466+
test "invoke: buffer too small returns -3" {
467+
var buf: [4]u8 = undefined;
468+
var len: usize = buf.len;
469+
const rc = boj_cartridge_invoke("clickhouse_connect", "{}", &buf, &len);
470+
try std.testing.expectEqual(@as(i32, -3), rc);
471+
try std.testing.expect(len > 4);
472+
}

cartridges/cloudflare-mcp/ffi/build.zig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,25 @@ 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("cloudflare_mcp_ffi", .{
1123
.root_source_file = b.path("cloudflare_mcp_ffi.zig"),
1224
.target = target,
1325
.optimize = optimize,
1426
});
27+
28+
ffi_mod.addImport("cartridge_shim", shim_mod);
1529
const lib = b.addLibrary(.{
1630
.name = "cloudflare_mcp_ffi",
1731
.root_module = ffi_mod,

cartridges/cloudflare-mcp/ffi/cloudflare_mcp_ffi.zig

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,115 @@ export fn cf_record_type_is_proxyable(record_type_int: c_int) c_int {
136136
export fn cf_proxied_provides_ipv6(proxied: c_int) c_int {
137137
return if (proxied != 0) 1 else 0;
138138
}
139+
140+
// ═══════════════════════════════════════════════════════════════════════
141+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
142+
// ═══════════════════════════════════════════════════════════════════════
143+
144+
const shim = @import("cartridge_shim");
145+
146+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "cloudflare-mcp";
147+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
148+
149+
export fn boj_cartridge_init() callconv(.c) c_int {
150+
return 0;
151+
}
152+
153+
export fn boj_cartridge_deinit() callconv(.c) void {}
154+
155+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
156+
return CARTRIDGE_NAME_PTR;
157+
}
158+
159+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
160+
return CARTRIDGE_VERSION_PTR;
161+
}
162+
163+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
164+
export fn boj_cartridge_invoke(
165+
tool_name: [*c]const u8,
166+
json_args: [*c]const u8,
167+
out_buf: [*c]u8,
168+
in_out_len: [*c]usize,
169+
) callconv(.c) i32 {
170+
_ = json_args;
171+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
172+
173+
const body: []const u8 = if (shim.toolIs(tool_name, "cf_list_zones"))
174+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
175+
else if (shim.toolIs(tool_name, "cf_get_zone"))
176+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
177+
else if (shim.toolIs(tool_name, "cf_list_dns_records"))
178+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
179+
else if (shim.toolIs(tool_name, "cf_get_dns_record"))
180+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
181+
else if (shim.toolIs(tool_name, "cf_create_dns_record"))
182+
"{\"result\":{\"status\":\"stub\"}}"
183+
else if (shim.toolIs(tool_name, "cf_update_dns_record"))
184+
"{\"result\":{\"status\":\"stub\"}}"
185+
else if (shim.toolIs(tool_name, "cf_patch_dns_record"))
186+
"{\"result\":{\"status\":\"stub\"}}"
187+
else if (shim.toolIs(tool_name, "cf_delete_dns_record"))
188+
"{\"result\":{\"status\":\"stub\"}}"
189+
else if (shim.toolIs(tool_name, "cf_get_zone_setting"))
190+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
191+
else if (shim.toolIs(tool_name, "cf_update_zone_setting"))
192+
"{\"result\":{\"status\":\"stub\"}}"
193+
else if (shim.toolIs(tool_name, "cf_purge_cache"))
194+
"{\"result\":{\"status\":\"stub\"}}"
195+
else
196+
return shim.RC_UNKNOWN_TOOL;
197+
198+
return shim.writeResult(out_buf, in_out_len, body);
199+
}
200+
201+
// ═══════════════════════════════════════════════════════════════════════
202+
// ADR-0006 invoke dispatch tests
203+
// ═══════════════════════════════════════════════════════════════════════
204+
205+
test "boj_cartridge_name returns cloudflare-mcp" {
206+
const n = std.mem.span(boj_cartridge_name());
207+
try std.testing.expectEqualStrings("cloudflare-mcp", n);
208+
}
209+
210+
test "boj_cartridge_init returns 0" {
211+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
212+
}
213+
214+
test "invoke: each declared tool succeeds" {
215+
var buf: [256]u8 = undefined;
216+
const tools = [_][]const u8{
217+
"cf_list_zones",
218+
"cf_get_zone",
219+
"cf_list_dns_records",
220+
"cf_get_dns_record",
221+
"cf_create_dns_record",
222+
"cf_update_dns_record",
223+
"cf_patch_dns_record",
224+
"cf_delete_dns_record",
225+
"cf_get_zone_setting",
226+
"cf_update_zone_setting",
227+
"cf_purge_cache",
228+
};
229+
for (tools) |t| {
230+
var len: usize = buf.len;
231+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
232+
try std.testing.expectEqual(@as(i32, 0), rc);
233+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
234+
}
235+
}
236+
237+
test "invoke: unknown tool returns -1" {
238+
var buf: [64]u8 = undefined;
239+
var len: usize = buf.len;
240+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
241+
try std.testing.expectEqual(@as(i32, -1), rc);
242+
}
243+
244+
test "invoke: buffer too small returns -3" {
245+
var buf: [4]u8 = undefined;
246+
var len: usize = buf.len;
247+
const rc = boj_cartridge_invoke("cf_list_zones", "{}", &buf, &len);
248+
try std.testing.expectEqual(@as(i32, -3), rc);
249+
try std.testing.expect(len > 4);
250+
}

cartridges/conflow-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("conflow_mcp", .{
1123
.root_source_file = b.path("conflow_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 = "conflow_mcp",
1832
.root_module = ffi_mod,

0 commit comments

Comments
 (0)