Personal AI memory system with semantic search. Any MCP-compatible AI (Claude Code, Claude Desktop) can store and retrieve memories through a standardized interface. Thoughts are stored as vector embeddings enabling meaning-based search, not just keyword matching.
Inspired by Nate B. Jones' OB1.
Claude Code / Desktop / Web UI
|
v
FastAPI Server (port 3000)
|
+----|------------+
| | |
v v v
Postgres+pgvector Ollama
(thoughts, vectors) (embeddings + LLM)
All services run locally. No cloud dependencies. Zero ongoing API cost.
- macOS (tested on Mac Mini M1)
- PostgreSQL 17 with pgvector extension
- Ollama with
nomic-embed-text(embeddings) andllama3.1:8b(LLM features) - Python 3.12+
# Install infrastructure
brew install postgresql@17 pgvector ollama
brew services start postgresql@17
brew services start ollama
ollama pull nomic-embed-text
ollama pull llama3.1:8b
# Create database
createdb openbrain
psql -d openbrain -c "CREATE EXTENSION IF NOT EXISTS vector;"
# Create schema (see Database Schema section below)
# Install and run
cd openbrain-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python main.pyThe server starts on http://0.0.0.0:3000.
CREATE TABLE thoughts (
id BIGSERIAL PRIMARY KEY,
content TEXT NOT NULL,
embedding VECTOR(768),
tsv TSVECTOR,
task_type TEXT,
tags TEXT[],
source TEXT,
user_id BIGINT REFERENCES users(id) NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE thought_links (
id BIGSERIAL PRIMARY KEY,
source_id BIGINT NOT NULL REFERENCES thoughts(id) ON DELETE CASCADE,
target_id BIGINT NOT NULL REFERENCES thoughts(id) ON DELETE CASCADE,
relation_type TEXT DEFAULT 'related',
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(source_id, target_id)
);
CREATE TABLE task_types (
name TEXT PRIMARY KEY,
color TEXT NOT NULL DEFAULT '#94a3b8',
description TEXT
);
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
brain_key TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE search_log (
id BIGSERIAL PRIMARY KEY,
query TEXT NOT NULL,
mode TEXT DEFAULT 'hybrid',
result_count INTEGER,
user_id BIGINT REFERENCES users(id) DEFAULT 1,
created_at TIMESTAMPTZ DEFAULT NOW()
);A trigger auto-populates the tsv column from content on insert/update. Indexes exist on created_at DESC, tsv (GIN), user_id, and both link foreign keys.
All config via environment variables (see config.py):
| Variable | Default | Description |
|---|---|---|
OPENBRAIN_PORT |
3000 |
Server port |
OPENBRAIN_DB_URL |
postgresql://richard@localhost/openbrain |
Postgres connection |
OPENBRAIN_OLLAMA_URL |
http://localhost:11434 |
Ollama endpoint |
OPENBRAIN_EMBED_MODEL |
nomic-embed-text |
Embedding model (768 dims) |
OPENBRAIN_BRAIN_KEY |
generated | API key for authentication |
Known devices are mapped in KNOWN_DEVICES in config.py for automatic source tagging based on client IP.
All requests require a brain key via:
- Header:
x-brain-key: YOUR_KEY - Query param:
?key=YOUR_KEY
| Tool | Description |
|---|---|
capture_thought |
Store a memory with embedding. Checks for near-duplicates (>95% similarity). Supports linked_to for auto-linking. Source auto-tagged by device IP. |
search_thoughts |
Search with mode: hybrid (default), semantic, or keyword. Supports filters: task_type, tags, since, until. |
browse_thoughts |
Chronological listing with optional type/date filters. |
update_thought |
Edit content (re-generates embedding), type, or tags. |
delete_thought |
Hard delete by ID. |
- Semantic — Embeds your query and finds closest vectors by cosine similarity
- Keyword — Full-text search using PostgreSQL
tsvector/tsquery - Hybrid — Combines both:
0.7 * semantic + 0.3 * normalized_text_rank
| Tool | Description |
|---|---|
related_thoughts |
Find nearest semantic neighbors of a thought. |
link_thoughts |
Create a named relationship between two thoughts. |
get_thought_links |
Get a thought with all linked thoughts (both directions). |
| Tool | Description |
|---|---|
suggest_tags |
LLM suggests tags and task_type for content. |
summarize |
Pulls related thoughts, LLM synthesizes a summary. |
generate_digest |
Summarizes a full day's thoughts into a digest entry. |
| Tool | Description |
|---|---|
stats |
Total count, by-type breakdown, oldest/newest. |
search_history |
Recent search queries with result counts. |
create_task_type |
Add a category with color. |
list_task_types |
List all categories. |
export_thoughts |
All thoughts as JSON (no embeddings). |
create_user |
New user with isolated brain key. |
list_users |
List all users. |
Standard JSON-RPC:
curl -X POST http://localhost:3000/mcp \
-H "x-brain-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_thoughts","arguments":{"query":"feedback"}}}'Legacy format (simpler, for testing):
curl -X POST http://localhost:3000/mcp \
-H "x-brain-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"tool":"search_thoughts","query":"feedback"}'SSE streaming:
GET /mcp/sse?key=YOUR_KEY&tool=stats&args={}
Available at http://localhost:3000/?key=YOUR_KEY
Tabs: Search, Capture, Browse, Timeline, Summarize, Types, Map
- Search — Hybrid/semantic/keyword with collapsible filters, similarity % badges
- Capture — Auto-tag via LLM, bulk import from .txt/.json files
- Timeline — Thoughts grouped by day with color-coded types
- Summarize — LLM-synthesized answers from stored memories
- Map — 2D PCA projection of all thought embeddings
Keyboard shortcuts: Cmd+K search, Cmd+N capture, Escape blur
Theme: Dark/light toggle (persisted in localStorage)
Every thought card has edit, related, and delete actions.
| Endpoint | Description |
|---|---|
POST /mcp |
MCP tool calls (JSON-RPC + legacy) |
GET /mcp/sse |
SSE streaming transport |
GET / |
Web UI |
GET /export?key= |
JSON export of all thoughts |
GET /backup?key= |
Full pg_dump SQL download |
GET /metrics |
Prometheus-compatible metrics |
GET /api/embeddings?key= |
Raw embeddings for visualization |
In-memory token bucket: 100 requests/minute per client IP. Returns 429 with Retry-After header when exceeded.
Prometheus-compatible at /metrics:
openbrain_tool_calls_total{tool="..."}— call countsopenbrain_tool_duration_seconds{tool="..."}— latency
launchctl load ~/Library/LaunchAgents/com.hasting.openbrain-mcp.plistRuns at boot, auto-restarts on crash. Logs: /tmp/openbrain-mcp.log, /tmp/openbrain-mcp-error.log.
hooks/passive-capture.sh can be wired into Claude Code hooks to auto-capture file edits and commands:
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write|Bash",
"command": "/path/to/openbrain-mcp/hooks/passive-capture.sh"
}]
}
}claude mcp add open-brain http://localhost:3000/mcp \
-t http -s user \
-H "x-brain-key: YOUR_KEY"For remote machines, use the server's LAN IP instead of localhost.
openbrain-mcp/
├── main.py # FastAPI server, MCP routing, endpoints
├── config.py # Environment config, known devices
├── db.py # asyncpg connection pool
├── embeddings.py # Ollama embedding calls
├── middleware.py # Rate limiter
├── metrics.py # Prometheus metrics collector
├── requirements.txt
├── hooks/
│ └── passive-capture.sh
├── static/
│ └── index.html # Web UI (single file)
└── tools/
├── autotag.py # LLM tag suggestions
├── browse.py # Chronological listing
├── capture.py # Thought capture with dedup + linking
├── contradictions.py # LLM contradiction detection
├── delete.py # Hard delete
├── digest.py # Daily digest generation
├── export.py # JSON export
├── links.py # Thought relationships
├── related.py # Nearest neighbors
├── search.py # Hybrid/semantic/keyword search
├── search_history.py # Query logging
├── stats.py # Aggregate statistics
├── summarize.py # LLM summarization
├── task_types.py # Category management
├── update.py # Edit with re-embedding
��── users.py # Multi-user management