Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,28 @@ jobs:
with:
deno-version: v2.x

- name: Install Elixir + OTP
# tests/e2e_full.sh requires `mix` on PATH to start the Elixir
# backend (elixir/ — `mix run --no-halt`). Pinned to match the
# estate convention (see hypatia-scan.yml across the org).
uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.18.2
with:
elixir-version: '1.18'
otp-version: '27'

- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y curl jq

- name: Fetch Elixir deps
working-directory: elixir
run: mix deps.get

- name: Compile Elixir deps
# Pre-compile so the in-test `mix run --no-halt` doesn't have
# to do it on the critical path of the 60s server-start window.
working-directory: elixir
run: mix compile

- name: Build FFI libraries
run: |
cd ffi/zig && zig build
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/zig-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,14 @@ jobs:
all="$(ls -d cartridges/*/ffi/ 2>/dev/null | sed 's|cartridges/||;s|/ffi/||' | sort)"
if [ "$EVENT" = "pull_request" ]; then
git fetch --no-tags --depth=50 origin "$BASE_REF" || true
changed="$(git diff --name-only "origin/${BASE_REF}...HEAD" -- 'cartridges/**' \
# Only scope cartridges whose `ffi/` actually changed —
# `cartridge.json` edits (e.g. adding a status field) and
# `abi/` edits should not pull a cart into this FFI test
# job, which exists to validate Zig builds. Using
# `cartridges/*/ffi/**` as the pathspec ensures only
# ffi-relevant diffs count (also matches the workflow's
# own `on.paths` filter — they were inconsistent before).
changed="$(git diff --name-only "origin/${BASE_REF}...HEAD" -- 'cartridges/*/ffi/**' \
| awk -F/ '{print $2}' | sort -u)"
scope=""
while IFS= read -r cart; do
Expand Down
1 change: 1 addition & 0 deletions cartridges/boj-health/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>",
"name": "boj-health",
"version": "0.1.0",
"status": "ffi_only",
"description": "BoJ server self-health cartridge — status, ping, and uptime queries. Self-contained Zig FFI (.so) reference implementation: no external services required.",
"domain": "infrastructure",
"tier": "Ayo",
Expand Down
1 change: 1 addition & 0 deletions cartridges/chromadb-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>",
"name": "chromadb-mcp",
"version": "0.1.0",
"status": "stub",
"description": "Chroma vector DB — embedded (local persistent) or client/server; LLM-app-focused; metadata + document storage alongside vectors.",
"domain": "vector",
"tier": "Teranga",
Expand Down
1 change: 1 addition & 0 deletions cartridges/claude-ai-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath)",
"name": "claude-ai-mcp",
"version": "0.1.0",
"status": "ffi_only",
"description": "Anthropic Messages API cartridge -- send messages to Claude models, count tokens, manage multi-turn conversations",
"domain": "AI",
"tier": "Ayo",
Expand Down
1 change: 1 addition & 0 deletions cartridges/echidna-llm-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>",
"name": "echidna-llm-mcp",
"version": "0.1.0",
"status": "stub",
"description": "LLM advisor cartridge for the ECHIDNA formal verification engine. Provides free-form consultation (consult) and structured proof-tactic generation (suggest_tactics) by routing to Anthropic Claude via ANTHROPIC_API_KEY.",
"domain": "Formal Verification",
"tier": "Ayo",
Expand Down
1 change: 1 addition & 0 deletions cartridges/elevenlabs-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>",
"name": "elevenlabs-mcp",
"version": "0.1.0",
"status": "stub",
"description": "Text-to-speech via ElevenLabs API — high-quality voices, multilingual, voice cloning (premium tier), streaming output.",
"domain": "multimodal",
"tier": "Teranga",
Expand Down
1 change: 1 addition & 0 deletions cartridges/ffmpeg-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>",
"name": "ffmpeg-mcp",
"version": "0.1.0",
"status": "stub",
"description": "Local FFmpeg gateway — probe metadata, transcode formats, extract audio, extract frames, concatenate, trim. Glue between whisper / replicate / browser screenshots. Local-only — requires host ffmpeg binary; not Worker-compatible.",
"domain": "multimodal",
"tier": "Teranga",
Expand Down
1 change: 1 addition & 0 deletions cartridges/lang-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath)",
"name": "lang-mcp",
"version": "0.1.0",
"status": "ffi_only",
"description": "Multi-language session manager for the nextgen-languages family: Eclexia, AffineScript, BetLang, Ephapax, MyLang, WokeLang, Anvomidav, Phronesis, Error-lang, Julia-the-Viper, Me-dialect, Oblibeny. Tracks per-language sessions, delegates type-checking and evaluation to each language's CLI tool, and provides a unified interface across all dialects.",
"domain": "Languages",
"tier": "Ayo",
Expand Down
1 change: 1 addition & 0 deletions cartridges/orchestrator-lsp-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>",
"name": "orchestrator-lsp-mcp",
"version": "0.1.0",
"status": "ffi_only",
"status": "ready",
"description": "Cross-domain LSP orchestrator. Routes LSP requests across all 12 poly-*-lsp servers (cloud, container, iac, k8s, db, queue, secret, git, ssg, proof, observability, browser) via a single GenLSP supervisor. Inspired by poly-orchestrator-lsp (polystack, archived). Wraps the 12 domain servers into one unified textDocument interface with domain-routing based on workspace root and file type.",
"domain": "LSP",
Expand Down
1 change: 1 addition & 0 deletions cartridges/pinecone-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>",
"name": "pinecone-mcp",
"version": "0.1.0",
"status": "stub",
"description": "Pinecone hosted vector DB — serverless indexes, upsert, similarity search, namespaces, metadata filtering.",
"domain": "vector",
"tier": "Teranga",
Expand Down
1 change: 1 addition & 0 deletions cartridges/qdrant-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>",
"name": "qdrant-mcp",
"version": "0.1.0",
"status": "stub",
"description": "Qdrant vector DB — Rust-native; payloads + filtering; sparse + dense vectors; self-host or Qdrant Cloud.",
"domain": "vector",
"tier": "Teranga",
Expand Down
1 change: 1 addition & 0 deletions cartridges/replicate-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>",
"name": "replicate-mcp",
"version": "0.1.0",
"status": "stub",
"description": "Replicate hosted ML models — image generation (Stable Diffusion, FLUX), video (Veo, Kling), upscaling, vision (LLaVA), audio (MusicGen). Async prediction model with polling.",
"domain": "multimodal",
"tier": "Teranga",
Expand Down
1 change: 1 addition & 0 deletions cartridges/search-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>",
"name": "search-mcp",
"version": "0.1.0",
"status": "stub",
"description": "Web search across multiple providers (Tavily, Brave, Exa, Perplexity) behind one cartridge.",
"domain": "research",
"tier": "Teranga",
Expand Down
1 change: 1 addition & 0 deletions cartridges/toolchain-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath)",
"name": "toolchain-mcp",
"version": "0.1.0",
"status": "ffi_only",
"description": "Toolchain orchestrator. Mints, provisions, and configures language toolchains composed from lsp-mcp, dap-mcp, lang-mcp, and bsp-mcp. Integrates with PanLL panels via Groove and supports collaborative Burble sessions for pair-debugging and pair-programming workflows.",
"domain": "Language Tools",
"tier": "Ayo",
Expand Down
39 changes: 37 additions & 2 deletions cartridges/ums-mcp/ffi/ums_ffi.zig
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,15 @@ const SessionSlot = struct {
};

