Knowledge as of Oct 26, 2026
The Inference TimesReference architectures, tutorials, demos and free skills — by Nitin Goswami
Epoch III · Step 16Ask the AI Nitin →

DESIGN DOCUMENT · mcp-memory-demo

MCP Memory Demo: give any MCP client a long-term memory it can search by meaning

A local MCP server that lets Claude Desktop, Cursor or your own agent remember facts and recall them by meaning later. Each tool maps to a CoALA memory action.

Stack
Python · MCP Python SDK · ChromaDB · uv
Models
all-MiniLM-L6-v2
Date
October 26, 2026
Status
Approved · built
Live demoComing soon.Run it locally ↓

Purpose & scope

The demo teaches how an agent gets memory that outlives one conversation. The MCP server owns a long-term episodic memory: a Chroma collection of things the user said, each with a timestamp, tags and a source. The client's model writes to it with a learning action (remember) and reads from it with a retrieval action (recall). Recalled memories land in the model's working memory (the context window) as a tool result, and the model reasons over them to answer. The vocabulary follows the CoALA framework (after Sumers et al., 2023). The protocol between client and server is MCP, so any MCP client gets the same memory.

rememberStore one memoryLearning action
personal_memoryEmbed · index on diskEpisodic memory (long-term)
recallEmbed query · top-kRetrieval action
CallToolResultMemories in contextWorking memory
client LLMAnswer using memoriesReasoning → external action

