shared-memory
Summary: The server provides a shared semantic memory layer that MCP clients can search, extend, update, safely delete from, and inspect.
memory_search: run semantic search with a natural-language query, optionally limited bytop_k(default 5, max 50),agent_filter,source_filter, and ISOsincelower bound.memory_add: add an ad-hoc memory chunk with text and optional metadata, returning the generated id.memory_update: update an existing chunk’s text and/or metadata in place while preserving its id; unknown ids raise instead of creating new chunks.memory_forget: delete chunks by explicit ids or agent/source filters, with dry-run on by default andexpect_countrequired for filter deletes; calls with no ids and no filters are refused.memory_stats: return collection statistics, including total count and breakdowns by agent and source.
Ships with a generic Markdown indexer/adapter that ingests folders of Markdown files into shared memory, tagging chunks by agent and source for later semantic search.
Supports OpenAI-compatible remote embedding APIs as an optional embedding provider, allowing memory chunks to be embedded through an external OpenAI-compatible service instead of local fastembed.
Uses SQLite as the default embedded storage backend, keeping text, an FTS5 full-text index, and vectors in a single local database file for persistent shared memory and hybrid ranking.
shared-memory
Listed on Glama: https://glama.ai/mcp/servers/fazerluga-creator/shared-memory
A portable, self-hosted semantic memory layer for LLM agents, exposed over the Model Context Protocol (MCP).
Point any number of agents — Claude Code, other MCP clients, or your own scripts — at one shared store and let them recall past decisions, notes, and conversations by meaning instead of grepping files.
One SQLite file. Text, a full-text index and vectors live together; no database service to run. ChromaDB is still available as an option.
Hybrid ranking. Vector similarity, plus exact matching of identifiers (order numbers,
ABC-123ids) that vectors alone miss, plus weights that put distilled notes above raw transcripts. See Search quality.Local by default. Embeddings run on-device via fastembed; nothing leaves the host unless you opt into a remote API (OpenAI-compatible or Voyage AI).
One store, many agents. Every chunk carries an
agentandsourcetag, so searches can be global or scoped to a single agent.Seven MCP tools.
memory_search,memory_add,memory_update,memory_forget,memory_stats,memory_links,memory_graph.Bring your own content. Ships with a generic markdown indexer; writing an adapter for chat logs, tickets, or docs is a short script.
Why
Agents are stateless between sessions. The common fix is a short "always-loaded" memory file, but it can't scale — you can't paste everything you've ever decided into every prompt.
shared-memory is the second layer: an unbounded store you search on demand.
Layer A — a short, hand-curated memory file loaded into every prompt (you keep whatever you already use for this).
Layer B (this project) — unbounded, searched semantically. "What did we decide about X?" returns the relevant past chunk instead of a file dump.
Related MCP server: mnemo-mcp
Architecture
┌────────────────┐
agent A ─┐ │ MCP server │ memory_search ──▶ memory.sqlite
agent B ─┼──▶ │ (FastMCP) │ memory_add / update chunks + FTS5 + vectors
scripts ─┘ │ │ memory_forget / stats (or ChromaDB, optional)
└────────────────┘
▲
│ indexers / adapters
your content (markdown, sessions, docs, …)shared-memory/
├── shared_memory/
│ ├── server.py # FastMCP server, the memory_* tools → shared-memory-mcp
│ ├── index_markdown.py # generic adapter: index a folder of *.md → shared-memory-index
│ ├── migrate.py # copy a Chroma store into SQLite → shared-memory-migrate
│ ├── embedder.py # embeddings: local fastembed | OpenAI-compatible | Voyage AI
│ ├── store.py # backend selection, shared helpers
│ └── store_sqlite.py # SQLite backend: storage, hybrid ranking, Chroma-style API
├── mcp-server/server.py # compatibility shim for 0.1/0.2 configs (removed in 1.0)
├── embedding.env.example # copy to embedding.env to customize (optional)
└── requirements.txtInstall
Requires Python 3.10+.
git clone https://github.com/fazerluga-creator/shared-memory.git
cd shared-memory
python -m venv .venv && source .venv/bin/activate
pip install -e . # SQLite backend (default)
pip install -e ".[chroma]" # also the ChromaDB backend / migrationOr without cloning:
pip install "git+https://github.com/fazerluga-creator/shared-memory.git@v0.3.0"This installs three commands: shared-memory-mcp (the server),
shared-memory-index (markdown indexer) and shared-memory-migrate.
Where data lives. From a clone (pip install -e .) the config and data stay
in the repo folder: embedding.env, memory.sqlite. From a regular install
they live in ~/.local/share/shared-memory/ ($XDG_DATA_HOME/shared-memory).
SHARED_MEMORY_HOME moves both; SHARED_MEMORY_ENV / SHARED_MEMORY_DB set
single files.
Optionally copy the config and tweak it (defaults are fine to start):
```bash
cp embedding.env.example embedding.envThe first run downloads the local embedding model (a few hundred MB) once.
With a remote provider (voyage, openai_compatible) nothing is downloaded.
Docker
docker build -t shared-memory .
docker run -i --rm -v shared-memory-data:/data shared-memoryThe container speaks MCP over stdio and needs no credentials: the SQLite store
lives in /data and embeddings run locally (the model is downloaded on the first
search or add). Mount /data to keep the memory between runs.
Quick start
Index some markdown and search it:
# 1. ingest a folder of notes
shared-memory-index ./notes --agent notes
# 2. run the MCP server (stdio transport)
shared-memory-mcp
# or over HTTP on 127.0.0.1:8000 (read the Security section first)
shared-memory-mcp --transport httpOr use the library directly:
from shared_memory import Embedder, get_client, get_collection
col = get_collection(get_client())
emb = Embedder()
col.add(
ids=["note::1"],
embeddings=[emb.embed_document_one("Ship the beta on Friday, feature-flag the new UI.")],
documents=["Ship the beta on Friday, feature-flag the new UI."],
metadatas=[{"agent": "notes", "source": "manual"}],
)
res = col.query(query_embeddings=[emb.embed_one("when is the beta?")], n_results=3)
print(res["documents"])MCP tools
Tool | Signature | Returns |
|
|
|
|
| id of the added chunk |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
memory_forget is a dry run unless you pass dry_run=False; deleting by filter
also requires expect_count equal to the number of matches, and a call with no
ids and no filters is refused.
Example memory_search result:
[
{
"id": "notes::markdown::decisions.md::003",
"text": "decisions.md › Release: ship the beta on Friday, feature-flag the new UI.",
"metadata": {"agent": "notes", "source": "markdown", "kind": "note",
"timestamp": "2026-10-01T09:12:00+00:00"},
"score": 0.7134
}
]Filters:
agent_filter— restrict to a singlemetadata.agentvalue.source_filter— restrict to ametadata.sourcevalue (e.g.markdown,sessions).since— ISO timestamp lower bound onmetadata.timestamp. On the SQLite backend it is applied before ranking, so a narrow window on a large store still returns the matches inside it.
On SQLite, memory_add also writes cross-links (failures are logged and do not
fail the insert):
similar — hybrid-search neighbours, top 5, score ≥
SHARED_MEMORY_LINK_THRESHOLD(default0.75), excluding the new chunk itself.tag — other chunks that share a
[[topic]]marker in the text, at most 20.
memory_links lists those rows. memory_graph returns the same edges as
{nodes, edges} for a UI or a prompt. filter is a substring of the chunk id,
text, or metadata.agent; direct neighbours are included until the cap.
Deleting a chunk removes its links. The chunks table itself is unchanged.
scripts/link_backfill.py rebuilds similar links for chunks that were stored
before this existed. It is a dry-run unless you pass --apply. --llm confirms
each new pair through OmniRoute at http://127.0.0.1:20128/v1/chat/completions
(API key from OMNIROUTE_API_KEY or a local file, never hardcoded) and stores
type=llm. The script does not install cron.
Connect to Claude Code / any MCP client
Add the server to your client config (paths are examples):
{
"mcpServers": {
"shared-memory": {
"command": "/path/to/shared-memory/.venv/bin/shared-memory-mcp"
}
}
}Security
The server has no authentication. Anyone who can reach it can read every
memory, add to it, rewrite it and delete it. Run it over stdio, or bind the
HTTP transport to 127.0.0.1 (FastMCP's default), and never expose it to the
internet or to a network you do not fully control. shared-memory-mcp --transport http refuses a non-loopback --host unless you also pass
--allow-unauthenticated-remote. If several machines need it,
put it behind a VPN or an authenticating reverse proxy.
Memories tend to collect secrets that were pasted into chats. Treat the
database file like a password store: restrictive file permissions (chmod 600)
and encrypted backups. With a remote embedding provider every chunk is sent to
that provider; filter secrets out in your indexer before that if it matters.
Configuration
All settings are read from embedding.env (see embedding.env.example). Paths
are overridable by environment variable:
Variable | Default | Purpose |
|
| location of the env file |
|
|
|
|
| SQLite database file |
|
| minimum hybrid score for a |
|
| ChromaDB persistence directory |
|
|
|
| MiniLM multilingual ( | embedding model id |
|
| collection name (display only on SQLite) |
To route embeddings through a remote OpenAI-compatible /v1/embeddings
endpoint, set EMBEDDING_PROVIDER=openai_compatible and fill in
EMBEDDING_BASE_URL / EMBEDDING_API_KEY. For Voyage AI set
EMBEDDING_PROVIDER=voyage and EMBEDDING_API_KEY (or VOYAGE_API_KEY).
Vectors are tagged with the model that produced them and search ignores vectors
of any other model, so changing the provider or model means re-indexing
(shared-memory-migrate --reembed does this for a Chroma store).
Upgrading from 0.1.x (Chroma)
If an existing chroma_data directory is found and no memory.sqlite, 0.2.0
keeps using Chroma and logs a warning, so nothing disappears after an upgrade.
To move to SQLite:
pip install -e ".[chroma]"
shared-memory-migrate # reuses stored vectors, no API calls
# check memory_stats / a few searches, then set SHARED_MEMORY_BACKEND=sqliteSearch quality
On the SQLite backend a chunk's score is its cosine similarity times a weight
for metadata.kind — note 1.0, doc 0.97, transcript 0.90, anything else
1.0 — plus 0.3 when the query contains an exact token (a number of 4+ digits, a
word with a digit, ABC-123) that the chunk contains verbatim. Chunks with
metadata.superseded: true are multiplied by 0.9: still findable, just below
the current version. Without a query vector (the embedding API is down) search
falls back to BM25 full-text ranking instead of failing.
Measured on a private 32-question benchmark over a real 25k-chunk multi-agent memory (right file among the top 3 results):
setup | top-3 |
ChromaDB + local MiniLM (0.1.x) | 16 / 32 |
Voyage | 26 / 32 |
Voyage + kind weights + exact tokens + superseded (0.2.0) | 30 / 32 |
The numbers come from one corpus; treat them as a direction, not a guarantee.
Upgrading from 0.2.x
The lib package is now shared_memory: change from lib.store import ... to
from shared_memory.store import ... (or from shared_memory import ...).
Client configs that run mcp-server/server.py keep working through a shim;
switch them to the shared-memory-mcp command when convenient. Data files in
a clone stay where they were.
Agents without access to the server: a file bus
The MCP server is meant to stay on a private machine (see Security). Agents that cannot reach it, such as a chat assistant on a phone or a hosted agent with only a Google Drive or Dropbox connector, can still share work through a plain shared folder used as a message bus. One setup that works in practice:
Agent Bus/
├── 00 Welcome # what this is, which files to read first
├── 01 Rules # task and answer formats, what never goes here (secrets, personal data)
├── 01_Inbox # one file per task: task_id, from, to, priority, goal, expected result
├── 02_Outbox # one file per answer: task_id, agent, status done|blocked|needs_review
├── 03_Shared Memory # confirmed facts and decisions only, one file per decision
└── 05_Logs # traces, daily snapshotsRules that keep it sane: every task and answer carries a task_id; silence
from another agent is never treated as agreement; a record is a proposal, not
permission to act on anything irreversible; secrets stay on the server and the
bus carries only references. An agent that does have server access (for
example Claude Code on the same machine) bridges the two: it reads the inbox,
does the work with memory_search / memory_add, and writes confirmed facts
back as files. shared-memory does not ship a bus; the folder layout above is a
convention, not code.
Writing your own indexer
shared_memory/index_markdown.py is the template. An adapter needs to:
discover source items,
turn each into
(text, metadata)chunks — always setagentandsource, an ISOtimestampif you wantsincefiltering, andkind(note/doc/transcript) if you want the ranking weights,call
add_chunks(collection, embedder, ids, texts, metadatas, upsert=True).
Use a stable, deterministic id scheme so re-runs upsert instead of duplicating.
Design notes
Embeddings are computed by us, not by the store. Vectors are passed explicitly on
add/query, so the store stays decoupled from embedder availability.No vector index on SQLite. Search is a brute-force cosine over an in-memory numpy matrix: 25k chunks x 1024 dims is ~100 MB and a few milliseconds per query, with nothing to corrupt or rebuild. The matrix is reloaded when another process writes to the file.
Chunking (markdown): split by
##headings; fall back to paragraphs; soft-cap 8000 chars at word/newline boundaries. Paths in ids/metadata use POSIX form for cross-OS stability.Timestamps are ISO-UTC strings throughout; the SQLite backend normalizes them on write, so
Z,+02:00, naive and unix-epoch inputs compare correctly.Search-time hygiene: results are de-duplicated by content hash and short known-error strings are dropped. Scores are clamped to
[0, 1].Stats:
memory_statspages through metadatas to bound peak memory.
Roadmap
TTL / forgetting policy for high-volume sources.
Optional PII filter at index time.
Session-end hooks to auto-index new content.
More example adapters (chat logs, issue trackers).
Contributing
Issues and PRs welcome — see CONTRIBUTING.md. New indexer adapters (chat logs, issue trackers, docs) are a great place to start.
License
MIT.
Available Tools
5 toolsmemory_addMemory AddC
Add an ad-hoc memory chunk. Returns the generated id.
Inputs: text (required), metadata. Returns: result
memory_forgetMemory ForgetA
Delete memory chunks by explicit ids or by agent/source filter.
Deletion is irreversible, so there are two safety catches: dry_run is on by default, and deleting by filter requires expect_count equal to the number of matches, so an over-broad filter cannot wipe the collection by mistake. A call with no ids and no filters is refused.
Inputs: ids, dry_run, agent_filter, expect_count, +1 more. Returns: object
memory_searchMemory SearchC
Semantic search across shared memory.
Inputs: query (required), since, top_k, agent_filter, +1 more. Returns: result
memory_statsMemory StatsA
Return collection statistics: total count, breakdown by agent/source.
Inputs: no named parameters. Returns: object
memory_updateMemory UpdateA
Update an existing memory chunk in place; the id is kept.
Keeping the id means references to it stay valid. An unknown id raises, so a typo never silently turns into a new chunk.
Inputs: id (required), text, metadata, replace_metadata. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 5 tools
Each tool maps to a distinct lifecycle operation on a single resource: add, search, update, forget, stats. There is no meaningful overlap — search retrieves, stats aggregates, and the write operations are cleanly separated.
All five tools follow a strict memory_<verb> snake_case pattern with clear, conventional verbs (add, search, update, forget, stats). No mixed conventions or vague names.
Five tools is a tight, well-scoped surface for a memory store: write, read, mutate, delete, and observe. Every tool earns its place with no redundancy or filler.
The set covers the full CRUD lifecycle plus stats, and search likely substitutes for a direct get-by-id. Minor gaps remain around bulk operations and explicit id-based retrieval, but core workflows are covered without dead ends.
Maintenance
Related MCP Connectors
Persistent memory for AI agents. Semantic search, memory graph, W3C DID identity.
Memory system for AI agents with semantic search. Store and recall memories with ease.
Hosted persistent memory with semantic search, importance and TTL for AI agents.
Universal memory for AI agents and tools. Save, organize and search context anywhere.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceSelf-hosted semantic memory for AI agents. Save worklogs, decisions, and notes via MCP, then recall them across sessions by meaning rather than keyword. Backed by Postgres + pgvector with local embeddings (multilingual-e5-base).1MIT
- AlicenseAqualityDmaintenancePortable cognitive memory for AI agents with semantic search, decay, and deduplication.819 npm2MIT
- AlicenseNot gradedqualityCmaintenanceA semantic memory store for AI agents, enabling persistent memory across sessions via MCP tools with semantic search, scoped access, and local embeddings.MIT
- AlicenseNot gradedqualityDmaintenanceA portable self-hosted memory layer for AI tools, storing context, memories, and handoffs for access from any MCP-compatible client.48 npmMIT