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>
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_asyncdrops data in CLI scripts — uses daemon threads that die on process exit. Use synccapturein any short-lived script context.capture_asyncis only safe in long-running processes (servers, daemons). -
type: "http"required for Claude Code MCP — nottype: "sse". They are different protocols. SSE is deprecated. Usingsseagainst this server silently fails to connect. -
MCP registration goes in
~/.claude.json— not~/.claude/settings.json. Always useclaude mcp add --scope userto register. Never hand-editmcpServersinsettings.json— that key is ignored by Claude Code's MCP loader. -
JSONB filter —
metadata->>'id'is exposed as?metadata_id=in list/delete endpoints. Only the top-levelidfield in metadata is indexed this way. -
Re-embedding on update —
PATCH /v1/thoughts/{id}calls Ollama again only whencontentchanges. Metadata-only updates skip re-embedding.