A personal memory server for AIs.
Mem0 + Qdrant
behind the Model Context Protocol — so any Claude or ChatGPT
session can search and store your long-term memories.
- Semantic recall over your own corpus (vector search), with optional domain/hall/room/topic routing for sharper retrieval.
- Two MCP endpoints: a full read+write one for the Claude.ai connector, and a lean, deep-research-shaped read (optionally write) one for ChatGPT.
- OAuth 2.1 shim (PKCE, dynamic client registration, revocable resource-scoped tokens) so the Claude.ai Custom Connector can authenticate.
- No GPU, no chat LLM required for retrieval — just an embedding model and a vector store. Runs on a small CPU box.
Reliquary is the serving engine. Building a corpus from your own notes/exports is left to you — it ingests any JSONL of
{"id", "text", "metadata"}records.
📖 Full guide: docs/GUIDE.md (also on the
project wiki) covers architecture,
ingestion (with an Obsidian example), embedder choices (Ollama / LM Studio /
external APIs), auth, and external-access options (Cloudflare Tunnel / Tailscale
/ ngrok).
cp .env.example .env # set MEM0_CLAUDE_MCP_TOKEN etc.
cp config.example.yaml config.yaml # point at the qdrant + embedder services
docker compose up -d
curl -s http://127.0.0.1:8787/healthzThree services come up: qdrant (vector store), embedder (Ollama serving
the multilingual 768-dim nomic-embed-text on CPU; a one-shot embedder-pull
fetches the model on first boot), and app (this server: MCP + OAuth + the
Mem0 client). Publish :8787 only behind a TLS terminator — a
Cloudflare Tunnel,
Caddy, or Traefik.
Embedder: the compose default is Ollama +
nomic-embed-text(mem0 talks to it via Ollama's OpenAI-compatible/v1/embeddings). To use a different model or an external embeddings API, edit theembedderblock inconfig.yamland keepembedding_model_dimsin sync with the model. Changing the embedder changes the vector space — re-ingest your corpus afterwards.
| Endpoint | For | Auth | Tools |
|---|---|---|---|
POST /claude/mcp |
Claude.ai Custom Connector | Bearer or OAuth | mem0_status, mem0_search, mem0_fetch, mem0_add_memory, mem0_delete |
POST /openai/mcp |
ChatGPT / OpenAI-compatible | Bearer (or no-auth) | search, fetch (lean snippet shape); add_memory + delete if MEM0_OPENAI_ALLOW_WRITE=true |
Also: GET /healthz (minimal, public), GET /status and GET /mem0/search?q=...
(both require the Claude bearer — they return config/taxonomy and raw
memories respectively), and the OAuth 2.1 discovery/authorize/token/revoke routes
under /.well-known/* and /oauth/*.
Add a Custom Connector pointing at https://your-host/claude/mcp. Claude runs
the OAuth flow; you authorize once by pasting your MEM0_CLAUDE_MCP_TOKEN into
the /oauth/authorize page, and the connector receives a derived, revocable
token (not the master). After it registers, pin MEM0_OAUTH_CLIENT_ID and set
MEM0_OAUTH_ALLOW_REGISTRATION=false.
Add an MCP server at https://your-host/openai/mcp with API key auth
(Bearer scheme) using MEM0_OPENAI_MCP_TOKEN. Keep
MEM0_OPENAI_ALLOW_NOAUTH=false so the token is required.
Behaviour is driven by env vars (see .env.example) and a Mem0 config file (see config.example.yaml). Highlights:
| Variable | Purpose |
|---|---|
MEM0_CLAUDE_MCP_TOKEN |
bearer for /claude/mcp (required for write + OAuth) |
MEM0_OPENAI_MCP_TOKEN |
bearer for /openai/mcp |
MEM0_OPENAI_ALLOW_NOAUTH |
true to allow unauthenticated /openai/mcp (default false) |
MEM0_OPENAI_ALLOW_WRITE |
expose add_memory + delete on /openai/mcp (default false). Refuses to start with ALLOW_NOAUTH=true, to avoid public write. |
MEM0_OAUTH_CLIENT_ID / MEM0_OAUTH_ALLOW_REGISTRATION |
lock the OAuth shim to one known client |
MEM0_DATASET_PATH |
curated JSONL enabling taxonomy routing + fetch bootstrap docs |
Run python app/server.py --help for the full flag list.
- Default-closed.
/openai/mcprequires a bearer by default; the write tool is opt-in and cannot be combined with no-auth. - OAuth tokens are derived & revocable (resource-scoped, 30-day expiry), held in memory — a restart invalidates them and clients re-authorize.
- Embedded vs server Qdrant. Reads run concurrently only against a Qdrant server; an embedded on-disk store is auto-detected and serialized (it is not read-thread-safe).
- Publish only behind TLS, and prefer narrowing your reverse proxy to the
/claude/*,/openai/*,/oauth/*,/.well-known/*,/healthzpaths.
# JSONL: one {"id": "...", "text": "...", "metadata": {...}} per line.
python app/ingest.py path/to/corpus.jsonl --config config.yaml --user-id defaultmetadata.title is used as the result title; domain/hall/room/topic
enable routing; any source_url/source_ref becomes the document URL.
Building the JSONL from your own notes is up to you.
examples/obsidian_to_jsonl.py is a runnable
starting point that walks an Obsidian vault and derives the taxonomy from your
folder layout — see docs/GUIDE.md for a full walkthrough.
pip install -r requirements.txt
python -m pytest # or: python tests/test_helpers.py
python -m py_compile app/*.pyThe helpers / catalog / runtime / oauth modules are dependency-light and
unit-tested without the Mem0/Qdrant stack.
Mem0 (memory layer), Qdrant (vectors), and Ollama (embeddings). Licensed under MIT.