enscribe
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@enscribewhat do I remember about the Postgres migration?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
enscribe
A standalone, local-first personal-memory daemon that every AI you authorize can read and write over MCP. One SQLite file. No cloud. No API keys. An optional HTTP mode shares the same memory across your agents and devices.
Formerly MacBrain. Every ENSCRIBE_* variable below is also read as
MACBRAIN_* when the ENSCRIBE_* form is unset, an existing ~/.macbrain/
data directory is used in preference to ~/.enscribe/, and the macbrain
command name still works.
What it is
Local-first. All memory lives in one SQLite file on your own machine.
AI-agnostic. Any MCP-capable client (Claude Code, for example) can search your memory and add to it.
Hybrid search. Full-text (SQLite FTS5) and vector search, fused with Reciprocal Rank Fusion. Embeddings are computed locally with an in-process ONNX model; nothing about your data leaves the machine.
Knowledge pages. Point it at a git-tracked markdown vault (wikilinks plus frontmatter) and it indexes your notes alongside agent memories: alias-aware link resolution, backlinks, unified search, and
put_page/edit_pagewrite paths that commit the markdown file itself. The files stay canonical; the database is a disposable index over them.No model on the read path. Queries never trigger an LLM call, a network request or a write. Judgment work (synthesis, contradiction checks) belongs in a separate client that talks to enscribe over MCP.
Related MCP server: mnema
Prerequisites
Node.js 22 or 23. Node 24 crashes on teardown inside
better-sqlite3, and Node 25+ has no prebuilt binary for it. The app fails fast on anything else.git
Get started
git clone https://github.com/Zalitygit/enscribe.git
cd enscribe
npm ci
npm test # first run downloads the embedding model (~100MB), then runs offline
npm run build # compiles TypeScript to dist/Try it (CLI)
echo '{"type":"note","subject":"Hello","content":"my first memory"}' > /tmp/r.json
node dist/cli/index.js ingest /tmp/r.json
node dist/cli/index.js query "first memory"
node dist/cli/index.js dumpThe database is created at ~/.enscribe/enscribe.db by default.
Use it with an AI (MCP over stdio)
claude mcp add -s user enscribe -e ENSCRIBE_DB="$HOME/.enscribe/enscribe.db" \
-- node "$(pwd)/dist/cli/index.js" mcpThe client gets 24 tools:
Records (8):
search_memory,add_memory,get_record,update_memory,link_records,list_recent,forget_record,summarize_context.Pages (11): unified
search,get_page,list_pages,get_links,get_backlinks,resolve_slug,doctor,orphans,recent_pages,put_page,edit_page.Findings (5):
findings,report_findings,add_finding,set_finding_state,set_finding_field.
The HTTP server adds a 25th, current_context, for tokens holding
context.read: it returns the markdown file at ENSCRIBE_CONTEXT_FILE
(default ~/.enscribe/context/current-context.md), written by whatever external
job you point at it. src/mcp/scopes.ts is the authoritative list.
search is the one to start with. It fuses pages and records in one pass and
answers {hits, confident, reason?}:
confidentisfalsewhen nothing in the top three was reached by both the keyword and the vector leg, or when every such hit is past the distance floor. A search that found nothing relevant says so instead of ranking noise.Page hits carry
page_kind(authored|raw|generated),bm25anddistance, so a caller can judge a hit rather than trust a fused rank.intentis declared by the caller, never inferred. Sendentitywhen the question is about one named thing; it adds pages one hop away in the wikilink graph. Other values are accepted but change nothing. Omitting it is safe.
The MCP doctor tool reports page-index health; the CLI doctor command
reports record-index health.
Shared server (HTTP, multi-client)
# tokens file: bearer token -> { name, scopes }; "*" grants every scope
cat > ~/.enscribe/tokens.json <<'JSON'
{ "REPLACE-WITH-A-SECRET": { "name": "my-agents", "scopes": ["*"] },
"REPLACE-WITH-ANOTHER": { "name": "read-only", "scopes": ["memory.read", "memory.search"] } }
JSON
chmod 600 ~/.enscribe/tokens.json
ENSCRIBE_PORT=3134 node dist/cli/index.js serveClients send Authorization: Bearer <token> to http(s)://host:PORT/mcp
(Streamable HTTP). For connector UIs with no header field, ?token=<token> also
works; the header wins, and query tokens show up in proxy access logs. Put it
behind a TLS reverse proxy for remote use.
Scopes: memory.read, memory.search, memory.append, memory.modify,
memory.delete, memory.private (declared, gates nothing), pages.read,
pages.write, findings.read, findings.report, findings.write,
context.read. tools/list is filtered by the token's scopes, so a client only
sees what it may call. Sessions bind to the exact token; body size, idle lifetime
and session count are bounded. Local stdio mode is unscoped.
serve also runs a nightly in-process pass at 04:00 local time: finding
detection, then the generators, then a backup. A deployed server needs no cron.
Knowledge vault
# ~/.enscribe/vault.json: { "root": "~/notes", "include": ["wiki"], "deny": ["private"] }
node dist/cli/index.js vault sync # ingest + prune + rebuild aliases + resolve links
node dist/cli/index.js vault watch # sync, then reindex on file change
node dist/cli/index.js generate # run the generators
node dist/cli/index.js findings # run the detectors, reconcile the ledger, list what is open
node dist/cli/index.js findings list --kind needs-human
node dist/cli/index.js findings show <key>
node dist/cli/index.js findings resolve <key> 'why'
node dist/cli/index.js findings wont-fix <key> 'why'
node dist/cli/index.js backup # SQLite online backup with retention
node dist/cli/index.js purge # hard-delete tombstoned pagesWhat vault sync refuses to do quietly:
Colliding slugs are quarantined. Two files claiming one slug are both left out, nothing is pruned for them, and the CLI exits 1.
No prune on an untrustworthy walk. If an include directory cannot be read, nothing is pruned and the CLI exits 1. Pruning is a soft delete; a tombstoned body survives until
purge.A malformed
vault.jsonstops the process rather than falling back to defaults, because the fallback would drop your deny-list.
Writes through put_page/edit_page refuse credential-shaped text (API key
prefixes, bearer headers, ?token= URLs, NAME_TOKEN=value assignments) and
report only the rule and line, never the matched text. The nightly pass flags any
such text already in the vault as a secret finding.
The finding ledger
One table of maintenance items with states open, acknowledged, wont_fix,
resolved. wont_fix is sticky: a declined finding that keeps being re-detected
stays quiet. Three sources write to it under disjoint kinds:
the built-in nightly detectors (
orphan,unresolved,unknown-link-type,secret);an external judgment client through
report_findings(red-link,contradiction);agents filing one item at a time through
add_finding(needs-humanfor something only the owner can do,todo).
Maintenance
Full-text, vector and relationship tables are derived indexes:
node dist/cli/index.js doctor # record-index health
node dist/cli/index.js reindex # rebuild every record indexThe page index is rebuilt by vault sync. Three tables are not rebuildable
and only backup protects them: findings (human verdicts), the snapshot tables
behind the change digest, and meta.
Configuration
ENSCRIBE_DB: database path (default~/.enscribe/enscribe.db).ENSCRIBE_EMBED_URL(+ENSCRIBE_EMBED_MODEL): use an Ollama-style HTTP embedding server instead of the built-in ONNX model.ENSCRIBE_VAULT_CONFIG: vault config (default~/.enscribe/vault.json).ENSCRIBE_GIT_ROOT: directory of bare git repos the repo-inventory and activity generators read (default/srv/git; missing is fine).ENSCRIBE_BACKUP_DIR: backup destination (default~/.enscribe/backups).ENSCRIBE_PORT/ENSCRIBE_HOST/ENSCRIBE_TOKENS: shared server.ENSCRIBE_DISTANCE_FLOOR(default0.85): calibrated for the unit-length ONNX embedder only. The HTTP embedder does not normalize, so set this per instance if you use one.ENSCRIBE_GRAPH_WEIGHT(default0.25),ENSCRIBE_KIND_WEIGHT: ranking knobs, measured rather than chosen. Leave them alone unless you are benchmarking.
Known issues
macOS: the CLI exits 134 after finishing its work when the ONNX embedder was loaded (onnxruntime teardown). The work is committed first. Linux and
serveare unaffected; it matters underset -e.test/pages/vault-watch.test.tsis a timing flake under full-suite load. Re-run that file alone before believing a failure there.
Project layout
Path | What it holds |
| record types, the SQLite store, supersession, |
| embedders: in-process ONNX (default), HTTP, fake-for-tests |
| hybrid full-text + vector search (RRF), records and pages unified |
| the vault: parse, slugs/aliases, links, sync, watch, write paths, secret guard, generators |
| the finding ledger: store, detectors, reconciliation |
| stdio and HTTP MCP servers, auth, scopes, nightly scheduler |
| the |
| schema, applied in order on open |
| record format, page format, MCP contract, and the vendored Open Knowledge Format spec |
| vitest suite |
Contributing
Write a failing test first, then make it pass. Keep npm test and
npm run typecheck green. Design rules worth not undoing: the markdown files are
canonical and every index rebuilds from them; forget erases a record's whole
version lineage; relationships are canonical in the record document; one SQLite
file has one writer; the read path does no analysis.
This repository is a periodic export of a private working repo, so history here moves in release-sized commits. Issues and pull requests are welcome; accepted changes are applied upstream and arrive in the next export.
The secret-guard patterns credit Pratiyush/llm-wiki (MIT).
License
MIT. See LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Private-by-default, local-first memory/context/task orchestrator for MCP apps and agents.
One memory, every AI. A shared, user-owned markdown memory your AI clients read and write over MCP.
Self-hosted AI workspace: semantic RAG document search, long-term memory, and AI chat via MCP.
- LogRackOAuthai.lograck
Shared memory for your AI tools: tasks, notes and context every tool can read and write over MCP.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceA local-first MCP memory server providing persistent, searchable memory for AI agents, powered by SQLite.1 npm1Apache 2.0
- AlicenseNot gradedqualityDmaintenanceProvides a file-first personal memory layer for AI agents, enabling them to store and retrieve memories as markdown files with an SQLite index. The MCP server offers read-only search by default, with optional write tools for manual memory addition and conflict resolution.1 npmMIT
- AlicenseBqualityCmaintenanceGives MCP clients a local, single-tenant SQLite-backed store for saving, searching, bootstrapping and recalling memory records, plus saving and loading session handoffs, using text search by default with optional embeddings. It runs over stdio or an authenticated loopback HTTP gateway with explicit-confirmation pruning, scoped policies, and admin-driven backup, restore-copy and restart-drill verification.16MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to store and recall durable facts, decisions, and lessons over MCP using hybrid semantic and keyword search, typed memories, graph relations, sessions, and time-travel queries. Runs locally with no server process or API key, persisting everything to files that both agents and a CLI can share.13 npmMIT