diff --git a/.cbmignore b/.cbmignore new file mode 100644 index 0000000..e992bdf --- /dev/null +++ b/.cbmignore @@ -0,0 +1,38 @@ +# codebase-memory-mcp ignore file (gitignore syntax). +# Defense-in-depth so the code indexer never ingests secrets or non-source bulk, +# on top of the hardcoded defaults (.git, node_modules) and the .gitignore layer. +# Keep this in sync with what must never be indexed; .gitignore already covers the +# real secrets, this is the explicit belt-and-suspenders list. + +# Secrets / credentials +.env +.env.* +!.env.example +*.sops +*.sops.* +secrets/ +**/secrets/ +*.pem +*.key +id_rsa +id_ed25519 +*.age + +# Runtime / operator state (gitignored, not source) +data/ +overrides/ + +# Large / generated / non-source +models/ +*.gguf +*.safetensors +*.bin +*.onnx +node_modules/ +.venv/ +venv/ +__pycache__/ +dist/ +build/ +*.stl +*.3mf diff --git a/.env.example b/.env.example index f5ca3e2..3a452c3 100644 --- a/.env.example +++ b/.env.example @@ -26,6 +26,13 @@ BASE_PATH=. # Optional: separate data directory (default: BASE_PATH/data) # DATA_PATH=D:/ai-data +# Optional: host path containing your code repos, mounted READ-ONLY into the +# codebase-memory MCP (must match what Hermes sees at /c/dev). Required only if you +# enable the `codebase-memory` server (docker compose --profile codebase-memory). +# CODE_ROOT=C:/dev +# Optional: to also run the codebase-memory 3D graph UI, enable the profile; it is +# served at https:///codebase-memory/ (Google SSO, on :443): +# docker compose --profile codebase-memory up -d --build # --- Models (llama.cpp / GGUF) --- # Main chat model filename under models/gguf/ (must exist before llamacpp starts). diff --git a/.gitattributes b/.gitattributes index 077206f..9691d15 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,6 +5,9 @@ dashboard/entrypoint.sh text eol=lf hermes/entrypoint.sh text eol=lf scripts/llamacpp/run-llama-server.sh text eol=lf +# nginx config is COPYed into the codebase-memory-ui image; keep LF so nginx +# doesn't choke on CRLF after a Windows checkout. +codebase-memory-ui/nginx.conf text eol=lf # SOPS-encrypted files must keep LF endings or `sops` chokes parsing the # embedded timestamp metadata. The failure surfaces as diff --git a/CHANGELOG.md b/CHANGELOG.md index 0442a40..b6abcf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ All notable changes to this project are documented here. The format is loosely b ## [Unreleased] ### Added +- **Codebase-Memory MCP — opt-in `--profile codebase-memory` code knowledge graph for Hermes.** + Adds `codebase-memory`, a gateway-spawned stdio MCP server wrapping the upstream + `DeusData/codebase-memory-mcp` static binary (MIT; bundled offline embeddings, no API + keys). Gives Hermes structural code navigation (`search_graph`, `trace_path`, + `get_architecture`, `get_code_snippet`, ...) over the repos under `CODE_ROOT`, mounted + read-only at `/c/dev`. The SQLite index persists in the `codebase-memory-cache` named + volume; the spawned container is `longLived` (warm across calls) and `disableNetwork` + (no egress). Image is checksum-pinned (portable/static build, v0.8.1). Enable with + `docker compose --profile codebase-memory build codebase-memory-mcp-image` then + `./scripts/mcp_add.sh codebase-memory` (set `CODE_ROOT` first). Indexing honors + `.gitignore` + a new root `.cbmignore` (defense-in-depth secret/non-source excludes). + Also adds an optional **`codebase-memory-ui`** service — the upstream interactive 3D + graph **visualization** — served at `https:///codebase-memory/` on the shared + `:443` Google-SSO origin and listed in the dashboard services section. The UI is an + absolute-asset SPA with no base-path option (its `/assets`/`/api` collide with Open + WebUI's root), so the image runs nginx that proxies the localhost-only UI and + `sub_filter`-rewrites its baked paths to the `/codebase-memory/` prefix. It indexes + the code root (`/c/dev:ro`) in its own process (in-memory; re-index after a restart). - **Voice STT/TTS services — opt-in `--profile voice` with secondary-GPU pinning.** Adds two OpenAI-compatible local speech services: `stt` (`fedirz/faster-whisper-server:latest-cuda`, `/v1/audio/transcriptions` at `http://stt:8000/v1`) and `tts` (`ghcr.io/remsky/kokoro-fastapi-gpu:latest`, diff --git a/auth/caddy/Caddyfile b/auth/caddy/Caddyfile index cf97f02..ad6418c 100644 --- a/auth/caddy/Caddyfile +++ b/auth/caddy/Caddyfile @@ -96,6 +96,17 @@ reverse_proxy comfyui:8188 } + # ---- codebase-memory 3D graph UI at /codebase-memory/ ---- + # Absolute-asset SPA served under a subpath: the codebase-memory-ui container + # runs nginx, which proxies to the UI and rewrites its baked /assets,/api,/rpc, + # /font-files to the /codebase-memory/ prefix (see codebase-memory-ui/nginx.conf). + # `handle` (no strip) keeps the prefix so nginx's location matches. Opt-in + # (--profile codebase-memory); 502s if the service isn't running. + @codebasememory path /codebase-memory /codebase-memory/* + handle @codebasememory { + reverse_proxy codebase-memory-ui:9750 + } + # ---- Open WebUI mounted at /chat (prefix stripped) ---- # /chat/* → open-webui with the /chat prefix removed, so the upstream # sees /, /c/, /models, etc. This is the canonical chat path. diff --git a/codebase-memory-mcp/Dockerfile b/codebase-memory-mcp/Dockerfile new file mode 100644 index 0000000..b3f4032 --- /dev/null +++ b/codebase-memory-mcp/Dockerfile @@ -0,0 +1,49 @@ +# syntax=docker/dockerfile:1 +# Codebase-Memory MCP server (gateway-spawned, stdio transport). +# +# Wraps the upstream DeusData/codebase-memory-mcp release binary so the stack's +# mcp-gateway can spawn it as a sibling container and expose its code knowledge +# graph tools (search_graph, trace_path, get_architecture, get_code_snippet, ...) +# to Hermes. The binary bundles its embedding model (nomic-embed-code) and runs +# 100% offline — no API keys, no network at runtime (we set disableNetwork in the +# gateway catalog entry). +# +# We use the *portable* (statically linked) release so there is no glibc coupling +# to the base image. Pinned + checksum-verified per the stack's "pin, don't float" +# rule: to bump, change CBM_VERSION and CBM_SHA256 together (sha256 comes from the +# release's checksums.txt for codebase-memory-mcp-linux-${CBM_ARCH}-portable.tar.gz). +FROM debian:bookworm-slim + +ARG CBM_VERSION=v0.8.1 +ARG CBM_ARCH=amd64 +ARG CBM_SHA256=6ab87a6c05d049dde57700803ca0ab4199fcf25973a0606618af0fcee73f5abd + +LABEL org.opencontainers.image.title="codebase-memory-mcp" \ + org.opencontainers.image.description="Code knowledge-graph MCP server (stdio) for the Ordo stack" \ + org.opencontainers.image.source="https://github.com/DeusData/codebase-memory-mcp" \ + org.opencontainers.image.licenses="MIT" \ + org.opencontainers.image.version="${CBM_VERSION}" + +# curl + ca-certificates are needed only at build time to fetch + verify the release. +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +RUN set -eux; \ + url="https://github.com/DeusData/codebase-memory-mcp/releases/download/${CBM_VERSION}/codebase-memory-mcp-linux-${CBM_ARCH}-portable.tar.gz"; \ + curl -fsSL -o /tmp/cbm.tar.gz "$url"; \ + echo "${CBM_SHA256} /tmp/cbm.tar.gz" | sha256sum -c -; \ + mkdir -p /usr/local/share/codebase-memory-mcp; \ + tar -xzf /tmp/cbm.tar.gz -C /usr/local/share/codebase-memory-mcp; \ + install -m 0755 /usr/local/share/codebase-memory-mcp/codebase-memory-mcp /usr/local/bin/codebase-memory-mcp; \ + rm -f /tmp/cbm.tar.gz + +# Index cache (SQLite) lives on a named volume mounted here by the gateway catalog +# entry; persists across container respawns. +ENV CBM_CACHE_DIR=/cache +# Keep logs on stderr / quiet so nothing pollutes the stdio JSON-RPC channel. +ENV CBM_LOG_LEVEL=error + +# Default entry point = MCP stdio server (matches the upstream MCP client config, +# which launches the bare binary with no args). +CMD ["codebase-memory-mcp"] diff --git a/codebase-memory-mcp/README.md b/codebase-memory-mcp/README.md new file mode 100644 index 0000000..f7e2b6f --- /dev/null +++ b/codebase-memory-mcp/README.md @@ -0,0 +1,64 @@ +# codebase-memory-mcp + +Gateway-spawned MCP server that gives Hermes a **structural code knowledge graph** +of the repos under your code root — call graphs, trace paths, architecture views, +symbol search — so it can navigate code instead of grepping blindly. + +It wraps the upstream [`DeusData/codebase-memory-mcp`](https://github.com/DeusData/codebase-memory-mcp) +release binary (MIT). The binary is a single static executable with a **bundled, +offline embedding model** (`nomic-embed-code`) — no API keys, no network at runtime. + +## How it's wired + +This is **not** a long-lived compose service. Like the other custom MCPs in this +stack (`qdrant-rag`, `comfyui`, `orchestration`), it is an **image** that the +`mcp-gateway` spawns as a sibling container over **stdio** when the tool is called. + +- `Dockerfile` — downloads + checksum-verifies the pinned release (portable/static + build) and sets the binary as the MCP stdio entry point. +- `docker-compose.yml` → `codebase-memory-mcp-image` (build-only, profile + `codebase-memory`) builds `ordo-ai-stack-codebase-memory-mcp:latest`. +- `mcp/gateway/registry-custom.yaml` → the `codebase-memory` catalog entry tells the + gateway how to spawn it: + - `volumes: ["PLACEHOLDER_CODE_ROOT:/c/dev:ro", "codebase-memory-cache:/cache"]` + — your code root mounted **read-only**, plus a **named volume** for the + persistent index. (`PLACEHOLDER_CODE_ROOT` is substituted from `CODE_ROOT` by + `gateway-wrapper.sh`.) + - `longLived: true` — keep the container warm across calls within a session. + - `disableNetwork: true` — the indexer needs no egress; this blocks exfiltration + of indexed code. + +Because the gateway spawns siblings via the host Docker daemon, the `/c/dev` bind +source is a **host path** and (under the gateway's 2026-06 bind-mount hardening) +must be read-only and allow-listed via `MCP_GATEWAY_DOCKER_BIND_ALLOWED_PATHS` +(set to `CODE_ROOT` in compose). The cache uses a named volume, which is exempt +from those host-path restrictions and so can be read-write. + +## Enabling it + +1. Set `CODE_ROOT` in `.env` to the **host** path that contains your repos, e.g. + `CODE_ROOT=C:/dev` (must match what Hermes sees at `/c/dev`). +2. Build the image (opt-in profile): + `docker compose --profile codebase-memory build codebase-memory-mcp-image` +3. Enable the server in the gateway: `./scripts/mcp_add.sh codebase-memory` + (the gateway hot-reloads in ~10s; no restart needed). + +## Indexing + +The index is built on demand and persists in the `codebase-memory-cache` named +volume. Hermes indexes a repo once (`index_repository` with a `/c/dev/` path), +then queries it (`search_graph`, `trace_path`, `get_architecture`, ...). Subsequent +sessions reuse the persisted index. + +## Security + +- Code root is mounted **read-only**; the container has **no network**. +- Indexing honors `.gitignore` and a project-level **`.cbmignore`** (gitignore + syntax). This repo ships a root `.cbmignore` that excludes secrets and + non-source paths as defense-in-depth; add one to each other indexed repo. +- Results are **navigation hints** — confirm in the actual file before editing. + +## Bumping the version + +Update `CBM_VERSION` + `CBM_SHA256` in the `Dockerfile` together (sha256 from the +release `checksums.txt`, `...-linux-amd64-portable.tar.gz` line), then rebuild. diff --git a/codebase-memory-ui/Dockerfile b/codebase-memory-ui/Dockerfile new file mode 100644 index 0000000..6791e02 --- /dev/null +++ b/codebase-memory-ui/Dockerfile @@ -0,0 +1,59 @@ +# syntax=docker/dockerfile:1 +# Codebase-Memory 3D graph visualization UI (long-lived service). +# +# Runs the upstream UI-variant binary (`codebase-memory-mcp --ui=true`), which +# serves an interactive 3D knowledge-graph at :9749 as a thread alongside the MCP +# server. Three upstream quirks this image works around: +# 1. The UI HTTP server binds 127.0.0.1 ONLY (src/ui/httpd.c) — unreachable from +# other containers. nginx (on 0.0.0.0:9750) proxies to 127.0.0.1:9749. +# 2. The UI is an absolute-asset SPA with no base-path option; nginx serves it +# under /codebase-memory/ and sub_filter-rewrites its baked /assets,/api,/rpc +# paths to that prefix so it can live on the shared :443 SSO origin (see +# nginx.conf). No port, no collision with Open WebUI's root. +# 3. The process is an MCP stdio server; with no client it would hit EOF on stdin +# and exit. The entrypoint keeps stdin open so the UI stays up. +# +# Shares the SAME index as the headless codebase-memory MCP via the +# `codebase-memory-cache` volume (CBM_CACHE_DIR=/cache), so it visualizes whatever +# Hermes has indexed. +# +# Pinned + checksum-verified (portable/static UI build). Bump CBM_VERSION + +# CBM_UI_SHA256 together (sha256 from the release checksums.txt, +# codebase-memory-mcp-ui-linux-${CBM_ARCH}-portable.tar.gz line). +FROM debian:bookworm-slim + +ARG CBM_VERSION=v0.8.1 +ARG CBM_ARCH=amd64 +ARG CBM_UI_SHA256=74ed63e3af2d85bfd98cbcce7191295536d0a54b084cbc0b868d69f2272172da + +LABEL org.opencontainers.image.title="codebase-memory-ui" \ + org.opencontainers.image.description="3D code knowledge-graph visualization UI for the Ordo stack" \ + org.opencontainers.image.source="https://github.com/DeusData/codebase-memory-mcp" \ + org.opencontainers.image.licenses="MIT" \ + org.opencontainers.image.version="${CBM_VERSION}" + +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl ca-certificates nginx \ + && rm -rf /var/lib/apt/lists/* + +RUN set -eux; \ + url="https://github.com/DeusData/codebase-memory-mcp/releases/download/${CBM_VERSION}/codebase-memory-mcp-ui-linux-${CBM_ARCH}-portable.tar.gz"; \ + curl -fsSL -o /tmp/cbm.tar.gz "$url"; \ + echo "${CBM_UI_SHA256} /tmp/cbm.tar.gz" | sha256sum -c -; \ + mkdir -p /usr/local/share/codebase-memory-ui; \ + tar -xzf /tmp/cbm.tar.gz -C /usr/local/share/codebase-memory-ui; \ + install -m 0755 /usr/local/share/codebase-memory-ui/codebase-memory-mcp /usr/local/bin/codebase-memory-mcp; \ + rm -f /tmp/cbm.tar.gz + +COPY nginx.conf /etc/nginx/nginx.conf +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +ENV CBM_CACHE_DIR=/cache +ENV CBM_LOG_LEVEL=error + +# nginx serves the rewritten SPA under /codebase-memory/ here; the UI itself listens +# on 127.0.0.1:9749 inside the container. +EXPOSE 9750 + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/codebase-memory-ui/README.md b/codebase-memory-ui/README.md new file mode 100644 index 0000000..fef0d64 --- /dev/null +++ b/codebase-memory-ui/README.md @@ -0,0 +1,52 @@ +# codebase-memory-ui + +Optional long-lived service that serves the **3D interactive code knowledge-graph** +from the same index the headless `codebase-memory` MCP builds — so you can *browse* +the graph, not just have Hermes query it. + +It runs the upstream UI-variant binary (`codebase-memory-mcp --ui=true --port=9749`), +which serves the visualization as a thread alongside the MCP server. + +## Two upstream quirks this image handles +1. **Absolute-asset SPA that binds `127.0.0.1` only.** The UI binds `127.0.0.1:9749` + and serves `/assets`, `/api`, `/rpc`, `/font-files` at the origin root with no + base-path option. The image runs **nginx** (on `0.0.0.0:9750`) which proxies to the + UI and `sub_filter`-rewrites those baked paths to the `/codebase-memory/` prefix + (see `nginx.conf`) — so Caddy serves it under that subpath on the shared `:443` SSO + origin without colliding with Open WebUI's root. +2. **The process is an MCP stdio server** — with no client attached it would read EOF + on stdin and exit. The entrypoint keeps stdin open (`tail -f /dev/null | …`) so the + UI stays up as a service. + +## Index (in-process) +The UI **indexes the source tree in its own long-lived process** and visualizes that +in-memory graph. It mounts the code root **read-only** at `/c/dev` (`${CODE_ROOT}`) for +this, plus the `codebase-memory-cache` volume at `/cache` for config. + +> The upstream binary does **not** reliably flush its graph index to `CBM_CACHE_DIR` +> across container exits, so the cache volume is **not** a shared index — the gateway +> MCP and the UI each index independently. Practical consequence: **the UI's graph is +> in-memory, so after a container restart you must re-index** (browse the UI's index +> action, or `POST /rpc` `index_repository`). Indexing honors `.gitignore` + `.cbmignore` +> (e.g. `secrets/`, `data/` are excluded — verified). + +## Exposure (SSO) +Served at **`https:///codebase-memory/`** on the shared `:443` origin, behind the +existing Google SSO — no dedicated port. Caddy routes `/codebase-memory/*` to this +container (the `@codebasememory` handle in `auth/caddy/Caddyfile`); nginx rewrites the +SPA's absolute paths so everything stays under the prefix. The dashboard's services +section links here via `SSO_ROUTES`. + +## Enable +``` +# set CODE_ROOT in .env first (host path of your repos), then: +docker compose --profile codebase-memory up -d --build +``` +Then browse **`https:///codebase-memory/`** (Google SSO). Index a +repo first (the UI's "index" action, or `POST /codebase-memory/rpc` `index_repository`) +or the graph will be empty. + +## Note +The UI exposes server actions (`/api/index`, `/api/process-kill`, …) to the browser; it's +SSO-gated to the single operator and network-isolated, which is the acceptable trust model +here. Pin/bump the binary via `CBM_VERSION` + `CBM_UI_SHA256` in the `Dockerfile`. diff --git a/codebase-memory-ui/entrypoint.sh b/codebase-memory-ui/entrypoint.sh new file mode 100644 index 0000000..7c62378 --- /dev/null +++ b/codebase-memory-ui/entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# Start the codebase-memory 3D graph UI as a long-lived service, served under the +# /codebase-memory/ subpath by nginx (which rewrites the SPA's absolute paths). +set -eu + +CACHE_DIR="${CBM_CACHE_DIR:-/cache}" +mkdir -p "$CACHE_DIR" + +# The UI binary is an MCP stdio server with the graph UI as a side thread (binds +# 127.0.0.1:9749). With no MCP client attached it would read EOF on stdin and exit, +# so keep stdin open (`tail -f /dev/null`) to hold the process — and the UI — up. +( tail -f /dev/null | codebase-memory-mcp --ui=true --port=9749 ) & + +# nginx (foreground = container lifecycle) proxies /codebase-memory/* -> the UI on +# 127.0.0.1:9749 and rewrites /assets,/api,/rpc,/font-files to the subpath. +exec nginx -g 'daemon off;' diff --git a/codebase-memory-ui/nginx.conf b/codebase-memory-ui/nginx.conf new file mode 100644 index 0000000..4162f9d --- /dev/null +++ b/codebase-memory-ui/nginx.conf @@ -0,0 +1,52 @@ +# Serves the codebase-memory 3D graph UI under the /codebase-memory/ subpath. +# +# The upstream UI is an absolute-asset SPA (it requests /assets, /api, /rpc, +# /font-files at the ORIGIN ROOT) and has no base-path option, so it can't share +# the :443 root with Open WebUI (which owns /assets). nginx proxies the UI under +# /codebase-memory/ and rewrites those baked absolute paths to the prefix so every +# request stays under /codebase-memory/* (no collision, single SSO origin). +# +# NOTE: the sub_filter rules below match the literal paths in the pinned v0.8.1 +# bundle — re-verify them when CBM_VERSION is bumped (curl the served JS and grep +# for any remaining bare "/api or "/assets). +worker_processes 1; +pid /run/nginx.pid; +events { worker_connections 256; } + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + access_log off; + sendfile on; + gzip on; + gzip_types text/css application/javascript application/json; + + server { + listen 9750; + server_name _; + + location = /codebase-memory { return 301 /codebase-memory/; } + + location /codebase-memory/ { + # trailing slash strips the /codebase-memory/ prefix before the upstream + proxy_pass http://127.0.0.1:9749/; + proxy_http_version 1.1; + proxy_set_header Host $host; + # disable upstream compression so sub_filter can rewrite the body + proxy_set_header Accept-Encoding ""; + + sub_filter_once off; + sub_filter_types text/html application/javascript application/json text/css; + # HTML asset refs (src="/assets/...", href="/assets/...") + sub_filter '"/assets/' '"/codebase-memory/assets/'; + sub_filter "'/assets/" "'/codebase-memory/assets/"; + # JS fetch() calls — double-quote, single-quote and template-literal forms + sub_filter '"/api/' '"/codebase-memory/api/'; + sub_filter "'/api/" "'/codebase-memory/api/"; + sub_filter '`/api/' '`/codebase-memory/api/'; + sub_filter '"/rpc"' '"/codebase-memory/rpc"'; + sub_filter '"/font-files/' '"/codebase-memory/font-files/'; + sub_filter "'/font-files/" "'/codebase-memory/font-files/"; + } + } +} diff --git a/dashboard/services_catalog.py b/dashboard/services_catalog.py index 97eeed2..343d31b 100644 --- a/dashboard/services_catalog.py +++ b/dashboard/services_catalog.py @@ -35,6 +35,15 @@ {"id": "hermes", "name": "Hermes Agent", "port": 9119, "url": "http://localhost:9119", "check": "http://hermes-dashboard:9119/", "has_gpu": False, "hint": "Managed by docker compose. Logs: docker compose logs hermes-dashboard"}, + # Opt-in (--profile codebase-memory). 3D code knowledge-graph visualization, served at + # https:///codebase-memory/ on the shared :443 SSO origin (the codebase-memory-ui + # container's nginx serves it under that subpath). The "Open" link comes from SSO_ROUTES + # in the frontend (-> /codebase-memory/), so no `url` is needed. The health check hits the + # nginx subpath, which proxies through to the UI. + {"id": "codebase-memory-ui", "name": "Codebase Memory", "port": 9750, + "check": "http://codebase-memory-ui:9750/codebase-memory/", "has_gpu": False, + "hint": "3D code knowledge-graph. Open at https:///codebase-memory/ (Google SSO). " + "In-memory index — re-index after a restart. Opt-in: --profile codebase-memory"}, ] diff --git a/dashboard/static/index.html b/dashboard/static/index.html index 873cddc..1a6a4ef 100644 --- a/dashboard/static/index.html +++ b/dashboard/static/index.html @@ -2240,6 +2240,7 @@

