{"slug": "onememory-self-hosted-shared-memory-for-ai-agents-over-mcp", "title": "OneMemory – self-hosted shared memory for AI agents over MCP", "summary": "OneMemory, a self-hosted shared memory store for AI agents, launched as an npm package (onememory-mcp) that lets Claude Code, Claude Desktop, claude.ai, Claude mobile, Cursor, Copilot, Cline, Continue and n8n share one note store over MCP or plain REST. The package requires Node.js 24 or newer because it relies on Node's built-in node:sqlite, which does not exist in Node 22 or older; users who cannot upgrade are directed to Docker. Running `onememory start` serves the dashboard and REST API on port 8790 and MCP-over-HTTP on port 8791 bound to 127.0.0.1, with all data, tokens and spaces config stored in ~/.onememory (owner-only 0700 on macOS/Linux) so npm updates never touch it.", "body_md": "**One small, self-hosted memory store that every AI agent you use can share — over MCP or plain REST, from your desktop, the web, or your phone.**\n\nWrite a note from Claude Code on your desktop; read it back from Claude on your phone in a fresh chat. Same store for Claude Code, Claude Desktop, claude.ai, Claude mobile, Cursor, Copilot, Cline, Continue, n8n — anything that speaks MCP or HTTP.\n\n**Requires Node.js 24 or newer.** OneMemory uses Node's built-in\n`node:sqlite`, which does not exist in Node 22 or older. Check with\n`node -v`. Can't upgrade? Use [Docker](#docker) instead.\n\n```\nnpm i -g onememory-mcp                           # 1. install\nclaude mcp add onememory -- onememory mcp        # 2. wire it into Claude Code (stdio)\nonememory doctor                                 # 3. verify Node, database, token\n```\n\nThen, in Claude, say **\"remember that my staging server is on port 8443\"**,\nand in a fresh chat ask \"what port is my staging server on?\". Step 4, only\nif you want the dashboard, REST API and MCP-over-HTTP (the `curl` examples\nbelow need it): `onememory start`.\n\nNo global install: `claude mcp add onememory -- npx -y onememory-mcp mcp`.\n`onememory init` prints the config for Claude Desktop / Cursor / Copilot;\n[INSTALL.md](https://github.com/calasdan-sketch/onememory/blob/main/INSTALL.md) has every client.\n\n`onememory start` serves the dashboard and REST API on `:8790` and\nMCP-over-HTTP on `:8791` (bound to `127.0.0.1`). To reach it from your phone\nor claude.ai, put an authenticated tunnel (Cloudflare Tunnel + Access,\nTailscale, or your own TLS proxy) in front of `:8791` — see\n[INFRA.md](https://github.com/calasdan-sketch/onememory/blob/main/INFRA.md).\n\nPlain REST works too once `onememory start` is running (the token is in\n`~/.onememory/.token`):\n\n```\nTOKEN=$(cat ~/.onememory/.token)\ncurl -X POST localhost:8790/notes -H \"Authorization: Bearer $TOKEN\" -H \"Content-Type: application/json\" -d '{\"title\":\"Deploy day\",\"body\":\"Shipped v1.1\"}'\ncurl -H \"Authorization: Bearer $TOKEN\" \"localhost:8790/search?q=deploy\"\n```\n\nEverything (database, token, scoped tokens, spaces config) lives in\n`~/.onememory` (Windows: `%USERPROFILE%\\.onememory`), outside the installed\npackage, so `npm update` and npx cache cleanups never touch it. Set\n`ONEMEMORY_HOME` to put it elsewhere, or `ONEMEMORY_DB_PATH` for just the\ndatabase. On macOS/Linux the folder is owner-only (`0700`, files `0600`).\nA from-source git checkout that already has `./data/state.db` keeps using\nit. `onememory doctor` shows which directory is in use.\n\n**Upgrading from an early 1.0.x install** (data kept inside the package folder): copy it yourself before removing the old package, e.g. `mkdir -p ~/.onememory && cp -Rn \"$(npm root -g)/onememory-mcp/data/.\" ~/.onememory/` (Windows: `%APPDATA%\\npm\\node_modules\\onememory-mcp\\data\\*` to `%USERPROFILE%\\.onememory\\`), then run `onememory rebuild-edges` once.\n\n```\ncp .env.example .env     # set ONEMEMORY_TOKEN (openssl rand -hex 32)\ndocker compose up -d     # dashboard at http://localhost:8790\nonememory backup --out ~/om-backup     # consistent hot copy; safe while the server runs\n```\n\n(The attestation signing key `.attest-key` is not copied; see [docs/trust.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/trust.md#setting-it-up-between-two-servers).)\n\n`onememory backup` writes `state.db` (via SQLite `VACUUM INTO`) plus, if\nat-rest encryption is on, a copy of the `.key` file. **Keep the key apart from\nthe backup**: a backup without its key is unreadable, and one stored next to it\nis not encrypted. See [INFRA.md](https://github.com/calasdan-sketch/onememory/blob/main/INFRA.md#7-backups). The JSON route (admin\ntoken) also works:\n\n```\nTOKEN=$(cat ~/.onememory/.token)\ncurl -H \"Authorization: Bearer $TOKEN\" localhost:8790/export > backup.json   # or: onememory export --format json --out backup.json\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" -H \"Content-Type: application/json\" \\\n  --data-binary @backup.json localhost:8790/import                           # admin scope only\n```\n\n- **Restore into an empty store.**`POST /import` does not de-duplicate:\nimporting a backup into a store that already holds those notes creates\nduplicates.\n- **Backups are snapshots of current notes, not per-edit history.** OneMemory\nkeeps only the latest version of each note (the`rev` counter tracks edits;\nit is not a snapshot list), so a store with months of edits exports as one\nentry per note with its latest body — export time and size track your note\ncount, not how often you rewrote them. The change feed's per-edit log is\nmetadata only (titles, no bodies), pruned after 90 days, and is not part of\na JSON backup.\n- **Exports stream.**`GET /export` and`onememory export --format json` page\nthrough the store and write as they go, so peak memory is one page (500\nnotes) regardless of store size — a multi-month store dumps in one pass.\n- **Restores keep the history that exists.**`created_at` ,`updated_at` ,`source` and the`rev` counter all survive the round trip (a note at rev 26\nstays at rev 26, so`if_rev` guards and the change feed stay accurate).\nImports over 200 notes land in seconds and defer the O(n²) similarity-graph\nrebuild — the response says so and search works immediately; run`onememory rebuild-edges` when you want neighbor ranking back. Restored\nnotes are re-embedded automatically when semantic search is enabled.\n- Markdown (one file per note, git-friendly) is a separate path:\n`onememory export --format md --dir ~/memory` — see[docs/file-sync.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/file-sync.md) .\n\n- **Small footprint.** One Node process, one SQLite file,**2 direct runtime\ndependencies** (`@modelcontextprotocol/sdk` ,`zod` ; ~94 packages installed\nin total, mostly the MCP SDK's own), no native modules, no vector database,\nno Python. Back it up by copying one file.\n- **Change feed.**`memory_since` /`GET /changes` : \"what changed while I was\naway\" — created, updated**and deleted** notes grouped by source, with a\nper-device \"since I last looked\" marker. Driven by a local change log, so\nrestores, file-sync imports and deletes are never missed. →[docs/change-feed.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/change-feed.md)\n- **Live wait.**`GET /changes?wait=25` /`memory_since { wait_seconds: 25 }` parks\nuntil something changes in your scope, so a session sees an idea added from\nyour phone within moments instead of on its next poll.**Use 25, not the 55\nmaximum:** many proxies and tunnels cut an idle response at 30s (a 55s wait\nbehind one died at 30.0s). Bounded (16 per token), and a dropped client or a\nserver shutdown frees its slot;`ack_mode: \"manual\"` makes delivery\nat-least-once. →[docs/change-feed.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/change-feed.md#live-wait)\n- **Task board, handoffs, catch-up.**`task_put` /`task_get` /`task_list` /`task_claim` ,`session_handoff` , and one`catch_up` call (changes since you last looked +\nopen tasks + latest handoff). Built on plain notes, so it is encrypted,\nnamespaced and searchable like the rest;`onememory recall` includes open\ntasks and the latest handoff at session start. →[docs/tasks.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/tasks.md)\n- **Memory linter.** Every write is scanned for secrets (API keys, JWTs,\nprivate keys, high-entropy tokens) and prompt-injection instructions\n(\"ignore previous instructions\", chat-template markers, tool-call\ncoercion). Secrets are blocked; injection is quarantined for admin review\nand invisible to search and recall until approved. It is a pattern\nlinter, with unit tests but no published false-positive rate: expect misses\nand the occasional false flag. →[docs/memory-linter.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/memory-linter.md)\n- **Plain-file sync.**`onememory export --format md --dir` writes one\nMarkdown file per note with YAML front-matter, deterministic and\ngit-friendly;`onememory import --dir` merges edits back three-way — newer\nwins, conflicts reported and the losing version kept, never silently lost.\nWorks across machines through GitHub or Syncthing. →[docs/file-sync.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/file-sync.md)\n- **Scoped tokens.**`read` /`write` /`admin` bearer tokens, enforced\nidentically on REST and MCP — give your phone a read token and a leaked\ntoken can't wipe the store.\n- **MCP *and* a plain REST API** on the same store, plus a small dashboard.\n\nAlso included: optional semantic / hybrid search with your own embeddings\nendpoint (Ollama or any OpenAI-compatible API), natural-language time\nfilters (\"yesterday evening\"), staleness warnings, near-duplicate\nconsolidation, a similarity graph, access-based ranking, server-side\nnamespace isolation, and a Claude Code SessionStart recall hook.\n→ [docs/search.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/search.md)\n\n```\nflowchart LR\n    subgraph Agents[\"Your agents\"]\n        CC[\"Claude Code\"]\n        CD[\"Claude Desktop\"]\n        PH[\"Phone / Web\"]\n        CU[\"Cursor / Copilot\"]\n    end\n    subgraph OM[\"OneMemory (one Node process)\"]\n        MCP[\"MCP server\\n(stdio + HTTP)\"]\n        REST[\"REST API + Dashboard\"]\n        LINT[\"Memory linter\\n(secrets + injection scan)\"]\n        DB[(\"SQLite\\n(one file)\")]\n    end\n    CC <--> MCP\n    CD <--> MCP\n    CU <--> MCP\n    PH <-->|tunnel| REST\n    MCP --> LINT --> DB\n    REST --> LINT\n```\n\nWrite once from your desktop, read back from your phone — every agent shares the same store.\n\nOther good projects solve overlapping problems, and we have not verified a feature-by-feature comparison against their current releases, so we do not publish one. Read their own docs and pick on your needs:\n\n- [mcp-memory-service](https://github.com/doobidoo/mcp-memory-service) ,[Basic Memory](https://github.com/basicmachines-co/basic-memory) ,[OpenMemory (mem0)](https://docs.mem0.ai/openmemory) and the[official MCP memory server](https://github.com/modelcontextprotocol/servers/tree/main/src/memory) are all worth a look.\n\nWhat OneMemory itself provides (all of it checkable in this repo): MIT\nlicense, Node 24+ with 2 direct runtime dependencies, one SQLite file,\nMCP over stdio and HTTP plus a REST API, optional semantic search with your\nown embeddings endpoint, a change feed that includes deletes, a write-time\nsecret and injection linter, Markdown export and three-way import, scoped\ntokens with optional per-token namespaces, and optional AES-256-GCM\nencryption of note bodies. See [SECURITY.md](https://github.com/calasdan-sketch/onememory/blob/main/SECURITY.md) for what it does\n**not** do.\n\n| Tool | Scope |  | \n|---|---|---|\n| `state_write` /`state_read` /`state_delete` | write / read / admin | CRUD with compare-and-swap ( `if_rev` ) and provenance (`source` ) | \n| `state_search` /`state_relevant` /`state_list` | read | keyword, semantic or hybrid search; vague-message recall; listing | \n| `memory_since` | read | change feed ( `wait_seconds` = live wait) | \n| `task_put` /`task_list` /`task_get` | write / read / read | shared task board across devices and models; `client_key` makes a create retry-safe | \n| `task_claim` | write | take / heartbeat / release a task with a lease (compare-and-swap) | \n| `task_approve` | admin | approve a command task so the PC's runner may run it ( [RUNNER.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/RUNNER.md) ) | \n| `runner_status` | read | is the PC's runner listening, and how many approved commands are waiting for it ( `onememory runner-status` ,`GET /runner/status` ;[RUNNER.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/RUNNER.md) ) | \n| `session_handoff` /`catch_up` | write / read | leave a handoff; one-call catch-up at session start | \n| `memory_ack` | read | confirm a manual-ack change-feed page | \n| `memory_stats` | read | totals, most-read, never-read, stale | \n| `memory_quarantine` | admin | review linter-held writes | \n| `trust_policy` | admin | list / change the per-namespace distrust list and the trusted attester keys ( [docs/trust.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/trust.md) ) | \n| `memory_consolidation_candidates` /`memory_consolidate` /`memory_rebuild_edges` | admin | dedup and similarity-graph maintenance | \n\nREST mirrors them (`/notes`, `/search`, `/relevant`, `/changes`, `/tasks`, `/handoffs`, `/catch-up`, `/stats`,\n`/export`, `/import`, …). Full reference: [docs/architecture.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/architecture.md).\n\n```\nonememory start | mcp | doctor | init\nonememory changes --since last --consumer laptop\nonememory runner-status                           # is each PC's runner listening? commands waiting?\nonememory trust [list | distrust <w> | undistrust <w> | identity | attester-add <key> --server-id <id> | attester-remove <id> | attester-revoke <id>]\nonememory trust [export-key --out <file> | rotate]         # EXPERIMENTAL: signed attestations (docs/trust.md)\nonememory watch --consumer pc [--exec program args...]     # react to changes as they land\nonememory backup [--out dir] [--include-approval-key] [--include-attest-key]   # hot copy of the database (+ keys)\nonememory export --format md --dir ~/memory     onememory import --dir ~/memory\nonememory export --format json [--attest] --out a.json     onememory import --file a.json [--require-attestation]\nonememory quarantine [list | approve <id> | reject <id>]\nonememory consolidate | rebuild-edges | reindex | relevant \"…\" | recall\n```\n\n`onememory help` explains each. Upgrading an existing 1.3.x install: [docs/upgrading.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/upgrading.md). The CLI runs with full local trust (no\ntoken).\n\nEvery note carries a **server-stamped** `written_by` (who last changed its title or body; a tag edit or a task status change does not count) and `created_by`\n(the first). It is the label of the authenticated token that made the call, or a fixed\nmarker for writers with no token: `stdio` (local MCP), `cli` (an `onememory` command),\n`server` (its own jobs), `runner` (the command runner). A client cannot set it: `written_by` in a request body is\nignored, and an import stamps the *importing* admin. The old `source` field is still\nthere, but it is only what the writer *claims*, so anyone can put anything in it.\n\nA note written by Claude Code and one written by Cursor are therefore no longer\nidentical on read-back. MCP results add a \"who wrote this\" block under the untrusted-data\nbanner, the session-start recall names the writer, and `state_list`, `state_search` and\n`memory_since` take `written_by` / `not_written_by` (REST: `?written_by=`) so an agent can\nignore its own writes or distrust a client. Rows from before 1.3.3 read `\"\"` (unknown).\nDetails and limits: [docs/provenance.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/provenance.md).\n\n**Trust (1.4.0, hardened in 1.4.1).** `writer_id` (`<server_id>/<label>`) says which server a label came from. An admin can put a\nwriter on a per-namespace **distrust list** (`onememory trust distrust <writer> --namespace work`, the\n`trust_policy` MCP tool, `/trust` REST): its notes, tasks, handoffs and feed entries are then left out of every\nread path unless an **admin** passes `include_distrusted`, they stay hidden after anyone else edits them, and a\ndistrusted command task is never run. A JSON export made with `--attest` (`?attest=1`; the default export is the\nplain `{\"notes\": [...]}` as in 1.3.x) carries a per-note Ed25519 **attestation**; an import that trusts the exporting server's key keeps the\noriginal author as `attested_writer`, otherwise it flattens provenance as before and marks the note `imported_unverified`. (Attestations are **EXPERIMENTAL** in 1.4.x.) What that proves and what it\ndoes not: [docs/trust.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/trust.md).\n\nLoopback by default and fail-closed: it refuses to bind a public interface\nwithout an explicit `ONEMEMORY_TOKEN`. Constant-time bearer auth on every\ndata route, scoped tokens, DNS-rebinding Origin checks, per-client rate\nlimits, size caps, an append-only audit log without note content, the\nwrite-time linter above, and an untrusted-data banner on every MCP read (REST responses do not\ncarry it) so agents treat note content as data, not instructions. Optional AES-256-GCM\nencryption at rest for note bodies only (`onememory encrypt enable`; titles,\ntags, timestamps and embeddings stay readable), per-note\nTTL with reversible soft-archive, and `?explain=1` so a caller can see why\na memory was retrieved. **Per-token namespace permissions** pin a token to\none namespace so a single shared bridge stays isolated per agent — a pinned\nadmin token is still confined. Your notes stay in one\nfile on your machine; a tunnel only carries requests. Details and known\nlimits: [SECURITY.md](https://github.com/calasdan-sketch/onememory/blob/main/SECURITY.md),\n[docs/token-namespaces.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/token-namespaces.md), and\n[docs/encryption-retention.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/encryption-retention.md).\n\n**About the retrieval eval.** `evals/cross-device-tasks.json` is 10 tasks over\n10 notes and lexically easy; its perfect score is a regression gate in CI,\nnot evidence of retrieval quality. Keyword-only `state_search` scores 0.70\nrecall@1 on it. See [docs/encryption-retention.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/encryption-retention.md#the-evaluation-set).\n\n| [INSTALL.md](https://github.com/calasdan-sketch/onememory/blob/main/INSTALL.md) | every MCP client | \n| [INFRA.md](https://github.com/calasdan-sketch/onememory/blob/main/INFRA.md) | tunnels, phone/web access, n8n | \n| [docs/configuration.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/configuration.md) | all environment variables, server-side namespace resolution | \n| [docs/search.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/search.md) | semantic, time-aware and relevant search; staleness; consolidation | \n| [docs/change-feed.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/change-feed.md) | `memory_since` /`GET /changes` | \n| [docs/tasks.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/tasks.md) | task board, claims, command tasks, handoffs, one-call catch-up | \n| [docs/memory-linter.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/memory-linter.md) | `ONEMEMORY_LINT` , detection rules, quarantine | \n| [docs/file-sync.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/file-sync.md) | Markdown export/import, GitHub and Syncthing workflows | \n| [docs/sessionstart-hook.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/sessionstart-hook.md) | automatic recall at Claude Code session start | \n| [docs/encryption-retention.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/encryption-retention.md) | at-rest encryption, retention (TTL + archive), retrieval visibility | \n| [docs/provenance.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/provenance.md) | who wrote a note: server-stamped `written_by` , filters, what is and is not trusted | \n| [docs/trust.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/trust.md) | distrust list, writer ids, signed attestations: what each does and does not prove, upgrade and rollback | \n| [docs/token-namespaces.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/token-namespaces.md) | per-token namespace permissions — one shared bridge, isolated agents | \n| [docs/architecture.md](https://github.com/calasdan-sketch/onememory/blob/main/docs/architecture.md) | modules, tools, REST API, schema | \n| [SKILL.md](https://github.com/calasdan-sketch/onememory/blob/main/SKILL.md) | when and how an agent should use the memory | \n| [CHANGELOG.md](https://github.com/calasdan-sketch/onememory/blob/main/CHANGELOG.md) | release notes | \n\nSeveral ideas were adapted from other open projects, with thanks:\n\n- **[llmem](https://github.com/netrixone/llmem)** — consolidation candidates,\nthe similarity-edge graph, access-count importance,`#status` staleness, and\nthe context/relevant split with keyword extraction.\n- **lontar** (rymanalu, described in*\"One memory across AI agents: what it\nactually takes\"* on dev.to) — the fail-silent SessionStart recall hook,\nserver-side namespace resolution from the working directory, and\none-directional`reads_from` grants.\n- **[lossless-memory](https://github.com/aru-labs/lossless-memory)** —\nnatural-language time phrases as a first-class query filter.\n\nMIT — see [LICENSE](https://github.com/calasdan-sketch/onememory/blob/main/LICENSE). Built by Dan Calas / Calas Automations Inc.", "url": "https://wpnews.pro/news/onememory-self-hosted-shared-memory-for-ai-agents-over-mcp", "canonical_source": "https://github.com/calasdan-sketch/onememory", "published_at": "2026-10-25 01:54:19+00:00", "updated_at": "2026-10-25 02:12:53.363312+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-tools", "developer-tools"], "entities": ["OneMemory", "onememory-mcp", "Node.js 24", "node:sqlite", "Claude Code", "Claude Desktop", "Cursor", "Copilot"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/onememory-self-hosted-shared-memory-for-ai-agents-over-mcp", "markdown": "https://wpnews.pro/news/onememory-self-hosted-shared-memory-for-ai-agents-over-mcp.md", "text": "https://wpnews.pro/news/onememory-self-hosted-shared-memory-for-ai-agents-over-mcp.txt", "jsonld": "https://wpnews.pro/news/onememory-self-hosted-shared-memory-for-ai-agents-over-mcp.jsonld"}}