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
38 changes: 38 additions & 0 deletions .cbmignore
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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://<CADDY_TAILNET_HOSTNAME>/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).
Expand Down
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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://<host>/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`,
Expand Down
11 changes: 11 additions & 0 deletions auth/caddy/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -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/<id>, /models, etc. This is the canonical chat path.
Expand Down
49 changes: 49 additions & 0 deletions codebase-memory-mcp/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
64 changes: 64 additions & 0 deletions codebase-memory-mcp/README.md
Original file line number Diff line number Diff line change
@@ -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/<repo>` 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.
59 changes: 59 additions & 0 deletions codebase-memory-ui/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
52 changes: 52 additions & 0 deletions codebase-memory-ui/README.md
Original file line number Diff line number Diff line change
@@ -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://<host>/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://<CADDY_TAILNET_HOSTNAME>/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`.
16 changes: 16 additions & 0 deletions codebase-memory-ui/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -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;'
Loading
Loading