MCP (Model Context Protocol) server backed by EvolutionDB — long-term memory plus a personal-assistant action loop (open loops, drafts, human-approved send across gmail/teams/outlook/slack/imessage) for Claude Desktop / Claude Code.
Project description
mcp-server-evolutiondb
A Model Context Protocol server that gives Claude Desktop / Claude Code persistent long-term memory backed by EvolutionDB. Anything Claude decides to remember during a conversation is written to a real database; in any future session — same window or weeks later — Claude can search it back without you having to repaste context.
Install
pipx install mcp-server-evolutiondb
# or: pip install --user mcp-server-evolutiondb
The package installs the mcp-server-evolutiondb console entry-point
(also aliased as mcp-server-evosql). It speaks the PostgreSQL wire
protocol over psycopg, so installation is pure-Python — no C
toolchain, no libevosql-memory.so to build. EvolutionDB still has
to be running somewhere reachable; docker compose up -d in the
main repo is the easiest
way.
┌──────────────────┐ ┌─────────────────────┐ ┌────────────────┐
│ Claude Desktop │ stdio │ mcp-server-evosql │ TCP │ EvolutionDB │
│ (or Claude │ ◀──────▶│ (this package) │ ◀──────▶│ (port 9967) │
│ Code) │ JSON- │ │ │ │
│ │ RPC │ save_memory │ │ MEMORY STORE │
│ │ 2.0 │ search_memory │ │ ENTITY STORE │
│ │ │ recent_memories │ │ │
│ │ │ forget │ │ │
│ │ │ list_tags │ │ │
└──────────────────┘ └─────────────────────┘ └────────────────┘
Why
The default Claude experience is stateless — every new chat starts from scratch, so you waste tokens re-explaining who you are, what project you're on, what your preferences are. Plug this server in and the model:
- saves preferences / decisions / facts during natural conversation,
- searches them back the next time you ask something related,
- forgets entries on demand,
- never sees the user_id that pins the namespace (we override it server-side, so the model can't accidentally fragment the namespace by inventing IDs across sessions).
Token math: 100 chats × 3,000 tokens of pre-loaded context (~$0.90
on Sonnet) → 100 chats × 250 tokens of just-relevant facts pulled
on demand ($0.26). Roughly 3.5× cheaper inputs without losing
context fidelity.
What's exposed to Claude
All under one evolutiondb-memory MCP server. Memory (the core):
| Tool | Purpose |
|---|---|
save_memory |
Persist a fact + optional tags |
search_memory |
Hybrid semantic + keyword search (before answering) |
recent_memories |
Last N saved facts (most-recent-first) |
forget |
Delete by key |
list_tags |
All distinct tags in use, with counts |
show_profile · expand_episode · feedback · restore_memory · set_language |
retrieval/profile extras |
Personal assistant + action loop (over your synced mail/chat — see below):
| Tool | Purpose |
|---|---|
daily_brief |
Who's waiting on you, what you owe, your day |
suggest_reply |
Draft a reply for an open loop (grounded) |
queue_reply |
Queue a drafted reply for approval (sends nothing) |
list_pending_replies |
Review the outbox |
approve_send |
The only tool that delivers (opt-in, gated) |
reject_reply |
Cancel a queued reply |
outbox_audit · send_scheduled |
the send trail + flush due scheduled sends |
Each call's user_id is overridden server-side from the
MCP_USER_ID env var — stops the model from drifting the namespace
across "user" / "default_user" / your name etc.
The assistant action loop
Beyond memory, the server runs a human-in-the-loop action loop over the
sources you sync in (gmail / teams / outlook / slack / imessage, via the sibling
*-sync connectors):
read (your mail/chat) → understand (open loops, self-model)
→ suggest (draft a reply) → queue → APPROVE → send → loop resolved
It is read + suggest by default — nothing is ever sent until you opt in and approve each reply. Drive it from Claude (the tools above) or from one CLI:
evolutiondb-brief # who's waiting on you, what you owe
evolutiondb-brief --queue 3 # draft + queue replies for the top 3 (nothing sent)
evolutiondb-outbox list # review what's queued
evolutiondb-outbox show <id> # preview exactly what would go out
evolutiondb-brief --approve # deliver (dry-run unless sending is enabled)
Turning on sending (deliberately)
Sending is off by default and the sync connectors stay read-only. To enable it, ask the onboarding helper what a channel needs — it prints the exact env + the one-time consent step, and writes nothing:
evolutiondb-send-setup # status: enabled? channels? guards?
evolutiondb-send-setup --channel gmail # the env to enable gmail + its auth step
Sending only happens when both EVOSQL_SEND_ENABLED=1 and a per-channel send
scope (gmail.send / Mail.Send / ChatMessage.Send / chat:write) are set,
and is wrapped in safe-default guards: an undo window, a rate cap, dedup
(no double-reply), and a queryable audit trail. Full design + the invariants:
docs/adr/ADR-004-action-loop-send-approval.md.
Install + run
1. Bring up EvolutionDB
cd /path/to/evolutiondb
docker compose up -d
Zero-Docker alternative: set EVOSQL_EMBEDDED=1 and the MCP server spawns its
own EvolutionDB on first connect (against a per-user data dir, reaped on exit) —
no docker compose step. If no evosql-server binary is found
(EVOSQL_SERVER_BINARY / PATH / dev checkout), it auto-fetches the prebuilt
one for your platform from the GitHub release (checksum-verified, cached) — so a
plain pip install + EVOSQL_EMBEDDED=1 needs neither Docker nor a binary.
Pre-fetch with evolutiondb-embedded-fetch; disable auto-fetch with
EVOSQL_EMBEDDED_AUTOFETCH=0. An already-running instance still wins, so it's
safe to leave on.
2. Build the SDK once
make -C client/libevosql-memory
3. Configure Claude Desktop
Open the config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Drop in the entry from
examples/claude_desktop_config.json,
substituting the absolute paths for your machine. Quit + restart
Claude Desktop.
You'll see a small 🔌 / hammer icon in the bottom-right of the
chat composer once evolutiondb-memory is connected.
4. Talk normally
Say "remember that I take my espresso single-shot, no sugar"; Claude
will run save_memory(...). Days later open a new chat, ask "what
do I drink?" — Claude runs search_memory(...) and recalls.
One-command setup for all supported hosts
pip install mcp-server-evolutiondb puts a small evolutiondb-mcp-setup
helper on $PATH. It autodetects every MCP host installed on the
machine and merges the evolutiondb-memory server into each of
their config files in one go:
evolutiondb-mcp-setup
# [setup] Claude Desktop: wrote /Users/you/Library/Application Support/Claude/claude_desktop_config.json
# [setup] ChatGPT Desktop: not detected — skipping
# [setup] Gemini CLI: wrote /Users/you/.gemini/settings.json
The merge is idempotent — running it twice changes nothing the
second time. Existing mcpServers entries are preserved; only the
evolutiondb-memory block is overwritten.
Useful flags:
--dry-run— show what would change, write nothing.--client claude-desktop— only configure one host (repeatable).--user-id NAME— override the sticky namespace (default:$MCP_USER_IDordefault_user).--port 5433— non-default EvolutionDB PG-wire port.
Override config paths via env vars when the auto-detected location
is wrong for your install: EVOSQL_CLAUDE_CONFIG,
EVOSQL_CHATGPT_CONFIG, EVOSQL_GEMINI_CONFIG.
Per-host manual config
If you'd rather hand-edit, every host accepts the same JSON shape —
the mcpServers block is part of the MCP spec, not specific to any
vendor:
{
"mcpServers": {
"evolutiondb-memory": {
"command": "uvx",
"args": ["mcp-server-evolutiondb"],
"env": {
"EVOSQL_HOST": "127.0.0.1",
"EVOSQL_PORT": "5433",
"EVOSQL_USER": "admin",
"EVOSQL_PASSWORD": "admin",
"EVOSQL_DATABASE": "evosql",
"MCP_USER_ID": "your-handle",
"MCP_STORE_PREFIX": "mcp"
}
}
}
}
File paths:
| Host | macOS | Linux / WSL | Windows |
|---|---|---|---|
| Claude Desktop | ~/Library/Application Support/Claude/claude_desktop_config.json |
~/.config/Claude/claude_desktop_config.json |
%APPDATA%\Claude\claude_desktop_config.json |
| Claude Code | ~/.claude/mcp.json |
~/.claude/mcp.json |
%USERPROFILE%\.claude\mcp.json |
| ChatGPT Desktop | ~/Library/Application Support/ChatGPT/mcp.json |
~/.config/ChatGPT/mcp.json |
%APPDATA%\ChatGPT\mcp.json |
| Gemini CLI | ~/.gemini/settings.json |
~/.gemini/settings.json |
%USERPROFILE%\.gemini\settings.json |
After editing, restart the host (or for Gemini CLI, just run the next command — it re-reads its config on every launch).
Remote HTTP transport (web ChatGPT, Gemini app, etc.)
Web-only MCP hosts cannot spawn a local process and so cannot use stdio. They speak the spec's "streamable HTTP" transport instead. Run the same server as an HTTP listener and point a tunnel at it:
# 1. Start the HTTP listener (default port 8970, path /mcp).
export EVOSQL_MCP_AUTH_TOKEN=$(openssl rand -hex 24)
evolutiondb-mcp-http
# 2. Expose it through a tunnel. Pick whichever you already use.
cloudflared tunnel --url http://127.0.0.1:8970
# → outputs https://random-words.trycloudflare.com
# OR
ngrok http 8970
# → outputs https://random.ngrok-free.app
In the web host's "MCP Connectors" or "Custom MCP server" panel, register:
- URL:
https://your-tunnel-host/mcp - Authorization:
Bearer <the token you just generated>
The server enforces three guards when bound to anything other than loopback:
- Every request must carry the bearer token.
Origin(when present) must be on the allow-list. The defaults coverchat.openai.com,chatgpt.com,claude.ai,gemini.google.com. Override viaEVOSQL_MCP_ALLOWED_ORIGINS(comma-separated).- Bound to 127.0.0.1 by default. The CLI warns if you flip to
0.0.0.0without a token.
Identical tool surface as stdio — save_memory, search_memory,
recent_memories, forget, list_tags. New tools land in both
transports at once because both go through the same MCPServer.handle.
Configuration
| Env var | Default | Purpose |
|---|---|---|
EVOSQL_HOST |
127.0.0.1 |
DB host |
EVOSQL_PORT |
9967 |
EVO port |
EVOSQL_USER |
admin |
DB user |
EVOSQL_PASSWORD |
admin |
DB password |
MCP_USER_ID |
default_user |
Sticky namespace for every tool call |
MCP_STORE_PREFIX |
mcp |
Catalog object prefix |
EVOSQL_PYTHON_SDK |
(auto-discovered) | Override path to the Python ctypes binding |
EVOSQL_MEMORY_LIB |
(auto-discovered) | Override path to libevosql-memory.dylib/so |
EVOSQL_EMBEDDING_PROVIDER |
none |
openai, local, or none. Turns search_memory into hybrid semantic+keyword |
EVOSQL_EMBEDDING_MODEL |
provider default | Override embedding model (e.g. text-embedding-3-large) |
OPENAI_API_KEY |
— | Required when provider is openai |
Context-layer boosts (all opt-in)
These layer extra signal onto retrieval. Each is off by default so the plain hybrid search is unchanged; turn them on per deployment.
| Env var | Default | Purpose |
|---|---|---|
EVOSQL_SALIENCE_BOOST |
0 |
Weight (0-1) for the per-row salience score (recency × sender activity × thread depth × feedback) |
EVOSQL_GRAPH_BOOST |
0 |
Weight (0-1) for knowledge-graph spreading activation — relational queries reach rows that never name the queried entity |
EVOSQL_PROFILE_BOOST |
0 |
Weight (0-1) for the user interest profile — biases results toward the cluster the query points at |
EVOSQL_DECAY |
0 |
1 to fade old, unused rows (archived, never deleted; include_archived digs them back up) |
EVOSQL_GRAPH_BUILD |
1 |
Build graph edges inline on save (cheap; needed before the graph boost helps) |
EVOSQL_ENTITY_EXTRACT |
1 |
Extract entities inline on save |
salience, profile, and decay read data the background scheduler
maintains, so run it for them to have any effect:
python -m mcp_server_evosql.scheduler run # hourly/daily/weekly jobs
python -m mcp_server_evosql.scheduler status # last run + failure ratio
It also embeds + entity-extracts new rows, recomputes salience, refreshes the interest profile, regenerates episode summaries, and runs the decay pass.
Semantic search
search_memory runs in keyword mode by default. Setting
EVOSQL_EMBEDDING_PROVIDER=openai (with OPENAI_API_KEY) or
EVOSQL_EMBEDDING_PROVIDER=local flips it into hybrid mode: new
saves are tagged with a dense vector and queries are ranked by a
weighted mix of cosine similarity (0.7) and substring overlap (0.3).
Rows saved before you enabled embeddings continue to score on
keyword overlap only, so the switch is non-destructive — older
memories don't disappear, they just rank lower against semantically
strong matches.
The local provider needs an extra install:
pip install 'mcp-server-evolutiondb[embeddings-local]'
Tests
cd client/mcp-server-evosql
python3 tests/test_mcp.py
Eight cases — initialize handshake, tools/list discovery, save+search round-trip, tag-filtered search, recent ordering, forget, list_tags aggregation, and the "user_id can't be hijacked from the LLM side" isolation case. Each test spawns the server as a real subprocess and talks JSON-RPC, so framing bugs that an in-process unit test would hide get caught.
Inspect the database directly
While Claude is using the server, open another terminal and:
docker compose exec evosql evosql-cli -W admin
Then:
SELECT mem_namespace, mem_key, mem_value FROM __mem_mcp_mem;
ENTITY RANK FROM mcp_ents;
Everything Claude has decided to remember is right there as queryable rows — no opaque blob storage.
Wire format
Newline-delimited JSON-RPC 2.0 over stdio (no Content-Length
headers — that's the LSP variant; MCP uses plain \n-delimited).
The server speaks protocol version 2024-11-05.
{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2024-11-05","capabilities":{}}}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
{"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"save_memory",
"arguments":{"fact":"loves jazz","tags":["preference"]}}}
Errors come back as {"jsonrpc":"2.0","id":N,"error":{"code":..,"message":..}}
or as a tools/call result with isError: true.
Known limitations
- Single-process EvolutionDB connection. The server holds one Connection — the SDK contract is one-per-thread, and MCP stdio is inherently single-threaded so this is fine.
- No streaming responses. Tool results return as single JSON blobs. Larger memories (>100 facts) take ~50 ms to serialise; the protocol can stream but Claude's tool-use UI doesn't render partial responses anyway.
- Authentication via env-vars only. If you expose the server to
another machine (which you shouldn't — it's stdio), set
EVOSQL_PASSWORDaccordingly. The server doesn't rotate secrets.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_server_evolutiondb-1.13.0.tar.gz.
File metadata
- Download URL: mcp_server_evolutiondb-1.13.0.tar.gz
- Upload date:
- Size: 276.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
97fe0702b1274f8fca5898d4e098b7a102500858371d8f83b17d8b72cbbf205b
|
|
| MD5 |
7f177ac5c2172998578226e60d2267bd
|
|
| BLAKE2b-256 |
7228635584526140cded3e11c4ab5259e6eb62780cca715f4f1ba962f96155eb
|
Provenance
The following attestation bundles were made for mcp_server_evolutiondb-1.13.0.tar.gz:
Publisher:
mcp-release.yml on alptekin/evolutiondb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_server_evolutiondb-1.13.0.tar.gz -
Subject digest:
97fe0702b1274f8fca5898d4e098b7a102500858371d8f83b17d8b72cbbf205b - Sigstore transparency entry: 1778992471
- Sigstore integration time:
-
Permalink:
alptekin/evolutiondb@630791e16bfccee78803dfa6245a43b6e5f26a8d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/alptekin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
mcp-release.yml@630791e16bfccee78803dfa6245a43b6e5f26a8d -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file mcp_server_evolutiondb-1.13.0-py3-none-any.whl.
File metadata
- Download URL: mcp_server_evolutiondb-1.13.0-py3-none-any.whl
- Upload date:
- Size: 212.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c10554837c2d876b33498bf3279defbd9bb2b5d9793968e16a3a944b34e7ba5b
|
|
| MD5 |
e058b9078a639e949382ecd26813820f
|
|
| BLAKE2b-256 |
2ce8c728c48faa2813c1343310146802cfb89dcbb178238c9479ba0b0711e88c
|
Provenance
The following attestation bundles were made for mcp_server_evolutiondb-1.13.0-py3-none-any.whl:
Publisher:
mcp-release.yml on alptekin/evolutiondb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_server_evolutiondb-1.13.0-py3-none-any.whl -
Subject digest:
c10554837c2d876b33498bf3279defbd9bb2b5d9793968e16a3a944b34e7ba5b - Sigstore transparency entry: 1778992602
- Sigstore integration time:
-
Permalink:
alptekin/evolutiondb@630791e16bfccee78803dfa6245a43b6e5f26a8d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/alptekin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
mcp-release.yml@630791e16bfccee78803dfa6245a43b6e5f26a8d -
Trigger Event:
workflow_dispatch
-
Statement type: