Hippocampus
by karrolcia
README.md
# Hippocampus
Universal memory for AI. One server, every platform.
Your AI shouldn't forget who you are just because you switched apps.
---
Hippocampus is an open-source, self-hosted MCP memory server. Deploy it once. Connect it to Claude, ChatGPT, Gemini, Cursor, Perplexity — anything that speaks MCP. Tell one AI about a project decision, and every other AI already knows.
## The problem
Every AI platform silos your context:
- Claude.ai memory doesn't work in Claude Code
- Claude Code's CLAUDE.md files don't work in ChatGPT
- ChatGPT's memory doesn't work in Gemini
- None of them talk to each other
You repeat yourself constantly. Context gets lost. Continuity breaks every time you switch tools.
## How it works
```
Claude.ai ────────────┐
Claude Code ──────────┤
Claude Desktop ───────┤
ChatGPT ──────────────┼── MCP ──▶ Hippocampus
Gemini CLI ───────────┤ (your server)
Cursor / Windsurf ────┤
Perplexity ───────────┘
```
[MCP](https://modelcontextprotocol.io/) (Model Context Protocol) is the open standard every major AI platform has adopted. Hippocampus is a remote MCP server that exposes memory tools over Streamable HTTP — `remember`, `recall`, `forget`, and eight more. Any AI client that supports MCP can connect.
Data model: knowledge graph with entities, observations, and relationships. Semantic search via local embeddings. Entire database encrypted at rest with SQLCipher (AES-256) — including embedding vectors, which [leak original text](https://arxiv.org/abs/2305.03010).
## Try it in 5 minutes
You need Node.js 20+ installed.
**1. Clone and install**
```bash
git clone https://github.com/karrolcia/hippocampus.git
cd hippocampus
npm install
```
**2. Create your `.env` file**
```bash
cp .env.example .env
```
Open `.env` and set your passphrase (this encrypts the database):
```env
HIPPO_PASSPHRASE=any-secret-phrase-you-want
```
That's the only required value. Everything else has defaults.
**3. Start the server**
```bash
npm run dev
```
You should see:
```
Hippocampus starting on http://0.0.0.0:3000
MCP endpoint: http://0.0.0.0:3000/mcp
```
The embedding model (~80MB) downloads automatically on first run — this takes a minute the first time.
**4. Verify it's alive**
```bash
curl http://localhost:3000/health
```
Expected response:
```json
{"status":"ok","version":"0.3.1"}
```
**5. Connect Claude Code**
```bash
claude mcp add hippocampus --transport http http://localhost:3000/mcp
```
**6. Try it**
Open a Claude Code session and say:
> Remember that my preferred language is TypeScript and I use Hono as my web framework.
Then in a new session:
> What do you know about my tech preferences?
If it comes back with TypeScript and Hono, it's working. Your AI now has persistent memory.
## Deploy to a server
Local is great for trying it out. To use Hippocampus across all your AI tools — Claude.ai, ChatGPT, Gemini, mobile — you need it running on a public URL with HTTPS.
### What you need
- A VPS (Hetzner CX22 at ~4 EUR/month is plenty — EU jurisdiction, GDPR)
- A domain (or subdomain) pointed at the VPS
- ~20 minutes
### Step 1: Set up the server
SSH into your VPS and install Docker:
```bash
# Firewall
ufw default deny incoming && ufw default allow outgoing
ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp
ufw enable
# Install Docker
curl -fsSL https://get.docker.com | sh
```
### Step 2: Point your domain
Add an A record in your DNS provider:
```
Type: A
Name: hippo (or whatever subdomain you want)
Value: <your VPS IP>
```
DNS propagation usually takes a few minutes. Verify it resolves before continuing:
```bash
dig hippo.yourdomain.com +short
# Should return your VPS IP
```
### Step 3: Configure
```bash
git clone https://github.com/karrolcia/hippocampus.git
cd hippocampus
chmod +x setup.sh
./setup.sh
```
The script asks for your domain, username, and password, then writes `.env` and `Caddyfile` for you. No Node.js required.
Caddy handles TLS certificates automatically via Let's Encrypt.
<details>
<summary>Manual setup (if you prefer)</summary>
```bash
cp .env.example .env
```
Generate a passphrase and an OAuth password hash:
```bash
# Generate a random passphrase — save this in your password manager
openssl rand -base64 32
# Generate a hash of the password you'll use to log in
# Replace 'your-password' with your actual password
echo -n 'your-password' | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '='
```
Edit `.env` with all required values:
```env
HIPPO_PASSPHRASE=<output of openssl rand -base64 32>
HIPPO_OAUTH_ISSUER=https://hippo.yourdomain.com
HIPPO_OAUTH_USER=admin
HIPPO_OAUTH_PASSWORD_HASH=<output of the hash command above>
```
Edit `Caddyfile` — replace the domain on the first line:
```caddy
hippo.yourdomain.com {
```
</details>
### Step 4: Start and verify
```bash
docker compose up -d
```
Wait ~30 seconds for the containers to start and Caddy to get a certificate, then:
```bash
curl https://hippo.yourdomain.com/health
```
Expected response:
```json
{"status":"ok","version":"0.3.1"}
```
If you get a certificate error, DNS might not have propagated yet. Wait a few minutes and retry.
### Step 5: Connect your AI tools
Now that your server is live, connect each platform you use.
**Claude Code:**
```bash
claude mcp add hippocampus --transport http https://hippo.yourdomain.com/mcp
```
**Claude.ai (browser + mobile):**
Settings > Integrations > Add custom integration > Enter your server URL:
```
https://hippo.yourdomain.com/mcp
```
You'll be redirected to log in with the username and password you configured in Step 3.
**Claude Desktop:**
Add to your MCP config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
```json
{
"mcpServers": {
"hippocampus": {
"url": "https://hippo.yourdomain.com/mcp"
}
}
}
```
**ChatGPT (web + mobile):**
Settings > Apps > Developer Mode > Create a new app > Set server URL to `https://hippo.yourdomain.com/mcp`
**Gemini CLI:**
Add to `~/.gemini/settings.json`:
```json
{
"mcpServers": {
"hippocampus": {
"uri": "https://hippo.yourdomain.com/mcp"
}
}
}
```
**Cursor / Windsurf / VS Code:**
Add to your MCP configuration file:
```json
{
"mcpServers": {
"hippocampus": {
"url": "https://hippo.yourdomain.com/mcp"
}
}
}
```
**Verify:** Open any connected platform and ask your AI to remember something. Switch to a different platform and ask it to recall. If it works across platforms, you're done.
### Alternative: Fly.io
If you don't want to manage a VPS. ~$5/month.
```bash
git clone https://github.com/karrolcia/hippocampus.git
cd hippocampus
fly launch # Creates app + Dockerfile detected automatically
fly volumes create hippo_data --size 1
```
Edit the generated `fly.toml` — add a volume mount so the database persists across deploys:
```toml
[mounts]
source = "hippo_data"
destination = "/data"
```
Generate your secrets — save both values in your password manager:
```bash
# Generate passphrase (save this — you lose your database without it)
openssl rand -base64 32
# Generate hash of your login password (replace 'your-password')
echo -n 'your-password' | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '='
```
Set them as Fly secrets and deploy:
```bash
fly secrets set HIPPO_PASSPHRASE=<generated passphrase>
fly secrets set HIPPO_OAUTH_ISSUER=https://<your-app>.fly.dev
fly secrets set HIPPO_OAUTH_USER=admin
fly secrets set HIPPO_OAUTH_PASSWORD_HASH=<generated hash>
fly deploy
```
Verify:
```bash
curl https://<your-app>.fly.dev/health
```
Fly handles HTTPS automatically. Connect your AI tools using `https://<your-app>.fly.dev/mcp` as the server URL.
### Alternative: Home server + Cloudflare Tunnel
Free, maximum control. Run Hippocampus on any machine at home and expose it via [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/) — no port forwarding, no static IP needed.
```bash
# On your home machine
git clone https://github.com/karrolcia/hippocampus.git
cd hippocampus
cp .env.example .env
```
Edit `.env` — set passphrase and OAuth variables (skip `setup.sh` here — it configures Caddy, which you don't need with Cloudflare Tunnel):
```bash
# Generate values
openssl rand -base64 32 # → HIPPO_PASSPHRASE
echo -n 'your-password' | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=' # → HIPPO_OAUTH_PASSWORD_HASH
```
```env
HIPPO_PASSPHRASE=<generated passphrase>
HIPPO_OAUTH_ISSUER=https://hippo.yourdomain.com
HIPPO_OAUTH_USER=admin
HIPPO_OAUTH_PASSWORD_HASH=<generated hash>
```
```bash
docker compose up -d hippocampus # only hippocampus — Caddy not needed
# Install cloudflared and create a tunnel
cloudflared tunnel create hippocampus
cloudflared tunnel route dns hippocampus hippo.yourdomain.com
```
Create `~/.cloudflared/config.yml`:
```yaml
tunnel: hippocampus
ingress:
- hostname: hippo.yourdomain.com
service: http://localhost:3000
- service: http_status:404
```
Start the tunnel:
```bash
cloudflared tunnel run hippocampus
```
You're responsible for uptime and physical security. See [SECURITY.md](SECURITY.md) for trade-offs.
## Tools
| Tool | Description |
|------|-------------|
| `remember` | Store a fact, preference, or piece of context. Optional `kind` classification (fact, decision, question, preference, or custom) and `importance` weighting. Reports overlapping observations so the AI can consolidate incrementally. Returns `version_hash` for cache invalidation. |
| `recall` | Search memories by semantic similarity + keyword match. Filter by `type`, `kind`, `since`. Use `spread: true` to follow relationships and discover connected memories. Includes `version_hash` per entity in all formats. |
| `get_observation` | Read ONE observation by its `observation_id` — the only id-taking tool that does not destroy anything. Use it to see what an id addresses before `merge` or `forget`. Bounded to the single row named; leaves `recall_count` untouched, unlike `recall`. |
| `context` | Get everything about a topic — observations, relationships, related entities. Includes `version_hash`. |
| `update` | Replace an existing observation with new content. Returns `version_hash`. |
| `forget` | Permanently delete a memory or entity (secure deletion) |
| `merge` | Merge multiple observations into one (atomic consolidation). Returns `version_hash`. |
| `merge_entities` | Merge multiple entities into one — moves all data, deletes sources. Returns `version_hash`. |
| `consolidate` | Find clusters of similar/duplicate memories, detect near-duplicate entities, surface contradictions, or run sleep mode for batch lifecycle analysis (compress/prune/refresh) |
| `export` | Export as CLAUDE.md context file, readable markdown, JSON, wire format, or Obsidian vault |
| `check_version` | "Did anything change?" — pass an entity name + cached hash, get back yes/no. No embedding computation, pure metadata. |
| `onboard` | Bootstrap memory from a new AI session. Returns structured extraction instructions the AI follows to capture user context. |
`since` is a UTC lower bound and accepts `YYYY-MM-DD`, `YYYY-MM-DD HH:MM:SS`, `2026-08-25T10:36:55` (no zone — read as UTC), and ISO-8601 with a zone (`2026-08-25T10:36:55Z`, `2026-08-25T13:36:55+03:00` — offsets are converted, not stripped). A value that can't be parsed is an **error**, never an empty result set: on a "what landed since my last run" query, silently returning nothing is indistinguishable from nothing having landed, and that's the one answer a memory server must never guess at.
The AI calls these tools naturally. You don't manage memory manually — you just talk to your AI and it remembers.
### Spreading activation
When you `recall` with `spread: true`, Hippocampus doesn't just return direct matches — it follows relationships one hop out from matched entities and scores their observations against your query. Related observations get a dampened score (0.5x decay), so they surface when relevant but don't drown out direct hits. Useful for questions that span multiple related topics.
### Contradiction detection
`consolidate` with `mode: "contradictions"` finds observation pairs that talk about the same thing (high embedding similarity) but say different things (low word overlap). No LLM required — pure embedding math plus Jaccard comparison. Review the flagged pairs and decide what to keep.
### Novelty scoring
Every `remember` call returns a `novelty` score (0–1) computed via SVD subspace projection. Pairwise cosine checks miss aggregate redundancy — five observations with moderate individual overlap can collectively explain a new observation entirely. Subspace projection compares against all existing observations simultaneously. When novelty drops below 0.1, the response warns that the information may already be captured.
### Near-match detection
When `remember` stores a new observation, it reports existing observations that overlap (cosine similarity 0.5–0.85) — the zone between "clearly different" and "near-duplicate." The AI sees these in the response and can consolidate immediately instead of waiting for a batch `consolidate` pass. No extra computation: the dedup scan already compares against all entity embeddings.
`near_matches` also carries any match above 0.85 that the dedup guards held back — an append-only entity, or an observation from a different UTC day (see [Append-safe writes](#append-safe-writes)). Overlap that isn't acted on is still overlap worth seeing.
Each match's `content` is **capped at 200 characters** — anything longer comes back as a preview — plus, off append-only entities, the `observation_id` that addresses it. The cap bounds the response, not what you can do with it: quoting three overlapping observations in full could push a *successful* write past a client's token cap, and a caller that sees only a size error cannot tell a rejected response from a rejected write. Each non-append-only match also carries `created_at` and `kind`, because overlapping observations tend to share an opening and their previews can be identical. To consolidate, re-read the near match with `recall` (which returns `observation_id` alongside full content) and then `merge` it with the observation this response just stored — `merge` keeps only the content you pass it, so merging on the preview alone would discard the rest. The re-read returns whole rows, so it holds for telegraphic facts and few-KB observations and stops holding once a handful of rows runs to tens of thousands of characters; consolidation is report-only there. Append-only entities carry none of the three fields on purpose: an id is a deletion key, and their whole point is that nothing consolidates them.
### Append-safe writes
Dedup on write is destructive: a >= 0.85 match whose stored content is shorter is deleted and replaced. That is right for a fact being restated with more detail, and wrong for a log — two dated entries sharing a template can clear 0.85 on the template alone. Two guards bound it:
- **Append-only entities.** Entity names matching a prefix in `HIPPO_APPEND_ONLY_PREFIXES` (default `ops:daily-log:,ops:session-check,synthesis:`) are exempt from dedup entirely — nothing is skipped, nothing is deleted. The same setting governs all three protections — the dedup exemption here, the `consolidate` exclusion above, and the staleness suppression in [Reconsolidation hints](#reconsolidation-hints) — so turning it off re-enables all three. Set it to your own log namespaces. Leaving it unset or blank keeps the defaults — blank cannot mean "off", because a compose file forwarding an unset variable would otherwise silently disable the protection; to actually turn exemptions off, set it to the literal `none`.
- **Same-day scoping.** Everywhere else, dedup only considers observations created on the same UTC calendar day. Entries written on different days can never evict each other, however similar they look.
`consolidate` respects the same boundary: append-only entities are excluded from all four of its modes, so a log entity is never returned as a merge cluster, a `prune`/`compress`/`refresh` candidate, a contradiction pair, or an entity-resolution match. The count comes back as `excluded_append_only` and is named in the message — the drop is never silent. Pass `include_append_only: true` to inspect them anyway, which is worth doing occasionally to catch accidental double-writes, since these entities no longer dedup on write.
Any write that did delete something says so in a top-level `replaced: true`, with the evicted text in `replaced_observation` and its id in `replaced_observation_id` — so a caller can detect (and undo) data loss without parsing the human-readable `message`.
### Sleep mode
`consolidate` with `mode: "sleep"` runs batch lifecycle analysis — the overnight defrag for your knowledge graph. Uses SVD leverage scores combined with temporal signals to classify old observations into three categories:
- **Compress**: redundant + old + recalled. Information captured elsewhere, safe to merge down.
- **Prune**: never recalled + old. The synapse never fired — delete candidates.
- **Refresh**: actively used + unique + old. The AI keeps serving these, but newer information exists on the entity. Reconsolidation candidates.
Returns `information_rank` and `redundancy_ratio` per entity for structural diagnosis. The AI acts on results using existing tools — `merge` for compress, `forget` for prune, `update` for refresh.
### Reconsolidation hints
When `recall` returns observations older than 30 days on an entity that has received newer information since, they're flagged `stale: true`. Lightweight date comparison on every retrieval — no embedding computation. The AI sees the flag and can decide whether to update or leave the observation as-is.
Append-only entities are never flagged. "The entity has newer information since" is simply what an active log looks like, so the condition matched every historical entry — and since the flag's suggested remedy is `update`, which replaces an observation, the one place it fired hardest was the one place it must never fire.
### Cross-platform staleness detection
You told Claude about your project stack on Monday. On Wednesday you switched to Gemini. Is Gemini's cached context still current? Every entity carries a `version_hash` — SHA-256 of its observation content. The AI caches this hash, and later calls `check_version` to ask "did anything change?" without re-fetching everything. One lightweight metadata call instead of re-reading the entire entity.
All mutation tools return the new hash after writing. All read tools include it in the response. The AI always has a fresh hash to cache — no extra round trip.
### Onboarding
New databases start cold — the `hippocampus://context` resource shows guidance prompting the AI to capture what it already knows about you (identity, projects, preferences). Once 5+ observations exist, the guidance disappears and the full knowledge graph takes over.
For systematic first-session extraction, the `onboard` tool returns a structured prompt the AI follows — what to look for, what's already stored, what format to use. The tool stores nothing itself; it hands the AI a checklist and lets it do what it's good at. Each platform uses its own context for extraction.
## Configuration
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `HIPPO_PASSPHRASE` | Yes | — | Database encryption passphrase |
| `HIPPO_DB_PATH` | No | `./data/hippocampus.db` | Database file location |
| `PORT` | No | `3000` | Server port |
| `HOST` | No | `0.0.0.0` | Bind address |
| `HIPPO_TOKEN` | No | — | Bearer token for local dev (skip OAuth) |
| `HIPPO_OAUTH_ISSUER` | No | — | Your server URL — enables OAuth |
| `HIPPO_OAUTH_USER` | No | — | OAuth login username |
| `HIPPO_OAUTH_PASSWORD_HASH` | No | — | SHA-256 hash of OAuth password |
| `RATE_LIMIT_REMEMBER` | No | `20` | Write rate limit per minute |
| `RATE_LIMIT_RECALL` | No | `60` | Read rate limit per minute |
| `HIPPO_CONTEXT_MAX_OBSERVATIONS` | No | `100` | Max observations in `hippocampus://context` resource |
| `TRANSFORMERS_CACHE` | No | System default | Embedding model cache directory |
## Security
- AES-256 database encryption via SQLCipher (text, embeddings, indexes — everything)
- OAuth 2.1 with PKCE for remote access
- Input validation: 50,000 char/memory, 200 char/entity name
- Rate limiting on all endpoints
- `PRAGMA secure_delete = ON` — forgotten memories are zeroed, not just unlinked
- Non-root Docker, `cap_drop: ALL`, read-only filesystem
- CORS restricted to known AI platform origins
- No external API calls — embeddings run locally via Transformers.js
See [SECURITY.md](SECURITY.md) for the full threat model and architecture.
## OAuth — what's happening under the hood
When you connect Claude.ai, ChatGPT, or other browser-based platforms, they use OAuth 2.1 to authenticate with your server. Hippocampus includes a self-contained OAuth server — no external auth provider needed.
Here's what happens when you click "Connect" in Claude.ai:
1. Claude.ai auto-registers as a client with your server (Dynamic Client Registration, RFC 7591)
2. You're redirected to a login page on your server
3. You enter the username and password from your `.env`
4. Your server issues a short-lived access token (1 hour) and a refresh token (30 days)
5. Claude.ai uses the access token for MCP requests, refreshes automatically when it expires
The three `.env` variables that enable this:
- `HIPPO_OAUTH_ISSUER` — your server's public URL (tells Hippocampus to turn on OAuth)
- `HIPPO_OAUTH_USER` — your login username
- `HIPPO_OAUTH_PASSWORD_HASH` — SHA-256 hash of your password (the server never stores your plaintext password)
For local development, you can skip OAuth entirely by setting `HIPPO_TOKEN` in `.env` and passing it as a Bearer token.
## Contributing
### Architecture
```
src/
├── index.ts # Hono server, MCP Streamable HTTP transport
├── config.ts # Environment config with Zod validation
├── mcp/
│ ├── server.ts # MCP tool registration (12 tools)
│ └── tools/ # remember, recall, get_observation, forget, update, merge, merge_entities, context, consolidate, export, check_version, onboard
├── db/
│ ├── index.ts # SQLCipher initialization
│ ├── schema.ts # Schema + migrations
│ ├── entities.ts # Entity CRUD
│ ├── observations.ts # Observation CRUD + keyword search
│ └── relationships.ts # Relationship CRUD + BFS graph traversal
├── embeddings/
│ ├── embedder.ts # Local embeddings (all-MiniLM-L6-v2) + semantic search
│ └── subspace.ts # SVD novelty scoring + redundancy analysis
└── auth/
└── oauth.ts # Self-contained OAuth 2.1 server
```
**Stack:** Node.js, TypeScript, Hono, MCP SDK, SQLCipher, Transformers.js
### Running tests
```bash
# Unit + integration tests
npm test
# Full end-to-end smoke test (real embeddings, temp encrypted DB)
HIPPO_PASSPHRASE=test HIPPO_DB_PATH=/tmp/hippo-test.db npx tsx test-all-tools.ts
```
## Platform compatibility
| Platform | Remote MCP | How to connect |
|----------|-----------|----------------|
| Claude.ai (browser + mobile) | Yes | Custom integration |
| Claude Code | Yes | `claude mcp add` |
| Claude Desktop | Yes | Config file |
| ChatGPT (web + mobile) | Yes | Developer Mode > Apps |
| ChatGPT API | Yes | `server_url` in tools |
| Gemini CLI | Yes | Settings file |
| Gemini in Android Studio | Yes | Settings > MCP Servers |
| Cursor / Windsurf / VS Code | Yes | MCP config file |
| Perplexity Mac | Partial | Local MCP support, remote coming |
## License
AGPL-3.0 — free to use, modify, and self-host. If you run a modified version as a network service, you must open-source your changes under the same license.
## Support
If Hippocampus is useful to you, [buy me a coffee](https://buy.stripe.com/5kQ9AT4IydTz7h1apHb7y00).
Open source. Free forever. No hosted version. No SaaS.
This server cannot be deployed
Maintenance
ActivityNo data
ResponsivenessNo issues