var sessions: [MAX_SESSIONS]SessionSlot = [_]SessionSlot{.{}} ** MAX_SESSIONS;

/// Find an inactive session slot and activate it.
/// Single coarse-grained mutex over the `sessions` table. The C-ABI
/// boundary is the concurrency boundary: every exported function takes
/// this lock for the duration of its slot access, so callers from any
/// thread (including the MCP bridge's tokio-style task pool) see a
/// linearised view of session state.
var sessions_mu: std.Thread.Mutex = .{};

/// Find an inactive session slot and activate it. Caller must hold
/// `sessions_mu`.
fn alloc_slot() ?usize {
for (&sessions, 0..) |*slot, i| {
if (!slot.active) {
Expand Down Expand Up @@ -126,6 +133,8 @@ pub export fn ums_can_transition(from: c_int, to: c_int) callconv(.c) c_int {

/// Create a new project. Returns slot index or -1 on failure.
pub export fn ums_create_project(name_ptr: [*]const u8, name_len: usize) callconv(.c) c_int {
sessions_mu.lock();
defer sessions_mu.unlock();
const idx = alloc_slot() orelse return -1;
const slot = &sessions[idx];
const copy_len = @min(name_len, MAX_NAME_LEN);
Expand All @@ -138,6 +147,8 @@ pub export fn ums_create_project(name_ptr: [*]const u8, name_len: usize) callcon

/// Open an existing project. Returns slot index or -1 on failure.
pub export fn ums_open_project(name_ptr: [*]const u8, name_len: usize) callconv(.c) c_int {
sessions_mu.lock();
defer sessions_mu.unlock();
const idx = alloc_slot() orelse return -1;
const slot = &sessions[idx];
const copy_len = @min(name_len, MAX_NAME_LEN);
Expand All @@ -150,6 +161,8 @@ pub export fn ums_open_project(name_ptr: [*]const u8, name_len: usize) callconv(

/// Delete a project. Requires idle or project_open state. Returns 0 on success.
pub export fn ums_delete_project(slot_idx: c_int) callconv(.c) c_int {
sessions_mu.lock();
defer sessions_mu.unlock();
if (slot_idx < 0 or slot_idx >= MAX_SESSIONS) return -1;
const idx: usize = @intCast(slot_idx);
const slot = &sessions[idx];
Expand All @@ -161,6 +174,8 @@ pub export fn ums_delete_project(slot_idx: c_int) callconv(.c) c_int {

/// Load a level in the current project. Returns 0 on success.
pub export fn ums_load_level(slot_idx: c_int, name_ptr: [*]const u8, name_len: usize) callconv(.c) c_int {
sessions_mu.lock();
defer sessions_mu.unlock();
if (slot_idx < 0 or slot_idx >= MAX_SESSIONS) return -1;
const idx: usize = @intCast(slot_idx);
const slot = &sessions[idx];
Expand All @@ -175,6 +190,8 @@ pub export fn ums_load_level(slot_idx: c_int, name_ptr: [*]const u8, name_len: u

/// Save the current level. Requires Valid state. Returns 0 on success.
pub export fn ums_save_level(slot_idx: c_int) callconv(.c) c_int {
sessions_mu.lock();
defer sessions_mu.unlock();
if (slot_idx < 0 or slot_idx >= MAX_SESSIONS) return -1;
const idx: usize = @intCast(slot_idx);
const slot = &sessions[idx];
Expand All @@ -186,6 +203,8 @@ pub export fn ums_save_level(slot_idx: c_int) callconv(.c) c_int {

/// Run ABI validation on the loaded level. Returns 0 (valid) or 1 (invalid).
pub export fn ums_validate_level_abi(slot_idx: c_int) callconv(.c) c_int {
sessions_mu.lock();
defer sessions_mu.unlock();
if (slot_idx < 0 or slot_idx >= MAX_SESSIONS) return -1;
const idx: usize = @intCast(slot_idx);
const slot = &sessions[idx];
Expand All @@ -202,6 +221,8 @@ pub export fn ums_validate_level_abi(slot_idx: c_int) callconv(.c) c_int {

/// List levels in the current project. Returns count or -1 on error.
pub export fn ums_list_levels(slot_idx: c_int) callconv(.c) c_int {
sessions_mu.lock();
defer sessions_mu.unlock();
if (slot_idx < 0 or slot_idx >= MAX_SESSIONS) return -1;
const idx: usize = @intCast(slot_idx);
const slot = &sessions[idx];
Expand All @@ -213,6 +234,8 @@ pub export fn ums_list_levels(slot_idx: c_int) callconv(.c) c_int {

/// Export the level configuration as JSON. Returns 0 on success.
pub export fn ums_export_level_config(slot_idx: c_int) callconv(.c) c_int {
sessions_mu.lock();
defer sessions_mu.unlock();
if (slot_idx < 0 or slot_idx >= MAX_SESSIONS) return -1;
const idx: usize = @intCast(slot_idx);
const slot = &sessions[idx];
Expand All @@ -231,13 +254,17 @@ pub export fn ums_export_level_config(slot_idx: c_int) callconv(.c) c_int {

/// Load available templates. Returns count or -1 on error.
pub export fn ums_load_templates(slot_idx: c_int) callconv(.c) c_int {
sessions_mu.lock();
defer sessions_mu.unlock();
_ = slot_idx;
// Templates are global, no session state required.
return 0;
}

/// Instantiate a level from a template. Returns 0 on success.
pub export fn ums_instantiate_template(slot_idx: c_int, tmpl_ptr: [*]const u8, tmpl_len: usize, name_ptr: [*]const u8, name_len: usize) callconv(.c) c_int {
sessions_mu.lock();
defer sessions_mu.unlock();
if (slot_idx < 0 or slot_idx >= MAX_SESSIONS) return -1;
const idx: usize = @intCast(slot_idx);
const slot = &sessions[idx];
Expand All @@ -256,6 +283,8 @@ pub export fn ums_instantiate_template(slot_idx: c_int, tmpl_ptr: [*]const u8, t

/// Get the current session state. Returns state int or -1 if inactive.
pub export fn ums_state(slot_idx: c_int) callconv(.c) c_int {
sessions_mu.lock();
defer sessions_mu.unlock();
if (slot_idx < 0 or slot_idx >= MAX_SESSIONS) return -1;
const idx: usize = @intCast(slot_idx);
const slot = &sessions[idx];
Expand All @@ -265,6 +294,8 @@ pub export fn ums_state(slot_idx: c_int) callconv(.c) c_int {

/// Read the result buffer. Returns bytes written or 0.
pub export fn ums_read_result(slot_idx: c_int, out_ptr: [*]u8, out_cap: usize) callconv(.c) c_int {
sessions_mu.lock();
defer sessions_mu.unlock();
if (slot_idx < 0 or slot_idx >= MAX_SESSIONS) return 0;
const idx: usize = @intCast(slot_idx);
const slot = &sessions[idx];
Expand All @@ -276,6 +307,8 @@ pub export fn ums_read_result(slot_idx: c_int, out_ptr: [*]u8, out_cap: usize) c

/// Close a session (force to idle and deactivate).
pub export fn ums_close(slot_idx: c_int) callconv(.c) c_int {
sessions_mu.lock();
defer sessions_mu.unlock();
if (slot_idx < 0 or slot_idx >= MAX_SESSIONS) return -1;
const idx: usize = @intCast(slot_idx);
sessions[idx] = .{};
Expand All @@ -284,6 +317,8 @@ pub export fn ums_close(slot_idx: c_int) callconv(.c) c_int {

/// Reset all sessions (testing/teardown).
pub export fn ums_reset() callconv(.c) void {
sessions_mu.lock();
defer sessions_mu.unlock();
sessions = [_]SessionSlot{.{}} ** MAX_SESSIONS;
}

Expand Down
1 change: 1 addition & 0 deletions cartridges/weaviate-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>",
"name": "weaviate-mcp",
"version": "0.1.0",
"status": "stub",
"description": "Weaviate vector DB — hybrid (vector + BM25 + filter) search, schema-driven classes, modular vectorisers; self-host or cloud.",
"domain": "vector",
"tier": "Teranga",
Expand Down
1 change: 1 addition & 0 deletions cartridges/whisper-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>",
"name": "whisper-mcp",
"version": "0.1.0",
"status": "stub",
"description": "Speech-to-text via OpenAI Whisper API + local whisper.cpp fallback. Transcription, language detection, optional translation to English.",
"domain": "multimodal",
"tier": "Teranga",
Expand Down
Loading
Loading