Dashboard login

webui: '/', n8n: '/n8n/', hermes: '/hermes/', + 'codebase-memory-ui': '/codebase-memory/', }; /** Prefer API `url` (paths + query) and match dashboard hostname. */ diff --git a/docker-compose.yml b/docker-compose.yml index 3611690..415eddb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -581,6 +581,53 @@ services: restart: "no" command: ["true"] + # Codebase-Memory MCP image (gateway-spawned, stdio). Opt-in (heavy: bundles an + # embedding model). Build with: + # docker compose --profile codebase-memory build codebase-memory-mcp-image + # then enable in the gateway with: ./scripts/mcp_add.sh codebase-memory + # (requires CODE_ROOT set to your host code root). + codebase-memory-mcp-image: + build: ./codebase-memory-mcp + image: ordo-ai-stack-codebase-memory-mcp:latest + pull_policy: build + restart: "no" + command: ["true"] + profiles: ["codebase-memory"] + + # Codebase-Memory 3D graph visualization UI (long-lived; opt-in). Visualizes the + # code knowledge-graph it indexes in its own process (mounts /c/dev:ro). The + # upstream UI binds 127.0.0.1:9749 and is an absolute-asset SPA with no base path, + # so the image runs nginx (on :9750) which proxies to it and rewrites its baked + # /assets,/api,/rpc paths to the /codebase-memory/ prefix — letting Caddy serve it + # at https:///codebase-memory/ on the shared :443 SSO origin (no extra port). + # Build with: docker compose --profile codebase-memory build codebase-memory-ui + codebase-memory-ui: + build: ./codebase-memory-ui + image: ordo-ai-stack-codebase-memory-ui:latest + pull_policy: build + restart: unless-stopped + profiles: ["codebase-memory"] + volumes: + - codebase-memory-cache:/cache + # Source tree (read-only) so the UI's own long-lived process can index and + # visualize it. The graph index lives in-process (it is not reliably flushed + # to the cache volume across container exits), so the UI indexes its own + # graph rather than depending on the gateway-spawned MCP's index. + - ${CODE_ROOT:-/c/dev}:/c/dev:ro + healthcheck: + test: ["CMD-SHELL", "curl -fsS -o /dev/null http://localhost:9750/codebase-memory/ || exit 1"] + interval: 30s + timeout: 5s + retries: 5 + start_period: 20s + networks: + - proxy-net + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + comfyui-mcp: image: ordo-ai-stack-comfyui-mcp:latest pull_policy: build @@ -716,6 +763,12 @@ services: - OPS_CONTROLLER_TOKEN=${OPS_CONTROLLER_TOKEN:-} # Injected into registry-custom.yaml for orchestration MCP (dashboard Bearer) - DASHBOARD_AUTH_TOKEN=${DASHBOARD_AUTH_TOKEN:-} + # Codebase-Memory MCP — host code root (read-only mount source for the + # spawned container) + bind-mount allowlist. CODE_ROOT must be the HOST path + # that contains your repos (what Hermes sees as /c/dev). Allow-listing it lets + # the gateway's hardened bind logic accept the read-only /c/dev mount. + - CODE_ROOT=${CODE_ROOT:-} + - MCP_GATEWAY_DOCKER_BIND_ALLOWED_PATHS=${CODE_ROOT:-} volumes: - /var/run/docker.sock:/var/run/docker.sock - ${DATA_PATH:-${BASE_PATH:-.}/data}/mcp:/mcp-config @@ -1118,6 +1171,14 @@ services: volumes: caddy_data: caddy_config: + # Per-container config/cache for codebase-memory (holds _config.db + config.json). + # NOTE: the graph index itself lives IN-PROCESS and is not reliably flushed here + # across container exits, so this is NOT a shared index — the gateway-spawned MCP + # and the UI each index in their own process. The volume is still shared/mounted + # by both; `name:` pins the literal name (no compose project prefix) to match the + # raw name the mcp-gateway uses when it spawns the MCP (-v codebase-memory-cache:/cache). + codebase-memory-cache: + name: codebase-memory-cache # Hermes data is now bind-mounted from data/hermes/ (see hermes-gateway/dashboard above). # The legacy `ordo-ai-stack_hermes-data` named volume still exists in Docker for # rollback. To revert: re-add `hermes-data:` here, then switch the hermes services' diff --git a/mcp/gateway/gateway-wrapper.sh b/mcp/gateway/gateway-wrapper.sh index 34b3d71..05d7284 100644 --- a/mcp/gateway/gateway-wrapper.sh +++ b/mcp/gateway/gateway-wrapper.sh @@ -63,7 +63,8 @@ resolve_registry_custom() { sed -e "s|PLACEHOLDER_OPS_CONTROLLER_TOKEN|${OPS_CONTROLLER_TOKEN:-}|g" \ -e "s|PLACEHOLDER_DASHBOARD_AUTH_TOKEN|${DASHBOARD_AUTH_TOKEN:-}|g" \ -e "s|PLACEHOLDER_COMFY_MCP_DEFAULT_MODEL|${COMFY_MCP_DEFAULT_MODEL:-flux1-schnell-fp8.safetensors}|g" \ - -e "s|PLACEHOLDER_N8N_API_KEY|${N8N_API_KEY:-}|g" "$src" >"$dst" + -e "s|PLACEHOLDER_N8N_API_KEY|${N8N_API_KEY:-}|g" \ + -e "s|PLACEHOLDER_CODE_ROOT|${CODE_ROOT:-}|g" "$src" >"$dst" else cp "$src" "$dst" fi diff --git a/mcp/gateway/registry-custom.yaml b/mcp/gateway/registry-custom.yaml index d3a3cca..074d680 100644 --- a/mcp/gateway/registry-custom.yaml +++ b/mcp/gateway/registry-custom.yaml @@ -82,3 +82,28 @@ registry: value: "false" - name: DISABLE_TELEMETRY value: "true" + codebase-memory: + description: "Code knowledge-graph navigation over the repos under your code root (mounted read-only at /c/dev). Structural queries grep can't do cheaply: call graphs, trace paths between symbols, architecture overviews, symbol/snippet lookup. Index is built on demand and persists in a named volume. Tools - index_repository, list_projects, index_status, search_graph, trace_path, query_graph, get_graph_schema, get_code_snippet, get_architecture, search_code, detect_changes, manage_adr, ingest_traces, delete_project. Results are navigation hints - confirm in the actual file before editing." + title: Codebase Memory + type: server + image: ordo-ai-stack-codebase-memory-mcp:latest + # Keep the indexer container warm across calls within a session. + longLived: true + # The indexer is 100% local (bundled embeddings) — no egress needed. This blocks + # exfiltration of indexed code from the spawned container. + disableNetwork: true + volumes: + # Code root (host path from $CODE_ROOT) mounted READ-ONLY. Under the gateway's + # 2026-06 bind-mount hardening, host binds must be read-only AND allow-listed via + # MCP_GATEWAY_DOCKER_BIND_ALLOWED_PATHS (set to CODE_ROOT in docker-compose.yml). + # PLACEHOLDER_CODE_ROOT is substituted from $CODE_ROOT by gateway-wrapper.sh. + - "PLACEHOLDER_CODE_ROOT:/c/dev:ro" + # Persistent SQLite index — a NAMED volume (exempt from host-path restrictions, + # so it can be read-write). Survives container respawns. + - "codebase-memory-cache:/cache" + env: + - name: CBM_CACHE_DIR + value: /cache + # Keep logs off stdout so they can't corrupt the stdio JSON-RPC channel. + - name: CBM_LOG_LEVEL + value: error diff --git a/ops-controller/main.py b/ops-controller/main.py index 205740f..d712321 100644 --- a/ops-controller/main.py +++ b/ops-controller/main.py @@ -60,7 +60,7 @@ # Services we allow operations on (allowlist) ALLOWED_SERVICES = { "llamacpp", "llamacpp-embed", "dashboard", "open-webui", "model-gateway", "mcp-gateway", - "comfyui", "n8n", "qdrant", "stt", "tts", + "comfyui", "n8n", "qdrant", "stt", "tts", "codebase-memory-ui", } # .env keys we allow updating via the API