@@ -324,6 +324,85 @@ pub export fn hetzner_mcp_reset() void {
324324}
325325
326326// ---------------------------------------------------------------------------
327+ // ═══════════════════════════════════════════════════════════════════════
328+ // Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
329+ // ═══════════════════════════════════════════════════════════════════════
330+
331+ const shim = @import ("cartridge_shim" );
332+
333+ const CARTRIDGE_NAME_PTR : [* :0 ]const u8 = "hetzner-mcp" ;
334+ const CARTRIDGE_VERSION_PTR : [* :0 ]const u8 = "0.1.0" ;
335+
336+ export fn boj_cartridge_init () callconv (.c ) c_int {
337+ return 0 ;
338+ }
339+
340+ export fn boj_cartridge_deinit () callconv (.c ) void {}
341+
342+ export fn boj_cartridge_name () callconv (.c ) [* :0 ]const u8 {
343+ return CARTRIDGE_NAME_PTR ;
344+ }
345+
346+ export fn boj_cartridge_version () callconv (.c ) [* :0 ]const u8 {
347+ return CARTRIDGE_VERSION_PTR ;
348+ }
349+
350+ /// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
351+ export fn boj_cartridge_invoke (
352+ tool_name : [* c ]const u8 ,
353+ json_args : [* c ]const u8 ,
354+ out_buf : [* c ]u8 ,
355+ in_out_len : [* c ]usize ,
356+ ) callconv (.c ) i32 {
357+ _ = json_args ;
358+ if (shim .invokeArgsNull (tool_name , out_buf , in_out_len )) return shim .RC_BAD_ARGS ;
359+
360+ const body : []const u8 = if (shim .toolIs (tool_name , "hetzner_list_servers" ))
361+ "{\" result\" :{\" items\" :[],\" count\" :0,\" status\" :\" stub\" }}"
362+ else if (shim .toolIs (tool_name , "hetzner_get_server" ))
363+ "{\" result\" :{\" metadata\" :{},\" status\" :\" stub\" }}"
364+ else if (shim .toolIs (tool_name , "hetzner_create_server" ))
365+ "{\" result\" :{\" status\" :\" stub\" }}"
366+ else if (shim .toolIs (tool_name , "hetzner_delete_server" ))
367+ "{\" result\" :{\" status\" :\" stub\" }}"
368+ else if (shim .toolIs (tool_name , "hetzner_server_action" ))
369+ "{\" result\" :{\" status\" :\" stub\" }}"
370+ else if (shim .toolIs (tool_name , "hetzner_resize_server" ))
371+ "{\" result\" :{\" status\" :\" stub\" }}"
372+ else if (shim .toolIs (tool_name , "hetzner_list_floating_ips" ))
373+ "{\" result\" :{\" items\" :[],\" count\" :0,\" status\" :\" stub\" }}"
374+ else if (shim .toolIs (tool_name , "hetzner_create_floating_ip" ))
375+ "{\" result\" :{\" status\" :\" stub\" }}"
376+ else if (shim .toolIs (tool_name , "hetzner_list_volumes" ))
377+ "{\" result\" :{\" items\" :[],\" count\" :0,\" status\" :\" stub\" }}"
378+ else if (shim .toolIs (tool_name , "hetzner_create_volume" ))
379+ "{\" result\" :{\" status\" :\" stub\" }}"
380+ else if (shim .toolIs (tool_name , "hetzner_list_firewalls" ))
381+ "{\" result\" :{\" items\" :[],\" count\" :0,\" status\" :\" stub\" }}"
382+ else if (shim .toolIs (tool_name , "hetzner_create_firewall" ))
383+ "{\" result\" :{\" status\" :\" stub\" }}"
384+ else if (shim .toolIs (tool_name , "hetzner_list_ssh_keys" ))
385+ "{\" result\" :{\" items\" :[],\" count\" :0,\" status\" :\" stub\" }}"
386+ else if (shim .toolIs (tool_name , "hetzner_create_ssh_key" ))
387+ "{\" result\" :{\" status\" :\" stub\" }}"
388+ else if (shim .toolIs (tool_name , "hetzner_list_images" ))
389+ "{\" result\" :{\" items\" :[],\" count\" :0,\" status\" :\" stub\" }}"
390+ else if (shim .toolIs (tool_name , "hetzner_create_snapshot" ))
391+ "{\" result\" :{\" status\" :\" stub\" }}"
392+ else if (shim .toolIs (tool_name , "hetzner_list_networks" ))
393+ "{\" result\" :{\" items\" :[],\" count\" :0,\" status\" :\" stub\" }}"
394+ else if (shim .toolIs (tool_name , "hetzner_create_network" ))
395+ "{\" result\" :{\" status\" :\" stub\" }}"
396+ else if (shim .toolIs (tool_name , "hetzner_list_load_balancers" ))
397+ "{\" result\" :{\" items\" :[],\" count\" :0,\" status\" :\" stub\" }}"
398+ else if (shim .toolIs (tool_name , "hetzner_create_load_balancer" ))
399+ "{\" result\" :{\" status\" :\" stub\" }}"
400+ else
401+ return shim .RC_UNKNOWN_TOOL ;
402+
403+ return shim .writeResult (out_buf , in_out_len , body );
404+ }
405+
327406// Tests
328407// ---------------------------------------------------------------------------
329408
@@ -443,3 +522,63 @@ test "slot exhaustion" {
443522 const new_slot = hetzner_mcp_authenticate (3600 );
444523 try std .testing .expect (new_slot >= 0 );
445524}
525+
526+ // ═══════════════════════════════════════════════════════════════════════
527+ // ADR-0006 invoke dispatch tests
528+ // ═══════════════════════════════════════════════════════════════════════
529+
530+ test "boj_cartridge_name returns hetzner-mcp" {
531+ const n = std .mem .span (boj_cartridge_name ());
532+ try std .testing .expectEqualStrings ("hetzner-mcp" , n );
533+ }
534+
535+ test "boj_cartridge_init returns 0" {
536+ try std .testing .expectEqual (@as (c_int , 0 ), boj_cartridge_init ());
537+ }
538+
539+ test "invoke: each declared tool succeeds" {
540+ var buf : [256 ]u8 = undefined ;
541+ const tools = [_ ][]const u8 {
542+ "hetzner_list_servers" ,
543+ "hetzner_get_server" ,
544+ "hetzner_create_server" ,
545+ "hetzner_delete_server" ,
546+ "hetzner_server_action" ,
547+ "hetzner_resize_server" ,
548+ "hetzner_list_floating_ips" ,
549+ "hetzner_create_floating_ip" ,
550+ "hetzner_list_volumes" ,
551+ "hetzner_create_volume" ,
552+ "hetzner_list_firewalls" ,
553+ "hetzner_create_firewall" ,
554+ "hetzner_list_ssh_keys" ,
555+ "hetzner_create_ssh_key" ,
556+ "hetzner_list_images" ,
557+ "hetzner_create_snapshot" ,
558+ "hetzner_list_networks" ,
559+ "hetzner_create_network" ,
560+ "hetzner_list_load_balancers" ,
561+ "hetzner_create_load_balancer" ,
562+ };
563+ for (tools ) | t | {
564+ var len : usize = buf .len ;
565+ const rc = boj_cartridge_invoke (t .ptr , "{}" , & buf , & len );
566+ try std .testing .expectEqual (@as (i32 , 0 ), rc );
567+ try std .testing .expect (std .mem .indexOf (u8 , buf [0.. len ], "result" ) != null );
568+ }
569+ }
570+
571+ test "invoke: unknown tool returns -1" {
572+ var buf : [64 ]u8 = undefined ;
573+ var len : usize = buf .len ;
574+ const rc = boj_cartridge_invoke ("nope" , "{}" , & buf , & len );
575+ try std .testing .expectEqual (@as (i32 , -1 ), rc );
576+ }
577+
578+ test "invoke: buffer too small returns -3" {
579+ var buf : [4 ]u8 = undefined ;
580+ var len : usize = buf .len ;
581+ const rc = boj_cartridge_invoke ("hetzner_list_servers" , "{}" , & buf , & len );
582+ try std .testing .expectEqual (@as (i32 , -3 ), rc );
583+ try std .testing .expect (len > 4 );
584+ }
0 commit comments