Metadata-Version: 2.4
Name: mcp-server-evolutiondb
Version: 1.12.0
Summary: 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.
Author-email: alptekin topal <topal.alptekin@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/alptekin/evolutiondb
Project-URL: Documentation, https://alptekin.github.io/evolutiondb/
Project-URL: Repository, https://github.com/alptekin/evolutiondb
Project-URL: Issues, https://github.com/alptekin/evolutiondb/issues
Project-URL: Source, https://github.com/alptekin/evolutiondb/tree/main/client/mcp-server-evosql
Keywords: mcp,model-context-protocol,evolutiondb,claude,claude-desktop,claude-code,long-term-memory,agent-memory,personal-assistant,open-loops,ai-agent
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: psycopg[binary]>=3.1
Provides-Extra: embeddings-local
Requires-Dist: sentence-transformers>=2.5; extra == "embeddings-local"
Provides-Extra: turkish
Requires-Dist: snowballstemmer>=2.2; extra == "turkish"
Provides-Extra: send
Requires-Dist: msal>=1.20; extra == "send"

# mcp-server-evolutiondb

<!-- mcp-name: io.github.alptekin/evolutiondb-memory -->

A [Model Context Protocol](https://modelcontextprotocol.io) 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

```bash
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](https://github.com/alptekin/evolutiondb) 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:

```bash
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**:

```bash
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`](../../docs/adr/ADR-004-action-loop-send-approval.md).

## Install + run

**1. Bring up EvolutionDB**

```bash
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**

```bash
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`](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:

```bash
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_ID` or `default_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:

```json
{
  "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:

```bash
# 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
  cover `chat.openai.com`, `chatgpt.com`, `claude.ai`,
  `gemini.google.com`. Override via `EVOSQL_MCP_ALLOWED_ORIGINS`
  (comma-separated).
- Bound to 127.0.0.1 by default. The CLI warns if you flip to
  `0.0.0.0` without 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:

```bash
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

```bash
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:

```bash
docker compose exec evosql evosql-cli -W admin
```

Then:

```sql
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_PASSWORD` accordingly. The server doesn't rotate secrets.
