@@ -33,7 +33,7 @@ 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.
36+ // Cartridge libraries — all 22 cartridges linked for full catalogue mounting.
3737// Each cartridge .so is built by: cd cartridges/<name>/ffi && zig build
3838#flag -L../../cartridges/agent-mcp/ffi/zig-out/lib
3939#flag -lagent_mcp
@@ -77,6 +77,8 @@ import time
7777#flag -lsecrets_mcp
7878#flag -L../../cartridges/ssg-mcp/ffi/zig-out/lib
7979#flag -lssg_mcp
80+ #flag -L../../cartridges/ums-mcp/ffi/zig-out/lib
81+ #flag -lums_mcp
8082
8183fn C.boj_catalogue_init () int
8284fn C.boj_catalogue_deinit ()
@@ -216,6 +218,7 @@ enum CapabilityDomain {
216218 fleet_dom = 12
217219 nesy_dom = 13
218220 feedback = 14
221+ ums = 15
219222}
220223
221224enum MenuTier {
@@ -263,6 +266,7 @@ fn domain_label(d CapabilityDomain) string {
263266 .fleet_dom { 'Fleet' }
264267 .nesy_dom { 'NeSy' }
265268 .feedback { 'Feedback' }
269+ .ums { 'UMS' }
266270 }
267271}
268272
@@ -649,6 +653,15 @@ fn (mut app BojApp) register_builtin_cartridges() ! {
649653 protocols: [ProtocolType.mcp, .rest]
650654 index: 0
651655 },
656+ CartridgeInfo{
657+ name: 'ums-mcp'
658+ version: '0.1.0'
659+ status: .ready
660+ tier: .shield
661+ domain: .ums
662+ protocols: [ProtocolType.mcp, .rest]
663+ index: 0
664+ },
652665 ]
653666 for b in builtins {
654667 app.register_cartridge (b)!
@@ -1376,8 +1389,9 @@ fn handle_cartridge_invoke(app &BojApp, cname string, body string) http.Response
13761389 if cname == 'comms-mcp' { return invoke_comms (invoke_req.tool, invoke_req.args) }
13771390 if cname == 'ml-mcp' { return invoke_ml (invoke_req.tool, invoke_req.args) }
13781391 if cname == 'research-mcp' { return invoke_research (invoke_req.tool, invoke_req.args) }
1392+ if cname == 'ums-mcp' { return invoke_ums (invoke_req.tool, invoke_req.args) }
13791393
1380- // All 18 cartridges should have handlers above — this is a safety fallback
1394+ // All cartridges should have handlers above — this is a safety fallback
13811395 return json_response (json.encode ({
13821396 'cartridge' : cname
13831397 'tool' : invoke_req.tool
@@ -2821,6 +2835,226 @@ fn invoke_research(tool string, args string) http.Response {
28212835 return error_response (400 , 'unknown research-mcp tool: "${tool }" — available: list_providers, authenticate, status' )
28222836}
28232837
2838+ // --- ums-mcp: IDApTIK Level Architect (Universal Map System) ---
2839+
2840+ fn C.ums_create_project (name_ptr & u8 , name_len usize) int
2841+ fn C.ums_open_project (name_ptr & u8 , name_len usize) int
2842+ fn C.ums_delete_project (slot_idx int ) int
2843+ fn C.ums_load_level (slot_idx int , name_ptr & u8 , name_len usize) int
2844+ fn C.ums_save_level (slot_idx int ) int
2845+ fn C.ums_validate_level_abi (slot_idx int ) int
2846+ fn C.ums_list_levels (slot_idx int ) int
2847+ fn C.ums_export_level_config (slot_idx int ) int
2848+ fn C.ums_load_templates (slot_idx int ) int
2849+ fn C.ums_instantiate_template (slot_idx int , tmpl_ptr & u8 , tmpl_len usize, name_ptr & u8 , name_len usize) int
2850+ fn C.ums_state (slot_idx int ) int
2851+ fn C.ums_read_result (slot_idx int , out_ptr & u8 , out_cap usize) int
2852+ fn C.ums_close (slot_idx int ) int
2853+ fn C.ums_reset ()
2854+
2855+ fn ums_state_label (s int ) string {
2856+ return match s {
2857+ 0 { 'idle' }
2858+ 1 { 'project_open' }
2859+ 2 { 'level_loaded' }
2860+ 3 { 'validating' }
2861+ 4 { 'valid' }
2862+ 5 { 'invalid' }
2863+ 6 { 'saved' }
2864+ else { 'unknown' }
2865+ }
2866+ }
2867+
2868+ fn read_ums_result (slot int ) string {
2869+ mut buf := []u8 {len: 8192 }
2870+ rc := C.ums_read_result (slot, buf.data, usize (buf.len))
2871+ if rc < = 0 {
2872+ return '{}'
2873+ }
2874+ return buf[..rc].bytestr ()
2875+ }
2876+
2877+ fn invoke_ums (tool string , args string ) http.Response {
2878+ if tool == 'create_project' {
2879+ params := json.decode (map [string ]string , args) or {
2880+ return error_response (400 , 'create_project requires {"name": "project-name"}' )
2881+ }
2882+ name := params['name' ] or { '' }
2883+ if name == '' {
2884+ return error_response (400 , 'create_project requires "name"' )
2885+ }
2886+ slot := C.ums_create_project (name.str, usize (name.len))
2887+ if slot < 0 {
2888+ return error_response (503 , 'no UMS session slots available' )
2889+ }
2890+ return json_response (json.encode ({
2891+ 'tool' : 'create_project'
2892+ 'name' : name
2893+ 'slot' : '${slot }'
2894+ 'state' : 'project_open'
2895+ 'result' : read_ums_result (slot)
2896+ }))
2897+ }
2898+ if tool == 'open_project' {
2899+ params := json.decode (map [string ]string , args) or {
2900+ return error_response (400 , 'open_project requires {"name": "project-name"}' )
2901+ }
2902+ name := params['name' ] or { '' }
2903+ if name == '' {
2904+ return error_response (400 , 'open_project requires "name"' )
2905+ }
2906+ slot := C.ums_open_project (name.str, usize (name.len))
2907+ if slot < 0 {
2908+ return error_response (503 , 'no UMS session slots available' )
2909+ }
2910+ return json_response (json.encode ({
2911+ 'tool' : 'open_project'
2912+ 'name' : name
2913+ 'slot' : '${slot }'
2914+ 'state' : 'project_open'
2915+ 'result' : read_ums_result (slot)
2916+ }))
2917+ }
2918+ if tool == 'delete_project' {
2919+ params := json.decode (map [string ]string , args) or {
2920+ return error_response (400 , 'delete_project requires {"slot": "0"}' )
2921+ }
2922+ slot := (params['slot' ] or { '0' }).int ()
2923+ rc := C.ums_delete_project (slot)
2924+ if rc == - 1 {
2925+ return error_response (404 , 'slot ${slot } not active' )
2926+ }
2927+ if rc == - 2 {
2928+ return error_response (409 , 'cannot delete: level is loaded (close level first)' )
2929+ }
2930+ return json_response (json.encode ({
2931+ 'tool' : 'delete_project'
2932+ 'slot' : '${slot }'
2933+ 'status' : 'deleted'
2934+ }))
2935+ }
2936+ if tool == 'load_level' {
2937+ params := json.decode (map [string ]string , args) or {
2938+ return error_response (400 , 'load_level requires {"slot": "0", "name": "level-name"}' )
2939+ }
2940+ slot := (params['slot' ] or { '0' }).int ()
2941+ name := params['name' ] or { '' }
2942+ if name == '' {
2943+ return error_response (400 , 'load_level requires "name"' )
2944+ }
2945+ rc := C.ums_load_level (slot, name.str, usize (name.len))
2946+ if rc < 0 {
2947+ return error_response (409 , 'load_level failed on slot ${slot } (code ${rc })' )
2948+ }
2949+ return json_response (json.encode ({
2950+ 'tool' : 'load_level'
2951+ 'slot' : '${slot }'
2952+ 'name' : name
2953+ 'state' : ums_state_label (C.ums_state (slot))
2954+ 'result' : read_ums_result (slot)
2955+ }))
2956+ }
2957+ if tool == 'save_level' {
2958+ params := json.decode (map [string ]string , args) or {
2959+ return error_response (400 , 'save_level requires {"slot": "0"}' )
2960+ }
2961+ slot := (params['slot' ] or { '0' }).int ()
2962+ rc := C.ums_save_level (slot)
2963+ if rc < 0 {
2964+ return error_response (409 , 'save_level failed on slot ${slot } (code ${rc }) — must validate first' )
2965+ }
2966+ return json_response (json.encode ({
2967+ 'tool' : 'save_level'
2968+ 'slot' : '${slot }'
2969+ 'state' : ums_state_label (C.ums_state (slot))
2970+ 'result' : read_ums_result (slot)
2971+ }))
2972+ }
2973+ if tool == 'validate_level_abi' {
2974+ params := json.decode (map [string ]string , args) or {
2975+ return error_response (400 , 'validate_level_abi requires {"slot": "0"}' )
2976+ }
2977+ slot := (params['slot' ] or { '0' }).int ()
2978+ rc := C.ums_validate_level_abi (slot)
2979+ if rc < 0 {
2980+ return error_response (409 , 'validate_level_abi failed on slot ${slot } (code ${rc })' )
2981+ }
2982+ return json_response (json.encode ({
2983+ 'tool' : 'validate_level_abi'
2984+ 'slot' : '${slot }'
2985+ 'state' : ums_state_label (C.ums_state (slot))
2986+ 'result' : read_ums_result (slot)
2987+ }))
2988+ }
2989+ if tool == 'list_levels' {
2990+ params := json.decode (map [string ]string , args) or {
2991+ return error_response (400 , 'list_levels requires {"slot": "0"}' )
2992+ }
2993+ slot := (params['slot' ] or { '0' }).int ()
2994+ rc := C.ums_list_levels (slot)
2995+ if rc < 0 {
2996+ return error_response (409 , 'list_levels failed on slot ${slot } (code ${rc })' )
2997+ }
2998+ return json_response (json.encode ({
2999+ 'tool' : 'list_levels'
3000+ 'slot' : '${slot }'
3001+ 'state' : ums_state_label (C.ums_state (slot))
3002+ 'result' : read_ums_result (slot)
3003+ }))
3004+ }
3005+ if tool == 'export_level_config' {
3006+ params := json.decode (map [string ]string , args) or {
3007+ return error_response (400 , 'export_level_config requires {"slot": "0"}' )
3008+ }
3009+ slot := (params['slot' ] or { '0' }).int ()
3010+ rc := C.ums_export_level_config (slot)
3011+ if rc < 0 {
3012+ return error_response (409 , 'export_level_config failed on slot ${slot } (code ${rc })' )
3013+ }
3014+ return json_response (json.encode ({
3015+ 'tool' : 'export_level_config'
3016+ 'slot' : '${slot }'
3017+ 'state' : ums_state_label (C.ums_state (slot))
3018+ 'result' : read_ums_result (slot)
3019+ }))
3020+ }
3021+ if tool == 'load_templates' {
3022+ rc := C.ums_load_templates (0 )
3023+ if rc < 0 {
3024+ return error_response (500 , 'load_templates failed' )
3025+ }
3026+ return json_response (json.encode ({
3027+ 'tool' : 'load_templates'
3028+ 'templates' : '[]'
3029+ 'count' : '0'
3030+ }))
3031+ }
3032+ if tool == 'instantiate_template' {
3033+ params := json.decode (map [string ]string , args) or {
3034+ return error_response (400 , 'instantiate_template requires {"slot": "0", "template": "name", "level_name": "name"}' )
3035+ }
3036+ slot := (params['slot' ] or { '0' }).int ()
3037+ tmpl := params['template' ] or { '' }
3038+ level_name := params['level_name' ] or { '' }
3039+ if tmpl == '' || level_name == '' {
3040+ return error_response (400 , 'instantiate_template requires "template" and "level_name"' )
3041+ }
3042+ rc := C.ums_instantiate_template (slot, tmpl.str, usize (tmpl.len), level_name.str, usize (level_name.len))
3043+ if rc < 0 {
3044+ return error_response (409 , 'instantiate_template failed on slot ${slot } (code ${rc })' )
3045+ }
3046+ return json_response (json.encode ({
3047+ 'tool' : 'instantiate_template'
3048+ 'slot' : '${slot }'
3049+ 'template' : tmpl
3050+ 'level_name' : level_name
3051+ 'state' : ums_state_label (C.ums_state (slot))
3052+ 'result' : read_ums_result (slot)
3053+ }))
3054+ }
3055+ return error_response (400 , 'unknown ums-mcp tool: "${tool }" — available: load_level, save_level, validate_level_abi, list_levels, export_level_config, create_project, open_project, delete_project, load_templates, instantiate_template' )
3056+ }
3057+
28243058struct CartridgeDetail {
28253059 name string
28263060 version string
0 commit comments