Skip to content

Releases: studiomeyer-io/local-memory-mcp

local-memory-mcp v2.4.3

Choose a tag to compare

@madetocreate madetocreate released this 19 Aug 17:51

Security + correctness release. 239 → 251 tests. No schema change, no API change.

Fixed — memory_import bypassed the validation every other write path enforces (#29)

Reported by @pmario with a runnable repro.

memoryImport() guarded incoming records with isStr() / asNum() instead of the Zod shapes the interactive tools use, which made the import envelope the one route into the database that skipped every constraint. An envelope could therefore write rows no tool could create: an open-string category, confidence of 999, 50 000 characters of content (embedded before the row was evaluated), an off-enum memoryType, and — the part that matters most — an arbitrary id.

A non-UUID id does not stay inside its row. It becomes the primary key in the embeddings table, a key in the FTS index, and the key of the in-memory vector map, which is only collision-free across record types because ids are UUIDs. It also reaches downstream filenames in tooling built on top of an export. ../x was therefore a path-traversal vector rather than a data-quality wart.

Every record type is now parsed with a Zod shape mirroring the bounds of its interactive counterpart — learnSchema, decideSchema, entityCreateSchema, entityObserveSchema, entityRelateSchema — and every id and foreign key is normalised to a UUID (see below). Failures land in the existing skipped.malformed counter, so an import stays additive and one bad record never aborts the envelope. Unknown fields are still stripped rather than rejected, so an envelope from a newer exporter remains importable by an older server.

The embedding pass now shares those parsed results instead of running its own parallel copy of the guards, which closes a second gap: an oversized field used to be paid for in inference and thrown away afterwards. Schema validity can no longer drift between the two phases. Referential eligibility still cannot be known before the transaction — an observation whose entity is missing, or a row INSERT OR IGNORE drops as a duplicate, is embedded before that is decidable — so a wasted embedding is still possible; it is simply never attached.

LEARNING_CATEGORIES is exported from learn.ts so import and learn() validate against one list rather than two copies that drift.

Ids are canonicalised, not rejected. Refusing a non-UUID id looks safer and is worse. Until v2.4.2 the import accepted any string, so anyone who fed this server through a hand-rolled importer has non-UUID rows in their database today; their next export carries those ids, and a strict import would drop exactly the rows a restore exists to save. A non-UUID id is therefore mapped to an RFC 4122 §4.3 name-based (v5) UUID derived from it. The mapping is deterministic, so re-importing the same envelope stays idempotent and INSERT OR IGNORE still dedupes; and pure, so a foreign key resolves to the same value as the id it points at with no bookkeeping. Ids that are already UUIDs pass through untouched, so a normal export round-trips unchanged. An empty id remains malformed — there is nothing to derive from.

Duplicate ids within one envelope are rejected. Valid UUID syntax is not uniqueness. Two records sharing an id — within a type or across two — collide in the id-keyed vector map, and INSERT OR IGNORE would drop the second row after the first had already been handed its embedding, attaching one record's vector to another record's text. First occurrence wins; every later one counts as malformed.

The last unbounded writes are bounded. profile values and goal reached the meta table with no length check, and profile took its key straight from the envelope. Values are capped, keys must be short and match [A-Za-z0-9_.-]. Each envelope array is capped at 100 000 items, with the overflow reported through skipped.malformed rather than truncated silently.

Changed — English for all user-facing strings (#25, #28)

Also reported and fixed by @pmario. message: values were German while error: values and the tool descriptions in tools/registry.ts were already English — 26 against 26, with 15 of the German ones containing English fragments. All 29 literals across 9 files are now English. No behaviour change. The PR also corrected an assertion in session.test.ts that checked for 'Projekt:' and would otherwise have kept passing while testing nothing.

v2.4.2 — MCPB bundle boot fix

Choose a tag to compare

@madetocreate madetocreate released this 01 Aug 20:58

Same-day patch on v2.4.1. The dynamic-version read introduced in 2.4.1 crashed the Claude Desktop (MCPB) bundle at import time — the bundle places code under server/ and did not ship package.json, so the probe threw before the server started. If you downloaded a v2.4.1 .mcpb bundle, replace it with a v2.4.2 one. npm installs of 2.4.1 were unaffected.

  • build-mcpb.sh ships package.json at the bundle root; the version probe tries both layouts and on total failure advertises 0.0.0 with a warning instead of dying — a version string can never again kill the boot.
  • The version-consistency test now also covers package-lock.json and the bundle copy step.
  • The rewritten updated_similar regression test seeds a bm25-threshold-reaching corpus so it genuinely detects any reintroduction of the removed fuzzy-merge branch.

v2.4.1 — every version carrier now agrees

Choose a tag to compare

@madetocreate madetocreate released this 01 Aug 20:44

Same-day patch on v2.4.0, from a cross-model review pass. v2.4.0 shipped three diverging versions at once: npm said 2.4.0, the MCP handshake advertised a hardcoded 2.3.0, and the MCPB manifest still said 2.2.0 — clients and Claude Desktop bundles each self-identified as older code.

  • The server now reads its version from package.json at boot — the handshake can no longer go stale.
  • build-mcpb.sh syncs the bundle manifest from package.json before packing; freshly built bundles self-identify correctly.
  • A version-consistency test pins every carrier (package.json, server.json ×2, MCPB manifest, no hardcoded server literal), so the next added carrier must be registered there.
  • The learn.ts docstring no longer describes the fuzzy-merge path removed in 2.4.0, and the legacy updated_similar test — which accepted either outcome — now pins the new contract: similar content is always added, the shorter original survives.

No functional changes beyond the advertised version. If you installed 2.4.0, the handshake reporting 2.3.0 was cosmetic — the fixes were active.

v2.4.0 — hybrid search actually on, memory_learn no longer destroys data

Choose a tag to compare

@madetocreate madetocreate released this 01 Aug 20:13

Both core fixes in this release were contributed by @pmario, who read the whitepaper, evaluated the code against it, and filed four issues with file:line root causes, cross-platform repros, and an explanation of why 233 green tests could not see either bug. This is the kind of contribution maintainers hope for.

Fixed

  • Vector search never loaded (#23, #24 by @pmario). require('sqlite-vec') threw ReferenceError on every boot under "type": "module", and the catch misreported it as a missing platform binary — hybrid retrieval was silently FTS5-only for every install since release. Now loads via createRequire. On boot you should see [vector] sqlite-vec extension loaded; memory_health reports vector.enabled: true.
  • memory_learn could silently overwrite an unrelated learning (#21, #22 by @pmario). The FTS5 similarity gate OR-ed every token of the new content, so ordinary words matched almost any stored row; bm25() is an unbounded relevance score, not a similarity, so the remaining decision was effectively "is the new entry longer?". The fuzzy-merge branch is removed: exact duplicates still bump usage_count, and enriching an existing entry is explicit via memory_learn_update. A regression test now asserts that storing one learning never removes or rewrites another.
  • Windows: npm test and npm run build work (env-var prefix and cp/mkdir -p/chmod replaced with vitest.config.ts and scripts/copy-assets.mjs) (#22).
  • README: the SessionStart hook example was missing hookEventName, so Claude Code silently dropped the injected context (#20 by @pmario).
  • README/WHITEPAPER no longer describe a similarity measure the code never had.

Upgrading

npm i -g @studiomeyer/local-memory-mcp@2.4.0 (or bump your npx pin). No schema migration; existing databases work as-is. If a learning of yours was previously overwritten, the data is unfortunately not recoverable — this class of write is exactly what this release removes.

v1.0.8 — trust + adoption sweep

Choose a tag to compare

@madetocreate madetocreate released this 22 May 11:28

🎉 New: One-click install via MCPB bundle for Claude Desktop on Linux x64. Download local-memory-mcp-1.0.8-linux-x64.mcpb from the assets below, double-click, done. No JSON editing, no npm install, no terminal. macOS / Windows users: keep using the npx -y install path — it rebuilds the SQLite native binary for your platform automatically.


Trust + adoption polish based on an outside-the-fleet audit

Three version drifts and one architectural omission. All small fixes with disproportionate trust impact for a project that is small but used.

Quickstart

npm install -g @studiomeyer/local-memory-mcp
local-memory-mcp

Add to Claude Desktop, Claude Code, Cursor, Codex, Continue, or any MCP client — one SQLite file under your OS data dir, no cloud, no API keys.

Fixed — Version drift sweep

  • src/server.ts hardcoded SERVER_VERSION = '1.0.6' while package.json was at v1.0.7. Every MCP initialize response saw the wrong version. Now reads 1.0.8 consistently.
  • server.json (MCP Registry manifest) was at 1.0.1 — six releases behind. Bumped to 1.0.8.
  • package.json bumped to 1.0.8.

Added — Four tools that already had handlers + tests, but were unreachable

entityCreate, entityDelete, goal, and health were exported, had Zod schemas, had unit-test coverage — but were missing from the TOOLS array. MCP clients calling tools/list got 13 tools instead of 17. Now registered:

  • memory_entity_create — explicit entity creation (idempotent on name + entityType)
  • memory_entity_delete — destructive entity removal (entity + observations + relations)
  • memory_goal — read / set / clear a user goal in the profile table
  • memory_health — SQLite integrity + page-count + DB-size + WAL status (zero input)

TOOLS.length is now 17. Updated the registry drift-test count assertion + added a new test that pins the four formerly-orphan tools by name so this class of drift cannot recur silently.

Added — CI workflow

.github/workflows/test.yml runs tsc --noEmit + npm test + npm run build on Node 20 / 22 / 24 for every push + PR to main. The repo previously had only the tag-driven publish-registry.yml — no green-on-PR signal for contributors.

Removed

  • Duplicate root CONTRIBUTING.md (canonical guide is .github/CONTRIBUTING.md).
  • bun.lock — two lockfiles is a known drift source. package-lock.json stays as source of truth.

Notes

  • 88/88 tests green, tsc clean, build clean.
  • No API breakage. The four newly-registered tools were already callable as importable functions; they are now also reachable over MCP.
  • Recommended upgrade for everyone on 1.0.x — wire-version is finally correct.

What's unique about this project

We're the only relevant local-memory MCP player with zero external dependencies beyond SQLite (no embedding model, no vector DB, no Docker, no API key), MIT-licensed knowledge graph (Mem0 has Graph behind Pro paywall, Zep / Cognee are cloud-only), and decision-tracking as a first-class surface (memory_decide — unique among memory servers).

— StudioMeyer, Palma de Mallorca

v1.0.7 — entitySearch fix + obs_au trigger + 71 new tests

Choose a tag to compare

@madetocreate madetocreate released this 21 Apr 18:44

Correctness patch + test expansion. No API changes.

Fix

memory_entity_search silently lost summary and observation hits.
The primary FTS5 query joined search_fts to entities with an OR that mixed entity-level and observation-level matches. That puts the MATCH subquery behind a derived JOIN, which SQLite rejects with "unable to use function bm25 in the requested context". The try/catch caught it and fell back to WHERE name LIKE ? — so any hit whose match lived in the entity summary or in an observation disappeared.

Rewrite: two MATCH-ed subqueries UNION ALL'd by entity_id, then JOIN entities + GROUP BY with MIN(rank). The fallback LIKE now also covers summary and observations.content via a LEFT JOIN, so an FTS outage still returns sensible rows.

Add

  • obs_au trigger on entity_observations so an UPDATE to an observation's content refreshes the FTS5 row instead of leaving a stale one pointing at the old text. Symmetric with the existing triggers on learnings / decisions / entities.

Tests

Expanded from 12 → 83 tests (+71). New files:

  • src/tools/entity.test.ts — 35 tests covering entity_create, entity_observe, entity_search, entity_open, entity_relate, entity_delete. Exercises the UNION-ALL rewrite, the obs_au trigger, and every zod-schema edge case.
  • src/tools/session.test.ts — 19 tests covering session_start / session_end, same-project ordering, the auto-close-latest-open path, archived-learning exclusion in recentLearnings.
  • src/tools/search.test.ts — 17 tests covering cross-type hits (learning / decision / entity / observation), type filters, the v1.0.6 archived-learning regression guard, bare-LIMIT semantics.

All 83 tests green, tsc --noEmit clean.

Install

npx -y @studiomeyer/local-memory-mcp