Skip to main content
Glama
abho7

mcp-memory-hnsw

by abho7

mcp-memory-hnsw

Persistent semantic memory for Claude Code and Claude Desktop, backed by a from-scratch HNSW vector index and a local embedding model. Nothing leaves the machine: no embedding API, no hosted vector database, no network calls after the model is cached once.

Claude gets four tools — store_memory, search_memory, list_memories, delete_memory — and memories written in one session are recallable in every later one.

How it fits together

  Claude Code / Desktop
          │  MCP over stdio
  ┌───────▼─────────────────────────────────────┐
  │ mcp_memory/server.py    four MCP tools      │
  │ mcp_memory/store.py     persistence, tags   │
  │ mcp_memory/embedder.py  MiniLM via ONNX     │
  └───────┬─────────────────────────────────────┘
          │  imports, never modifies
  ┌───────▼─────────────────────────────────────┐
  │ hnsw-engine/   VectorDB + HNSWIndex         │
  │                (github.com/abho7/vectordb-hnsw) │
  └─────────────────────────────────────────────┘
          │
     ~/.claude/memory-hnsw/
       memories.json   source of truth: text, tags, ids
       index.npz       graph snapshot + vectors

hnsw-engine/ is a plain checkout, treated as a read-only dependency. Two things it does not provide — persistence and enumeration — are supplied by store.py from the outside, so the engine can be git pulled without merge conflicts.

Related MCP server: quarry

Setup

Requires Python 3.10+.

git clone https://github.com/abho7/vectordb-hnsw.git ./hnsw-engine
pip install -r requirements.txt
pytest                     # 63 tests

Then register the server with Claude Code (-s user makes it available in every project, not just this one):

claude mcp add memory-hnsw -s user -- /absolute/path/to/python /absolute/path/to/mcp-memory-server/run_server.py

Point it at run_server.py rather than -m mcp_memory.server: the CLI's own option parser claims the -m and rejects the command. The launcher also puts the package on sys.path itself, so the server does not depend on the client's working directory. Check it came up with claude mcp list.

For Claude Desktop, add it to claude_desktop_config.json (on Windows, %APPDATA%\Claude\; on macOS, ~/Library/Application Support/Claude/):

{
  "mcpServers": {
    "memory-hnsw": {
      "command": "/absolute/path/to/python",
      "args": ["/absolute/path/to/mcp-memory-server/run_server.py"]
    }
  }
}

Use an absolute path to the Python executable in both cases. Desktop and Claude Code do not inherit your shell's PATH, and a bare python that resolves in your terminal is the most common reason a server shows up dead. On Windows, escape backslashes in the JSON (C:\\Users\\...).

Newly added servers are picked up at startup, so restart Claude Code or Claude Desktop before the tools appear.

Copy SKILL.md into .claude/skills/memory-hnsw/SKILL.md (project) or ~/.claude/skills/memory-hnsw/SKILL.md (global) so Claude knows when to reach for the tools rather than only what they do.

The first store_memory call downloads ~90MB of MiniLM weights to ~/.cache/mcp-memory-hnsw/. Every run after that is fully offline.

Configuration

Variable

Default

Purpose

MCP_MEMORY_DIR

~/.claude/memory-hnsw

where memories are stored

MCP_MEMORY_MODEL_DIR

~/.cache/mcp-memory-hnsw/all-MiniLM-L6-v2

model weight cache

MCP_MEMORY_ENGINE_DIR

./hnsw-engine

path to the engine checkout

MCP_MEMORY_EAGER_LOAD

unset

1 loads the model at startup instead of on first use

Demo: recall across two sessions

Two separate OS processes sharing nothing but a directory on disk — which is what two Claude sessions are. This is real captured output.

Session 1 writes and exits:

$ python scripts/demo_two_sessions.py write --reset
=== SESSION 1 (process A) ===

Opened store at C:\Users\satya\mcp-memory-server\.demo-memory in 0.00s (0 existing)

  stored 685814f899be  [decision, db]  We picked Postgres over MySQL because we need JSONB indexing
  stored eb48e4499d68  [ops]  The staging deploy key rotates on the first of every month
  stored 6f1dbc3c81bf  [preference]  Ali prefers code review comments to be specific and short
  stored 4ba2f6b59477  [testing]  Integration tests live in tests/integration and need Docker running

4 memories written to disk.
Session 1 now exits. Nothing stays in RAM.

Session 2 is a brand new process:

$ python scripts/demo_two_sessions.py read
=== SESSION 2 (process B, started fresh) ===

Loaded 4 memories in 0.13s (restored from snapshot)

  query: "which database did we choose and why"
    -> [0.393] We picked Postgres over MySQL because we need JSONB indexing

  query: "how should I write review feedback"
    -> [0.375] Ali prefers code review comments to be specific and short

  query: "what do I need installed to run the slower tests"
    -> [0.378] Integration tests live in tests/integration and need Docker running

Tag filter (list_memories tag='ops'):
  eb48e4499d68  The staging deploy key rotates on the first of every month

