memory — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited memory (Plugin) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A standalone semantic-memory MCP server. It stores text "memories" with free-form provenance and recalls them by meaning (vector search), by keyword (FTS5), or by structured filter. Backed by SQLite + [sqlite-vec], embeddings from a local [Ollama] model (or an opt-in remote provider).
It is persona-agnostic: no journal/persona/approval coupling, just a generic memory store and tool surface. It can be used on its own, or as a Claude Code plugin.
[sqlite-vec]: https://github.com/asg017/sqlite-vec [Ollama]: https://ollama.com
A memory is content plus provenance:
| field | meaning |
|---|---|
content | the text (embedded for recall) |
source | where it came from — free-form (owner, web, tool:foo) |
observed | how it was observed — free-form (stated, inferred, imported) |
date | ISO date the memory pertains to (defaults to today, UTC) |
tags | free-form tags for filtering |
supersedes | id of a prior memory this one replaces (history is kept) |
Recall is hybrid: recall (semantic), lexical (FTS5 keyword), and query (structured). reindex + recall_docs index/search a directory of markdown documents separately from the memory store.
The server registers under the id `fortytwo-memory`, so a consumer calls the tools as mcp__fortytwo-memory__<tool>:
| tool | description |
|---|---|
store | store a memory (+ provenance); set supersedes to replace one |
query | structured query (source/observed/tag/time; live rows by default) |
recall | semantic top-k recall by meaning |
recall_docs | semantic recall over reindexed markdown |
lexical | full-text keyword search (FTS5) |
reindex | self-heal the doc index from a markdown directory |
export_range | render a date range of memories to markdown |
On connect, the server also sends usage instructions in the MCP initialize result: when to search vs. store, which search tool fits, how to correct a memory with supersedes, and reindex caveats. Hosts such as Claude Code add them to the model's context. The text lives in src/server.ts; update it when the tool surface changes.
Consumers depend on the tool surface, not the internals. The contract is versioned:
import { MEMORY_TOOL_CONTRACT_VERSION, memoryToolContract } from '@justfortytwo/memory/contract';a contract break → bump MEMORY_TOOL_CONTRACT_VERSION. Siblings pin a caret range on @justfortytwo/memory, so a major bump forces an explicit opt-in.
memoryToolContract is the authoritative human-readable list of tools and their guarantees, kept in sync with the wire schema in src/tools.ts.
The default embedder is `OllamaEmbedder`, which calls a local Ollama /api/embeddings endpoint.
OLLAMA_BASE_URL=http://localhost:11434 # default
EMBED_MODEL=qwen3-embedding:0.6b # default model (1024-dim)Pull the model once:
ollama pull qwen3-embedding:0.6bIf EMBED_MODEL is unset, the server falls back to a deterministic, dependency-free `FakeEmbedder` — useful for tests, CI, and first-run smoke checks with zero infra. (The vector tables are fixed at 1024-dim; a model with a different dimensionality requires a schema change.)
Ollama stays the default. To use a remote OpenAI-compatible provider instead, set EMBED_PROVIDER=openai:
EMBED_PROVIDER=openai
EMBED_MODEL=text-embedding-3-small # must be in the allowlist below
EMBED_API_KEY=sk-... # optional for self-hosted servers
EMBED_BASE_URL=https://api.openai.com/v1 # default for OpenAI/Voyage models; required otherwiseOnly models that emit (or can be truncated to) 1024 dims are accepted, because the vector tables are fixed at that size. Any other EMBED_MODEL fails at startup with the accepted list:
EMBED_MODEL | Default EMBED_BASE_URL | Notes |
|---|---|---|
text-embedding-3-small | https://api.openai.com/v1 | truncated to 1024 via dimensions |
text-embedding-3-large | https://api.openai.com/v1 | truncated to 1024 via dimensions |
voyage-3-large | https://api.voyageai.com/v1 | 1024 by default |
BAAI/bge-m3 | none — set EMBED_BASE_URL | self-hosted, e.g. vLLM; 1024 native |
Qwen/Qwen3-Embedding-0.6B | none — set EMBED_BASE_URL | self-hosted, e.g. vLLM; no query instruction prefix is applied |
For self-hosted servers the model must be served under exactly the name above (vLLM: --served-model-name).
Switching models on an existing database: two models with the same dimension do not share a vector space. Vectors already stored by another model are not comparable with new query vectors, andrecallwill not error — it will return plausible but wrong neighbours. Re-embed existing memories with the library'sreembed(h, embedder, id)(one id at a time; no bulk command or MCP tool is provided) and re-run thereindextool for docs after switching. New databases are unaffected.
npm install @justfortytwo/memoryPrerequisites. Requires Node.js >= 20. The package depends on [better-sqlite3] ^12, a native module that ships prebuilt binaries for LTS Node releases — on an unsupported Node version or platform, npm install will compile it from source (needs a C/C++ toolchain). It also needs an embedding source for real embeddings — a local (or remote) [Ollama], or an opt-in remote provider; without EMBED_MODEL set it falls back to a deterministic FakeEmbedder (see Embedder). sqlite-vec ships as a bundled npm dependency — no manual SQLite-extension install is needed.
[better-sqlite3]: https://github.com/WiseLibs/better-sqlite3
# build (once); the server runs the built JS, not TS
npm run build
# apply migrations to the DB (DB_PATH or ./memory.db)
DB_PATH=./memory.db npm run migrate
# run the MCP server over stdio
DB_PATH=./memory.db EMBED_MODEL=qwen3-embedding:0.6b fortytwo-memoryThe bin is fortytwo-memory → dist/index.js. You can also run it with npx -y @justfortytwo/memory (it is published to npm).
.mcp.jsonTo use the server in any MCP host (not as a Claude Code plugin), drop this into your project's .mcp.json with a real DB path:
{
"mcpServers": {
"fortytwo-memory": {
"command": "npx",
"args": ["-y", "@justfortytwo/memory"],
"env": {
"DB_PATH": "./memory.db",
"EMBED_MODEL": "qwen3-embedding:0.6b",
"OLLAMA_BASE_URL": "http://localhost:11434"
}
}
}
}OLLAMA_BASE_URL may point at a remote host. The embedder strips a trailing slash, so either form works — but prefer no trailing slash.
import { openDb, runMigrations, OllamaEmbedder, embedderFromEnv, store, recall, deleteByIds } from '@justfortytwo/memory';
const h = openDb('memory.db');
await runMigrations(h.k);
const embedder = new OllamaEmbedder();
// or choose from env exactly like the server does (EMBED_PROVIDER / EMBED_MODEL / ...):
// const embedder = embedderFromEnv();
await store(h, embedder, { content: 'the deploy script lives in scripts/deploy.sh', source: 'owner', observed: 'stated' });
const hits = await recall(h, embedder, 'how do I deploy?', 5);
deleteByIds(h, [hits[0].id]); // hard-delete (row + vector + FTS); returns the count removeddeleteByIds(h, ids) hard-deletes memories — the row, its vector, and its FTS entry — so nothing resurfaces in recall/lexical/query. It is intentionally a library API and NOT an MCP tool: deletion is an owner-privileged operation, and exposing it to the assistant's turn loop would let prompt-injected content trick it into erasing memories. Selecting which ids to remove (by query, tag, date range, …) is the caller's job — see @justfortytwo/installer's forget.
.claude-plugin/plugin.json declares the plugin; .mcp.json registers the fortytwo-memory server. By default it launches via npx:
{
"mcpServers": {
"fortytwo-memory": {
"command": "npx",
"args": ["-y", "@justfortytwo/memory"],
"env": {
"OLLAMA_BASE_URL": "http://localhost:11434",
"EMBED_MODEL": "qwen3-embedding:0.6b",
"DB_PATH": "${CLAUDE_PLUGIN_DATA}/memory.db"
}
}
}
}${CLAUDE_PLUGIN_DATA} survives plugin updates, so the DB persists across upgrades. When developing from source, build first (npm run build) and swap the command to node with args ["${CLAUDE_PLUGIN_ROOT}/dist/index.js"]. Claude Code does not build MCP servers — they run via npm/npx.
enrich(h, embedder, candidates) folds a batch of candidate memories into the store: it drops low-salience candidates, dedupes near-duplicates by meaning, and writes the survivors with provenance — honoring an explicit supersedes to replace a stale belief (history is kept, never a silent overwrite). enrichFromTurn(h, embedder, turn, extractor) runs an injected SalienceExtractor and feeds its candidates to enrich.
The salience extractor itself is model-driven and lives in the sibling `@justfortytwo/salience` engine (a SalienceExtractor with an injected LlmClient) — memory owns only the dedupe + write, never the model call.
memory depends on two sibling packages one-directionally (declared as optional peers, no cycle):
GateApprovalStore(src/gate-approval-store.ts), a durable SQLite-backed implementation of gate's ApprovalStore + AuditLogger interfaces. Pass it to gate's decide(..., { store, audit }) to back the safety gate's one-shot approvals with memory's db instead of the gate's standalone JSONL store.
into enrichFromTurn (see above).
npm run build # tsc
npm test # vitest run
npm run test:watch # vitestSet RUN_OLLAMA_TESTS=1 to run the opt-in live-Ollama embedder test.
MIT © 2026 Enrico Deleo
Created and maintained by Enrico Deleo.
memoryHead(h) returns { database, position }, using a persistent database identity and SQLite's AUTOINCREMENT high watermark (not timestamps or MAX(id)). memoryDelta(h, cursor, limit = 50) returns ascending insertion pages and the last reviewed position; it includes superseded rows as evidence and never skips unreturned rows. Deleted ID gaps remain safe. Invalid/foreign/backwards cursors fail closed. These are library APIs only; the MCP tool contract is unchanged.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.