Shared-Memory MCP
Shared‑Memory MCP for AI Tools
A self‑hosted shared memory system that lets multiple AI tools — Claude Code, Codex, and a local OpenCode‑powered model — share one canonical conversation history. Everything is stored in a single PostgreSQL database and exposed to the tools over MCP (stdio). The memory is append‑only with supersede without delete: a changed fact or decision simply appends a newer row, so the full history is preserved and can be queried.
Features
Canonical history – raw messages, facts, decisions, preferences, events, and project states all live in one PostgreSQL database shared by every tool.
Idempotent appends – messages are deduplicated by
source_id, so retries and re‑sends never create duplicates.Full‑text search –
to_tsvector/ts_rankover the message body, with a GIN index. Search spans all sessions/providers in one query.Optional semantic search – when
MEMORY_USE_EMBEDDINGS=1, each message also stores a deterministic local embedding in a real pgvector column, and retrieval becomes a hybrid FTS + cosine‑similarity search. Off by default.Pagination – read a session's messages page by page.
Current context – one tool returns the latest facts, decisions, preferences, project state, and recent events for a project across all its sessions.
Append‑only streams – facts / decisions / preferences / events / project states are stored as an immutable log; changing a value just appends a new row.
Change tracking – every write is tagged with a
source_client, so you can ask "what did Codex change?" and get a diff of another client's writes.Credential rejection – appends that look like secrets (
sk-…keys) are refused.MCP, not REST – the server speaks MCP over stdio (the transport Claude Code, Codex, and OpenCode use).
Related MCP server: Unforget MCP Server
Architecture
+-----------------+ MCP / stdio +--------------------+
| Claude Code | ------------------> | |
| Codex | (shared-memory) | PostgreSQL |
| OpenCode AI | <------------------ | (pgvector + FTS) |
+-----------------+ +--------------------+Each tool is a thin wrapper over memory.crud. The database is the single source
of truth; there is no in‑server cache to sync. Two clients writing concurrently
see each other's rows the next time they query — there is nothing to "sync", only
one shared store.
Requirements
Python 3.12+
PostgreSQL 16 + pgvector. This repo ships with a bundled embedded PostgreSQL 16.2 (with the
vectorextension) so you can run everything locally with no Docker and no system package installation. You can also point it at any other PostgreSQL viaDATABASE_URL.
Quick Start (embedded PostgreSQL, no Docker)
git clone <this-repo> && cd shared-memory
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# 1. Start the bundled PostgreSQL on port 5433 (idempotent).
scripts/start-pg.sh start
# 2. Create the database + schema. Either path works and produces the same schema:
# (a) migrations via Alembic (preferred for versioning/dev parity):
# export DATABASE_URL="postgresql+asyncpg://memory:memory@localhost:5433/memory"
# alembic upgrade head
# (b) or just run the MCP server — it boots the schema itself on startup.
# 3. Run the MCP server (stdio).
python -m memory.mcp_serverEnvironment variables
Variable | Default | Meaning |
|
| async SQLAlchemy URL. Override for any PostgreSQL. |
|
|
|
|
| asyncpg pool sizing. |
Database setup & migrations
The schema is defined once, in the ORM (memory/models.py), and two independent
paths produce an identical database:
memory.database.init_schema()– idempotent bootstrap used by the runtime and tests. Creates the pgvector extension, all tables, and indexes.Alembic –
alembic/versions/2026_09_27_initial.pymirrors the ORM exactly. Use it to evolve the schema over time:
export DATABASE_URL="postgresql+asyncpg://memory:memory@localhost:5433/memory"
alembic upgrade head # apply migrations
alembic check # verify no drift from the ORM ("No new upgrade operations detected.")
alembic downgrade -v -1 # step back one migrationNote: the runtime app uses asyncpg (async), but Alembic's migration context is synchronous, so
alembic/env.pytransparently rewrites the URL to the pure‑Python pg8000 driver for migrations only. Seealembic/env.py.
Using the MCP server from a client
The server is launched over stdio (see memory/mcp_server.py entrypoint).
Configure any MCP client to spawn it. Every client starts the server from its
own working directory, so set PYTHONPATH to the project root, or the import
python -m memory.mcp_server fails with ModuleNotFoundError. The examples
below point DATABASE_URL at the shared memory database so every client sees
the same history.
Claude Code
Use -s user to make it available in every project (there is no
~/.claude/projects/.claude-mcp.json); -s project writes .mcp.json in the
project root instead.
claude mcp add -s user shared-memory \
-e DATABASE_URL=postgresql+asyncpg://memory:memory@localhost:5433/memory \
-e PYTHONPATH=/path/to/llmmemory \
-- /path/to/llmmemory/.venv/bin/python -m memory.mcp_serverCodex
Codex is configured in ~/.codex/config.toml, which is TOML, not JSON. You
can add the entry by hand:
[mcp_servers.shared-memory]
command = "/path/to/llmmemory/.venv/bin/python"
args = ["-m", "memory.mcp_server"]
env = { DATABASE_URL = "postgresql+asyncpg://memory:memory@localhost:5433/memory", PYTHONPATH = "/path/to/llmmemory" }or equivalently with the CLI (codex mcp add shared-memory --env KEY=VALUE ... -- <command>).
OpenCode
OpenCode v2 uses mcp.servers.<name> in its config, not mcpServers. Use the
CLI so the format comes out right:
opencode mcp add --global shared-memory \
--env DATABASE_URL=postgresql+asyncpg://memory:memory@localhost:5433/memory \
--env PYTHONPATH=/path/to/llmmemory \
-- /path/to/llmmemory/.venv/bin/python -m memory.mcp_serverOpenCode note: the user's OpenCode runs inside a network‑isolated sandbox that cannot reach the host's PostgreSQL. Reaching it requires a bridge added to the sandbox wrapper — outside this project, so it is listed under What still needs the user.
All three clients point at the same DATABASE_URL, so they share one
canonical history — there is nothing to "sync", only one shared store.
MCP tool reference
Every tool returns a plain dict wrapped as {"is_error": false, …}, or a
structured error {"is_error": true, "code": …, "error": …} — tools never raise
on business errors, so clients can inspect results programmatically.
Tool | Purpose |
| List all projects. |
| Create a project. |
| Create/find a session (idempotent by provider+source). |
| Append a message (idempotent by source_id). |
| Read a session's messages, paginated. |
| Fetch one message. |
| Full‑text (+ optional vector) search across all history. |
| Record a sourced, append‑only memory value. |
| Record an event. |
| Latest facts/decisions/preferences/project state + recent events for a project. |
| Full append‑only timeline (facts/decisions/preferences/events/project_states). |
| Snapshot a project's current state (append‑only). |
| List records written by another |
Capture: automatic, hook, or manual
There are three ways data gets into memory. Be explicit about which one your integration uses.
Automatic capture is not built yet. Nothing is captured automatically today: none of these three clients sends its conversation to an MCP server on its own. The server only sees what the model chooses to send through tool calls — that is manual capture, and it will miss turns.
Automatic capture needs a small importer per client. The importer reads the client's own session store and calls
append_message, which is idempotent bysource_id, so re‑reading is safe. Planned importers:Claude Code – hooks configured in
settings.json(e.g.Stop,SessionEnd) receive JSON on stdin that includessession_idandtranscript_path. Transcripts are JSONL files at~/.claude/projects/<project>/<session-id>.jsonl.Codex – sessions are JSONL files at
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl; anotifyprogram set inconfig.tomlruns after each turn.OpenCode v2 – sessions live in its SQLite database
opencode.db, in tablessession_v2andsession_message. The sandboxed install keeps that file under the sandbox's own home directory.
Hook / on‑demand capture – the client captures only at specific points (when a tool call returns, on session end, when a fact is surfaced). It is still client‑code deciding when to call
append_message/record_fact.Manual capture – a human or agent calls
record_fact,record_decision,record_preference,record_event, orrecord_project_statedirectly. This is how structured, high‑signal knowledge (a decision, a preference) gets in without a full message stream.
All three write to the same tables, so the data is indistinguishable downstream —
each write is tagged with source_client and can be found via
find_changes / inspect_project_history.
Known limitation: the credential filter refuses a whole message rather than blanking out just the secret, so a captured transcript will have gaps wherever a message trips it. This is intentional for now; the filter is deliberately not changed.
Two‑client demo
examples/two_client_demo.py simulates Claude and Codex sharing one
database and proves cross‑client visibility:
scripts/start-pg.sh start
python -m examples.two_client_demoThe demo writes to its own throwaway memory_demo database, read from
DEMO_DATABASE_URL (default postgresql+asyncpg://memory:memory@localhost:5433/memory_demo),
so it never needs DATABASE_URL and never touches the shared memory database.
It resets memory_demo before running, so re‑running is safe.
It shows Claude writing one session and Codex another on the same project, each
client seeing the other's writes via find_changes, and project context + search
spanning both sessions.
Security
The store contains conversation history and derived knowledge. Two guards are in place:
Credential rejection – appends whose content looks like a secret (e.g.
sk-…API keys) are refused bymemory.crud_secret.Never store secrets – it is the caller's responsibility not to send secrets; the guard is a safety net, not a substitute. For production, front the database with TLS + auth and audit access.
Running the tests
The suite runs against a real PostgreSQL instance (the bundled embedded one by default).
scripts/start-pg.sh start
export LD_LIBRARY_PATH="$PWD/.embedded-pg/pginstall/lib"
export PYTHONPATH="$PWD"
python -m pytestTests cover: idempotent appends, conflict detection, full message field storage,
pagination, cross‑client visibility, full‑text search (+ source_client filter),
fact supersession, decisions/preferences, project context, events, project states,
find_changes, credential rejection, and structured error reporting. There is also
test_embeddings.py, which exercises the real pgvector semantic search path
(MEMORY_USE_EMBEDDINGS=1).
Project layout
memory/ ORM models, async engine, CRUD, MCP server, embeddings
alembic/ schema migrations (mirror the ORM)
scripts/ start-pg.sh / bootstrap-pg.sh for the embedded PostgreSQL
examples/ two_client_demo.py — cross-client write/read demo
tests/ pytest integration suite against real PostgreSQLWhat still needs the user
A few things are out of scope for this repo and must be done on the user's side.
OpenCode sandbox → host PostgreSQL. The user's OpenCode runs inside a network‑isolated sandbox that cannot reach the host's PostgreSQL. Pointing the MCP server's
DATABASE_URLat a reachable database (or adding a bridge to the sandbox wrapper) is outside this project and must be arranged there.Automatic capture. None of the three clients streams its conversation to this server automatically (see Capture). The per‑client importers described there still need to be written.
DATABASE_URLin production. The examples default to the local embedded server on port5433. PointDATABASE_URL(andDEMO_DATABASE_URLfor the demo) at your real PostgreSQL to run against it.
Contributing
Please run pytest locally and add tests for new features. Keep the ORM
(memory/models.py) and the latest Alembic migration in sync, and verify with
alembic check.
About this copy
A public snapshot of my private working repo. More of my projects: nbdevlab.com.
About this copy
A public snapshot of my private working repo. More of my projects: nbdevlab.com.
License
GNU General Public License v3.0 or later, see LICENSE.
Preparing this public copy from my private repo was done with help from Claude.
This server cannot be deployed
Maintenance
Related MCP Connectors
Shared cross-LLM long-term memory over MCP: semantic recall, sessions, and media (pgvector).
Hosted PostgreSQL MCP server for Claude, ChatGPT and Cursor, with each action switched per account.
Persistent AI memory shared across Claude, ChatGPT, coding agents, and compatible MCP clients.
Persistent memory for AI assistants. Save once; recall from Claude, ChatGPT, or any MCP client.
Related MCP Servers
- AlicenseAqualityCmaintenanceA personal memory MCP server that ingests AI agent conversation logs from multiple platforms into a searchable PostgreSQL+pgvector database, enabling cross-session recall of past reasoning and decisions.6MIT

Unforget MCP Serverofficial
AlicenseNot gradedqualityDmaintenanceLong-term memory for Claude Code, Cursor, and MCP clients with zero-config embedded PostgreSQL. Stores and retrieves facts, preferences, and decisions using 4-channel semantic, BM25, entity, and temporal search with cross-encoder reranking.1Apache 2.0- AlicenseNot gradedqualityBmaintenanceCentralizes verbatim local AI conversation history into a SQLite database and exposes it via an MCP server, enabling tools like Claude Code and Codex to retrieve exact context from other tools.1 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to persist, retrieve, and share source-linked memory across sessions via MCP tools, backed by PostgreSQL with project/session management, decision tracking, keyword search, and cross-host context handoff.Apache 2.0