Files
openbrain/README.md
Jason Woltje 934a6fc0f4
All checks were successful
ci/woodpecker/push/build Pipeline was successful
docs: add README with REST API, MCP setup, and Claude Code gotchas
Documents full CRUD REST API, MCP tool list, Claude Code registration
procedure (claude mcp add --scope user --transport http), and critical
gotchas:
- MCP config belongs in ~/.claude.json not ~/.claude/settings.json
- transport must be 'http' not 'sse' for FastMCP streamable HTTP
- capture_async daemon thread issue in CLI scripts
- JSONB metadata filter pattern
- re-embedding behavior on PATCH

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 21:34:33 -06:00

6.4 KiB

OpenBrain

Self-hosted semantic brain — pgvector + REST API + MCP server for any AI agent.

Stores and retrieves decisions, context, gotchas, and project state via semantic search. Works across all agent harnesses (Claude Code, Codex, OpenCode) in the same session or across sessions.

What It Does

  • Capture thoughts with natural-language content, a source tag, and arbitrary metadata
  • Search by meaning using pgvector cosine similarity (Ollama embeddings)
  • Retrieve recent thoughts or filter by source/metadata
  • Full CRUD — get, update, delete single or bulk by filter

Stack

  • FastAPI + asyncpg (PostgreSQL + pgvector)
  • Ollama for embeddings (bge-m3:latest, 1024-dim)
  • FastMCP for MCP server (streamable HTTP transport)
  • Deployed via Docker Swarm + Portainer

REST API

All endpoints require Authorization: Bearer <token>.

BASE=https://your-openbrain-host

# Capture
curl -X POST $BASE/v1/thoughts \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content": "...", "source": "agent-name", "metadata": {}}'

# Search (semantic)
curl -X POST $BASE/v1/search \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query": "your search", "limit": 5}'

# Recent
curl $BASE/v1/thoughts/recent?limit=10 -H "Authorization: Bearer $TOKEN"

# Get by ID
curl $BASE/v1/thoughts/{id} -H "Authorization: Bearer $TOKEN"

# Update (re-embeds if content changes)
curl -X PATCH $BASE/v1/thoughts/{id} \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content": "updated text", "metadata": {"key": "val"}}'

# Delete single
curl -X DELETE $BASE/v1/thoughts/{id} -H "Authorization: Bearer $TOKEN"

# Bulk delete by filter (source and/or metadata_id required)
curl -X DELETE "$BASE/v1/thoughts?source=agent-name&metadata_id=my-entity" \
  -H "Authorization: Bearer $TOKEN"

# List with filters
curl "$BASE/v1/thoughts?source=agent-name&limit=20" -H "Authorization: Bearer $TOKEN"

# Stats
curl $BASE/v1/stats -H "Authorization: Bearer $TOKEN"

MCP Server (Claude Code)

OpenBrain exposes an MCP server at /mcp using FastMCP's streamable HTTP transport.

Registering in Claude Code

MCPs must be registered in ~/.claude.json via the CLI — not in ~/.claude/settings.json. settings.json does not load MCP servers. Edits there are silently ignored.

# Register once per machine (user scope = persists across all projects)
claude mcp add --scope user --transport http openbrain https://your-openbrain-host/mcp \
  --header "Authorization: Bearer YOUR_TOKEN"

# Verify it registered
claude mcp list

Transport must be http — not sse. FastMCP uses streamable HTTP. type: "sse" is a different (deprecated) protocol and will silently fail to connect.

Available MCP Tools

Once registered, the following tools are available natively in Claude Code sessions:

Tool Description
capture Store a thought with content, source, and metadata
search Semantic search by meaning
recent List most recently added thoughts
get Retrieve a thought by ID
update Update content, source, or metadata (re-embeds if content changes)
delete Delete a thought by ID
delete_where Bulk delete by source and/or metadata ID
list_thoughts List with source/metadata filters and pagination
stats Total counts and per-source breakdown

When to Use Each Tool

Trigger Action
Session start search or recent to load prior context
Discovery or gotcha capture immediately
Significant decision capture with rationale
Task or milestone complete capture summary
Cross-agent handoff capture current state before ending
Repeated migration delete_where + recapture (upsert pattern)

Environment Variables

Variable Required Description
DATABASE_URL Yes PostgreSQL connection string with pgvector
API_KEY Yes Bearer token for authentication
OLLAMA_URL Yes Ollama base URL (e.g. http://10.1.1.42:11434)
OLLAMA_MODEL No Embedding model (default: bge-m3:latest)
MCP_ALLOWED_HOSTS Yes Comma-separated allowed hostnames for DNS rebinding protection

Deployment

Docker Compose (Portainer / Swarm)

services:
  openbrain:
    image: your-registry/openbrain:sha-<digest>
    environment:
      DATABASE_URL: postgresql://user:pass@db:5432/openbrain
      API_KEY: your-api-key
      OLLAMA_URL: http://ollama-host:11434
      MCP_ALLOWED_HOSTS: your-openbrain-host.com
    ports:
      - "8000:8000"

See docker-compose.portainer.yml for the full Swarm stack definition.

Database Setup

Requires PostgreSQL with the vector extension:

CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE thoughts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    content TEXT NOT NULL,
    embedding vector(1024),
    source TEXT NOT NULL DEFAULT '',
    metadata JSONB NOT NULL DEFAULT '{}',
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON thoughts USING ivfflat (embedding vector_cosine_ops);
CREATE INDEX ON thoughts (source);
CREATE INDEX ON thoughts ((metadata->>'id'));

Development

# Install deps
pip install -e ".[dev]"

# Run locally
uvicorn src.main:app --reload

# Lint + format
python -m ruff check src/
python -m ruff format src/

# Tests
pytest

Known Gotchas

  • capture_async drops data in CLI scripts — uses daemon threads that die on process exit. Use sync capture in any short-lived script context. capture_async is only safe in long-running processes (servers, daemons).

  • type: "http" required for Claude Code MCP — not type: "sse". They are different protocols. SSE is deprecated. Using sse against this server silently fails to connect.

  • MCP registration goes in ~/.claude.json — not ~/.claude/settings.json. Always use claude mcp add --scope user to register. Never hand-edit mcpServers in settings.json — that key is ignored by Claude Code's MCP loader.

  • JSONB filtermetadata->>'id' is exposed as ?metadata_id= in list/delete endpoints. Only the top-level id field in metadata is indexed this way.

  • Re-embedding on updatePATCH /v1/thoughts/{id} calls Ollama again only when content changes. Metadata-only updates skip re-embedding.