@@ -33,10 +33,50 @@ import time
3333#flag -lboj_sla
3434#flag -lboj_community
3535#flag -lboj_sdp
36+ // Cartridge libraries — all 21 cartridges linked for full catalogue mounting.
37+ // Each cartridge .so is built by: cd cartridges/<name>/ffi && zig build
38+ #flag -L../../cartridges/agent-mcp/ffi/zig-out/lib
39+ #flag -lagent_mcp
40+ #flag -L../../cartridges/bsp-mcp/ffi/zig-out/lib
41+ #flag -lbsp_mcp
42+ #flag -L../../cartridges/cloud-mcp/ffi/zig-out/lib
43+ #flag -lcloud_mcp
44+ #flag -L../../cartridges/comms-mcp/ffi/zig-out/lib
45+ #flag -lcomms_mcp
3646#flag -L../../cartridges/container-mcp/ffi/zig-out/lib
3747#flag -lcontainer_mcp
48+ #flag -L../../cartridges/dap-mcp/ffi/zig-out/lib
49+ #flag -ldap_mcp
50+ #flag -L../../cartridges/database-mcp/ffi/zig-out/lib
51+ #flag -ldatabase_mcp
3852#flag -L../../cartridges/feedback-mcp/ffi/zig-out/lib
3953#flag -lfeedback_mcp
54+ #flag -L../../cartridges/fleet-mcp/ffi/zig-out/lib
55+ #flag -lfleet_mcp
56+ #flag -L../../cartridges/git-mcp/ffi/zig-out/lib
57+ #flag -lgit_mcp
58+ #flag -L../../cartridges/iac-mcp/ffi/zig-out/lib
59+ #flag -liac_mcp
60+ #flag -L../../cartridges/k8s-mcp/ffi/zig-out/lib
61+ #flag -lk8s_mcp
62+ #flag -L../../cartridges/lsp-mcp/ffi/zig-out/lib
63+ #flag -llsp_mcp
64+ #flag -L../../cartridges/ml-mcp/ffi/zig-out/lib
65+ #flag -lml_mcp
66+ #flag -L../../cartridges/nesy-mcp/ffi/zig-out/lib
67+ #flag -lnesy_mcp
68+ #flag -L../../cartridges/observe-mcp/ffi/zig-out/lib
69+ #flag -lobserve_mcp
70+ #flag -L../../cartridges/proof-mcp/ffi/zig-out/lib
71+ #flag -lproof_mcp
72+ #flag -L../../cartridges/queues-mcp/ffi/zig-out/lib
73+ #flag -lqueues_mcp
74+ #flag -L../../cartridges/research-mcp/ffi/zig-out/lib
75+ #flag -lresearch_mcp
76+ #flag -L../../cartridges/secrets-mcp/ffi/zig-out/lib
77+ #flag -lsecrets_mcp
78+ #flag -L../../cartridges/ssg-mcp/ffi/zig-out/lib
79+ #flag -lssg_mcp
4080
4181fn C.boj_catalogue_init () int
4282fn C.boj_catalogue_deinit ()
@@ -582,6 +622,33 @@ fn (mut app BojApp) register_builtin_cartridges() ! {
582622 protocols: [ProtocolType.mcp, .rest]
583623 index: 0
584624 },
625+ CartridgeInfo{
626+ name: 'comms-mcp'
627+ version: '0.1.0'
628+ status: .ready
629+ tier: .teranga
630+ domain: .cloud
631+ protocols: [ProtocolType.mcp, .rest]
632+ index: 0
633+ },
634+ CartridgeInfo{
635+ name: 'ml-mcp'
636+ version: '0.1.0'
637+ status: .ready
638+ tier: .teranga
639+ domain: .cloud
640+ protocols: [ProtocolType.mcp, .rest]
641+ index: 0
642+ },
643+ CartridgeInfo{
644+ name: 'research-mcp'
645+ version: '0.1.0'
646+ status: .ready
647+ tier: .teranga
648+ domain: .cloud
649+ protocols: [ProtocolType.mcp, .rest]
650+ index: 0
651+ },
585652 ]
586653 for b in builtins {
587654 app.register_cartridge (b)!
@@ -1306,6 +1373,9 @@ fn handle_cartridge_invoke(app &BojApp, cname string, body string) http.Response
13061373 if cname == 'dap-mcp' { return invoke_dap (invoke_req.tool, invoke_req.args) }
13071374 if cname == 'bsp-mcp' { return invoke_bsp (invoke_req.tool, invoke_req.args) }
13081375 if cname == 'feedback-mcp' { return invoke_feedback (invoke_req.tool, invoke_req.args) }
1376+ if cname == 'comms-mcp' { return invoke_comms (invoke_req.tool, invoke_req.args) }
1377+ if cname == 'ml-mcp' { return invoke_ml (invoke_req.tool, invoke_req.args) }
1378+ if cname == 'research-mcp' { return invoke_research (invoke_req.tool, invoke_req.args) }
13091379
13101380 // All 18 cartridges should have handlers above — this is a safety fallback
13111381 return json_response (json.encode ({
@@ -2602,6 +2672,155 @@ fn invoke_feedback(tool string, args string) http.Response {
26022672 return error_response (400 , 'unknown feedback-mcp tool: "${tool }" — available: list_channels, open_channel, submit, summary, status' )
26032673}
26042674
2675+ // ═══════════════════════════════════════════════════════════════════════
2676+ // Comms-MCP — Communications cartridge (Gmail, Calendar)
2677+ // ═══════════════════════════════════════════════════════════════════════
2678+
2679+ fn C.comms_authenticate (provider int ) int
2680+ fn C.comms_logout (slot int ) int
2681+ fn C.comms_begin_operation (slot int ) int
2682+ fn C.comms_end_operation (slot int ) int
2683+ fn C.comms_state (slot int ) int
2684+ fn C.comms_can_transition (from int , to int ) int
2685+ fn C.comms_reset () int
2686+
2687+ fn invoke_comms (tool string , args string ) http.Response {
2688+ if tool == 'list_providers' {
2689+ return json_response (json.encode ({
2690+ 'tool' : 'list_providers'
2691+ 'available' : 'gmail,calendar'
2692+ }))
2693+ }
2694+ if tool == 'authenticate' {
2695+ params := json.decode (map [string ]string , args) or {
2696+ return error_response (400 , 'authenticate requires {"provider": "gmail|calendar"}' )
2697+ }
2698+ provider := params['provider' ] or { 'gmail' }
2699+ provider_id := match provider {
2700+ 'gmail' { 1 }
2701+ 'calendar' { 2 }
2702+ else { 99 }
2703+ }
2704+ slot := C.comms_authenticate (provider_id)
2705+ if slot < 0 {
2706+ return error_response (503 , 'no comms session slots available' )
2707+ }
2708+ return json_response (json.encode ({
2709+ 'tool' : 'authenticate'
2710+ 'provider' : provider
2711+ 'slot' : '${slot }'
2712+ 'state' : 'authenticated'
2713+ }))
2714+ }
2715+ if tool == 'status' {
2716+ return json_response (json.encode ({
2717+ 'tool' : 'status'
2718+ 'state' : 'active'
2719+ 'message' : 'comms-mcp is running. Providers: gmail, calendar.'
2720+ }))
2721+ }
2722+ return error_response (400 , 'unknown comms-mcp tool: "${tool }" — available: list_providers, authenticate, status' )
2723+ }
2724+
2725+ // ═══════════════════════════════════════════════════════════════════════
2726+ // ML-MCP — Machine Learning cartridge (HuggingFace)
2727+ // ═══════════════════════════════════════════════════════════════════════
2728+
2729+ fn C.ml_authenticate (provider int ) int
2730+ fn C.ml_logout (slot int ) int
2731+ fn C.ml_begin_operation (slot int ) int
2732+ fn C.ml_end_operation (slot int ) int
2733+ fn C.ml_state (slot int ) int
2734+ fn C.ml_can_transition (from int , to int ) int
2735+ fn C.ml_reset () int
2736+
2737+ fn invoke_ml (tool string , args string ) http.Response {
2738+ if tool == 'list_providers' {
2739+ return json_response (json.encode ({
2740+ 'tool' : 'list_providers'
2741+ 'available' : 'huggingface'
2742+ }))
2743+ }
2744+ if tool == 'authenticate' {
2745+ params := json.decode (map [string ]string , args) or {
2746+ return error_response (400 , 'authenticate requires {"provider": "huggingface"}' )
2747+ }
2748+ provider := params['provider' ] or { 'huggingface' }
2749+ provider_id := match provider {
2750+ 'huggingface' { 1 }
2751+ else { 99 }
2752+ }
2753+ slot := C.ml_authenticate (provider_id)
2754+ if slot < 0 {
2755+ return error_response (503 , 'no ML session slots available' )
2756+ }
2757+ return json_response (json.encode ({
2758+ 'tool' : 'authenticate'
2759+ 'provider' : provider
2760+ 'slot' : '${slot }'
2761+ 'state' : 'authenticated'
2762+ }))
2763+ }
2764+ if tool == 'status' {
2765+ return json_response (json.encode ({
2766+ 'tool' : 'status'
2767+ 'state' : 'active'
2768+ 'message' : 'ml-mcp is running. Providers: huggingface.'
2769+ }))
2770+ }
2771+ return error_response (400 , 'unknown ml-mcp tool: "${tool }" — available: list_providers, authenticate, status' )
2772+ }
2773+
2774+ // ═══════════════════════════════════════════════════════════════════════
2775+ // Research-MCP — Academic research cartridge (Semantic Scholar, OpenAlex)
2776+ // ═══════════════════════════════════════════════════════════════════════
2777+
2778+ fn C.research_authenticate (provider int ) int
2779+ fn C.research_logout (slot int ) int
2780+ fn C.research_begin_operation (slot int ) int
2781+ fn C.research_end_operation (slot int ) int
2782+ fn C.research_state (slot int ) int
2783+ fn C.research_can_transition (from int , to int ) int
2784+ fn C.research_reset () int
2785+
2786+ fn invoke_research (tool string , args string ) http.Response {
2787+ if tool == 'list_providers' {
2788+ return json_response (json.encode ({
2789+ 'tool' : 'list_providers'
2790+ 'available' : 'semantic_scholar,openalex'
2791+ }))
2792+ }
2793+ if tool == 'authenticate' {
2794+ params := json.decode (map [string ]string , args) or {
2795+ return error_response (400 , 'authenticate requires {"provider": "semantic_scholar|openalex"}' )
2796+ }
2797+ provider := params['provider' ] or { 'semantic_scholar' }
2798+ provider_id := match provider {
2799+ 'semantic_scholar' { 2 }
2800+ 'openalex' { 3 }
2801+ else { 99 }
2802+ }
2803+ slot := C.research_authenticate (provider_id)
2804+ if slot < 0 {
2805+ return error_response (503 , 'no research session slots available' )
2806+ }
2807+ return json_response (json.encode ({
2808+ 'tool' : 'authenticate'
2809+ 'provider' : provider
2810+ 'slot' : '${slot }'
2811+ 'state' : 'authenticated'
2812+ }))
2813+ }
2814+ if tool == 'status' {
2815+ return json_response (json.encode ({
2816+ 'tool' : 'status'
2817+ 'state' : 'active'
2818+ 'message' : 'research-mcp is running. Providers: semantic_scholar, openalex.'
2819+ }))
2820+ }
2821+ return error_response (400 , 'unknown research-mcp tool: "${tool }" — available: list_providers, authenticate, status' )
2822+ }
2823+
26052824struct CartridgeDetail {
26062825 name string
26072826 version string
0 commit comments