In scope

  • Expose four MCP tools (remember, recall, list_recent, forget) and one resource (memory://stats).
  • Embed and search memories locally with Chroma's default embedding function (all-MiniLM-L6-v2 via ONNX). No API keys.
  • Return a similarity score (1 − cosine distance) with every recalled memory, and filter by tags.
  • Serve the same code over stdio (spawned by a desktop client) and streamable HTTP (--http).
  • Seed five sample memories and run one recall with seed.py.

Out of scope

Authentication and per-user memories, deduplication or consolidation of repeated facts, time-range and source filters, hybrid keyword + vector search, and any user interface of its own (the MCP client is the interface). Section 10 lists these as follow-ups.

Use cases

You, via an MCP client personal-memory MCP server Remember a fact Recall by meaning List recent memories Forget a memory Read store stats all-MiniLM-L6-v2 local ONNX model embed text embed query
Use case diagram: five things a person can do with their memory through any MCP client.
IDUse caseBehaviour
UC1Remember a factThe model calls remember with the text and optional tags and source. The server stores it and returns {id, status: "stored"}.
UC2Recall by meaningThe model calls recall with a natural-language query. The server returns up to limit (default 5) memories, each with a score, best first.
UC3List recent memoriesThe model calls list_recent. The server returns up to limit (default 10) memories, newest first.
UC4Forget a memoryThe model calls forget with an id. The server deletes it and returns deleted: true, or false if no such id exists.
UC5Read store statsThe client reads memory://stats. The server returns the count, collection name and storage path as text.

Architecture

There is one runtime part: a Python process running server.py. It owns the MCP surface (tool and resource definitions, transport). memory_store.py owns storage and search, through Chroma's PersistentClient. Chroma owns embedding (the MiniLM ONNX model, cached in ~/.cache/chroma) and the on-disk index in ./data/chroma. The LLM lives in the MCP client, not in this repo.

MCP client Claude Desktop · Cursor server.py · MCPServer remember recall list_recent forget memory://stats stdio · or HTTP :8000/mcp tools/call memory_store.py MemoryStore add · search · list search() Chroma client PersistentClient query all-MiniLM-L6-v2 ONNX · 384 dims embed ./data/chroma cosine HNSW top-k
Component diagram: the recall path from the MCP client through server.py, MemoryStore and Chroma to the on-disk index.
Your computer MCP client Claude Desktop, Cursor… Python 3.10+ · uv venv server.py stdio | 127.0.0.1:8000/mcp spawns · stdio Other MCP client local agent, script HTTP POST /mcp ./data/chroma MCP_MEMORY_PATH read / write ~/.cache/chroma onnx_models · 79.3 MB load model
Deployment view for a single computer: stdio subprocess by default, HTTP on 127.0.0.1:8000 as an option.

Key flows

4.1 Remember: tools/call → add → embed → write

You MCP client server.py remember MemoryStore.add Chroma collection MiniLM (ONNX) 1 “Remember I’m allergic to shellfish” 2 tools/call remember {content, tags} 3 add(content, tags, source) 4 uuid4 hex, created_at (UTC), tags joined 5 add(ids, documents, metadatas) 6 embed document 7 384-dim vector 8 write to ./data/chroma 9 ok 10 memory id 11 {id, status: "stored"} 12 “Got it.”
Sequence: remember (CoALA learning action).

4.2 Recall: tools/call → embed query → nearest neighbours → score

You MCP client server.py recall MemoryStore.search Chroma collection MiniLM (ONNX) 1 “What should I read next?” 2 tools/call recall {query, limit: 5} 3 search(query, limit, tags) 4 n = limit, or limit × 5 (≤ 100) with tags 5 query(query_texts, n_results = n) 6 embed query 7 384-dim vector 8 cosine HNSW, nearest n 9 ids, documents, metadatas, distances 10 score = 1 − distance; tag filter; cut 11 [{id, content, tags, score}] 12 tool result (JSON) 13 answer that uses the memories
Sequence: recall (CoALA retrieval action).

With tags, search asks Chroma for limit × 5 neighbours (at most 100), keeps those that share any tag, and cuts to limit. Without tags it asks for exactly limit.

Backend design

server.py +mcp: MCPServer("personal-memory") +store: MemoryStore +remember(content, tags, source) +recall(query, limit, tags) +list_recent(limit) +forget(memory_id) +stats() memory://stats +main() --http → streamable-http seed.py +SAMPLES: 5 example memories +main() MemoryStore +path: Path (MCP_MEMORY_PATH) +collection_name (MCP_MEMORY_COLLECTION) +add(content, tags, source) → id +search(query, limit, tags) → list[dict] +list_recent(limit) → list[dict] +delete(memory_id) → bool +stats() → {count, path, collection} memory_store.py helpers -_flatten_query(result) → dicts with score -_flatten_get(result) → dicts search() add() calls
Class diagram: server.py tools delegate to MemoryStore, which wraps the Chroma collection.
FileResponsibility
server.pyCreates MCPServer("personal-memory"), defines the four tools and the memory://stats resource, picks stdio or streamable HTTP in main().
memory_store.pyMemoryStore: opens the persistent Chroma collection (cosine HNSW), adds, searches, lists, deletes, counts. Turns Chroma's nested results into flat dicts.
seed.pyAdds five sample memories and prints the top 3 hits for "what should I read next?".
tests/test_memory_store.pyFive offline tests of MemoryStore against a temporary directory.
examples/claude_desktop_config.jsonThe mcpServers entry for Claude Desktop.

System prompt

None. The server sends no system prompt. The model decides when and how to call each tool from its name, docstring and parameter descriptions, so those strings do a prompt's job. Verbatim from server.py:

API contract

MCP over JSON-RPC 2.0. Transport: stdio, or streamable HTTP at POST http://127.0.0.1:8000/mcp (responses as text/event-stream, session in the mcp-session-id header).

MethodPathRequestResponse
tools/list——4 tools with JSON Schemas
tools/callremembercontent, tags?, source?{id, status: "stored"}
tools/callrecallquery, limit? (1–50, 5), tags?[{id, content, created_at, tags, source, score}]
tools/calllist_recentlimit? (1–100, 10)[{id, content, created_at, tags, source}]
tools/callforgetmemory_id{id, deleted}
resources/readmemory://stats—text: count, collection, storage path

Errors: invalid arguments (a missing content, limit out of range) and unknown tool names come back as a tool result with isError: true and the validation message; forget on an unknown id is not an error, it returns deleted: false.

Data model

Chroma creates the schema on first use: get_or_create_collection("personal_memory", metadata={"hnsw:space": "cosine", …}). One record is one memory: a uuid4 hex id, the text, its 384-dimension embedding, and three metadata fields. Chroma metadata can't hold lists, so tags are stored as one comma-joined string and split again on read.

collection name "personal_memory" hnsw:space cosine description metadata memory record id PK · uuid4 hex document the memory text embedding 384 dims · MiniLM created_at ISO 8601, UTC tags comma-joined string source "" when none contains metadata: created_at · tags · source
Entity-relationship diagram of the Chroma collection.

Frontend design

Not applicable: the demo has no UI of its own. The MCP client (Claude Desktop, Cursor, an agent) is the frontend; it shows tool calls and their results in its own interface.

Project layout

mcp-memory-demo/
├── server.py                     MCP surface: 4 tools, 1 resource, transports
├── memory_store.py               MemoryStore over a Chroma collection
├── seed.py                       5 sample memories + one recall
├── tests/test_memory_store.py    offline tests (pytest)
├── examples/
│   └── claude_desktop_config.json
├── docs/
│   ├── DESIGN.md                 this document
│   └── figures/                  fig-01 … fig-07 (hand-drawn SVG)
├── .github/                      CI · issue and PR templates
├── .env.example                  MCP_MEMORY_PATH · MCP_MEMORY_COLLECTION
├── pyproject.toml                mcp[cli] ≥ 2.0 · chromadb ≥ 1.0 · pytest (dev)
├── CONTRIBUTING.md · LICENSE (MIT) · README.md
└── data/chroma/                  created at run time, git-ignored

Run & verify

git clone https://github.com/nitingoswami-io/mcp-memory-demo.git && cd mcp-memory-demo
uv sync                     # Python 3.10+; installs mcp, chromadb, pytest
uv run seed.py              # first run downloads the MiniLM model (79.3 MB)
uv run pytest -q            # 5 passed, offline
uv run server.py            # stdio; or: uv run server.py --http  → http://127.0.0.1:8000/mcp
# then add examples/claude_desktop_config.json to Claude Desktop and restart it
CheckExpected
uv run seed.pyFive stored <id> lines, Total memories: 5, then the reading-list memory as the top hit for "what should I read next?" (score 0.3483 in our run).
tools/list from any MCP clientremember, recall, list_recent, forget; resources/list shows memory://stats.
recall "who leads project atlas?" after seedingThe Project Atlas memory first, with tags: ["work", "project-atlas"], source: "slack", score 0.7568 in our run.
forget with an id that doesn't exist{"id": "…", "deleted": false}, not an error.
recall with limit: 0Tool error: "Input should be greater than or equal to 1".
HTTP: POST /mcp with initialize200 OK, content-type: text/event-stream, an mcp-session-id header.

Next steps

IdeaWhat it teaches
Hybrid search: BM25 + vector score, then rerankWhy pure embeddings miss names and exact phrases; a better retrieval action.
created_at range and source filters in recallStructured retrieval alongside semantic retrieval; "last Tuesday" queries.
Consolidation: merge near-duplicate memories, summarise old onesReflection as a learning action that rewrites long-term memory (after Park et al., 2023).
Split facts from events: a semantic store next to the episodic oneThe CoALA difference between semantic and episodic memory.
OAuth on the HTTP transport and one collection per userWhat changes when memory is shared: auth, isolation, audit.
More resources (memory://recent, memory://tags/{tag})When context should be read by the client rather than fetched by a tool call.

Contribute

Fork the repo, pick an idea from the table, and open a pull request. Bugs and unclear steps go in issues; the workflow is in CONTRIBUTING.md.

Credits & sources

  1. Theodore R. Sumers, Shunyu Yao, Karthik Narasimhan, Thomas L. Griffiths. Cognitive Architectures for Language Agents (2023)The CoALA vocabulary (episodic and working memory, learning, retrieval and reasoning actions) used to map each tool.
  2. Model Context Protocol. Model Context Protocol Specification (revision 2026-07-28) (2026)The tool / resource split, JSON-RPC methods and the stdio and streamable HTTP transports.
  3. Model Context Protocol. MCP Python SDK · MITMCPServer, the tool and resource decorators, and the client used to test the server.
  4. Chroma. Chroma · Apache-2.0The persistent vector store, cosine HNSW index and default embedding function.
  5. sentence-transformers. all-MiniLM-L6-v2 · Apache-2.0The 384-dimension embedding model Chroma runs locally.
  6. Joon Sung Park et al.. Generative Agents: Interactive Simulacra of Human Behavior (2023)The idea of reflection over a memory stream, cited in Next steps.
  7. Original workThe server, store wrapper, tests and figures are new in this demo.

Demo · RAG

RAG Demo: ask real-world documents, see the evidence

A minimal web app that shows semantic Retrieval-Augmented Generation end to end. Pick a knowledge base (HR policies, a lease, a router manual), ask a question, and see the passages, similarity scores and database rows behind every answer. Each stage maps to the CoALA framework.

  • FastAPI
  • LangChain
  • pgvector
  • Next.js
FollowsCoALA →