Skip to content

Commit 57f86ee

Browse files
hyperpolymathclaude
andcommitted
feat(cartridges): wire ums-mcp cartridge (#22, Shield tier) for IDApTIK Level Architect
Add UMS (Universal Map System) cartridge with full 3-layer ABI/FFI/adapter stack. Implements level lifecycle state machine (Idle -> ProjectOpen -> LevelLoaded -> Validating -> Valid/Invalid -> Saved) with 10 MCP tools: load_level, save_level, validate_level_abi, list_levels, export_level_config, create_project, open_project, delete_project, load_templates, instantiate_template. 6 Zig FFI tests passing, libums_mcp.so built. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8c45cab commit 57f86ee

6 files changed

Lines changed: 1163 additions & 2 deletions

File tree

adapter/v/src/main.v

Lines changed: 236 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

8183
fn C.boj_catalogue_init() int
8284
fn 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

221224
enum 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+
28243058
struct CartridgeDetail {
28253059
name string
28263060
version string

0 commit comments

Comments
 (0)