None of those queries share meaningful wording with the text they matched — "which database did we choose" against "We picked Postgres over MySQL", "run the slower tests" against "Integration tests ... need Docker". That is the point of embedding rather than grepping.

In Claude, the same exchange looks like:

Session 1 — "Remember that we went with Postgres over MySQL for JSONB indexing." → store_memory → Stored memory 685814f899be with tags [decision, db].

Session 2, next day — "Remind me which database we settled on?" → search_memory("which database did we settle on") → We picked Postgres over MySQL because we need JSONB indexing

Design notes

Persistence, given an engine that has none. hnsw-engine's ARCHITECTURE.md lists in-memory-only as a known limitation: every restart rebuilds from scratch. Rebuilding is not viable at MCP startup — the engine's published build cost is 5.1s for 500 vectors at dim=32, and MiniLM's 384 dimensions make each distance computation over ten times more expensive.

So the store snapshots the built graph (vectors, per-layer adjacency in CSR form, entry point) to index.npz and restores it directly. Measured on this machine with 200 memories:

time

load from snapshot

0.08s

load by rebuilding

4.38s

warm query

5ms

first query (includes model load)

0.48s

Restoring means writing into the engine's private attributes, which would normally be fragile. It is safe here because it is guarded: memories.json is the source of truth, the snapshot carries a fingerprint of exactly which entries it should contain, and every structural assumption is checked before use. Any mismatch — stale snapshot, corrupt file, changed engine layout — falls back to rebuilding from the stored text. The worst case is a slow startup, never a wrong or lost memory. Both paths are tested, including a test that they return identical search results.

Rebuilding is also the compaction pass. The engine soft-deletes: a deleted node stays in the graph and is filtered out of results, so the index only ever grows. Its docs list compaction as future work. A rebuild here drops tombstones, so deleting a lot and forcing one rebuild reclaims the space.

IDs are never recycled. Re-inserting an id the engine has soft-deleted leaves the old vector live in the graph under that same id, which surfaces as a duplicate hit carrying stale text. (The engine's own test_reinsert_after_delete_succeeds passes because it only asks for k=1.) Every store_memory mints a fresh id, so the situation never arises.

ONNX instead of PyTorch. Same all-MiniLM-L6-v2 weights and the same mean-pool-then-normalize computation sentence-transformers performs, run under onnxruntime: ~90MB installed against ~2.5GB, and a cold start well under a second. Embeddings are unit length, so the engine's cosine distance is exactly 1 - similarity.

Similarity scores read low. MiniLM puts genuine paraphrase matches around 0.3–0.5, not 0.9. Rank matters; the absolute number does not. SKILL.md tells Claude to read them relatively.

Tests

pytest                                        # 63 tests, no model needed
MCP_MEMORY_TEST_REAL_MODEL=1 pytest           # + 3 against real MiniLM weights

Most tests use a deterministic hashing embedder rather than the neural model: fast, offline, and it makes ranking assertions exact instead of dependent on what a neural net happens to think.

File

Covers

test_store.py

CRUD, tags, snapshot round-trip, all three rebuild-fallback triggers, compaction, id recycling, graph-structure fidelity, both load paths agreeing

test_tools.py

each of the four tools, MCP registration and schemas, dispatch through server.call_tool, cross-session recall through the tools

test_embedder.py

mean-pooling under the attention mask, padding invariance, unit length; real-model tests are opt-in

The engine's own 28 tests still run from hnsw-engine/ (cd hnsw-engine && pytest) and are unaffected — nothing in that directory was modified.

Limitations

  • No update tool. Superseding a memory is store-then-delete.

  • Tag filtering happens after search, not inside it, so list_memories filters by tag but search_memory ranks on text alone.

  • Single writer. Two servers pointed at one data directory will clobber each other's writes; each write rewrites both files atomically, but there is no cross-process lock.

  • Plaintext on disk. memories.json is human-readable and unencrypted. Don't store secrets in it.

  • Everything in RAM while running. Fine for thousands of memories at 384 dimensions; this is not a billion-scale store.

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    Persistent memory for Claude Code. Automatically indexes every conversation and provides production-grade hybrid search (BM25 + vectors + reranker) via MCP tools. 100% local, zero config, zero API keys, zero invoice.
    16
    49
    7
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Enables local semantic search over documents and code for Claude Code and Claude Desktop, running entirely offline with local embeddings and vector storage.
    11
    3
    MIT
  • A
    license
    -
    quality
    D
    maintenance
    Provides persistent, searchable memory for Claude Code using local SQLite, semantic embeddings, and full-text search, enabling Claude to recall and retrieve context across sessions and projects without external services.
    10
    3
    MIT
  • F
    license
    -
    quality
    D
    maintenance
    Provides persistent semantic memory for Claude Code via local embeddings and six MCP tools, enabling context storage and retrieval across sessions without cloud dependencies.

View all related MCP servers

Related MCP Connectors

  • User-owned memory for AI agents, Copilot, Claude, IDEs, CLIs, and chat apps over remote MCP.

  • Persistent memory for AI agents. Search, store, and recall across sessions.

  • Persistent memory and knowledge management for AI agents with semantic search and 50+ tools.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/abho7/mcp-memory-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server