A persistent, shared mind for AI agents, built on an Obsidian vault.
Every Claude session — on any machine — reads from and writes to the same knowledge base: a plain-markdown Obsidian vault replicated by Self-hosted LiveSync (CouchDB). This MCP server is how agents search it, extend it, and keep it organized.
flowchart LR
subgraph Agent side
C[Claude or any MCP client]
end
C <-->|MCP tools| S[tesseract-mcp server]
S <-->|read/write markdown| V[(Obsidian vault)]
S <-->|rebuildable caches| Q[(SQLite + embeddings in ~/.tesseract-mcp/)]
L[Librarian + organizer<br/>scheduled caretakers] -->|index, file, heal| V
L --> Q
V <-->|LiveSync| DB[(CouchDB)]
DB <-->|LiveSync| M2[Vault on other machines]
The vault's markdown is the single source of truth. Everything the server
computes — the search index, embeddings, the graph cache — lives under
~/.tesseract-mcp/ and is rebuildable from the vault on demand, so it never
has to travel through LiveSync itself.
BM25 keyword ranking and embedding cosine similarity, fused with Reciprocal Rank Fusion — rank-based fusion means the two score spaces never need to be normalized against each other. Vectors reuse Obsidian's Smart Connections embeddings when fresh, with a same-model local fallback (bge-micro-v2) so the similarity space is never mixed.
An LLM pass extracts people, organizations, domains, topics, projects and
sources from notes into real markdown entity notes under Claude/Graph/ —
visible in Obsidian's graph, synced like everything else, and mirrored into
SQLite for traversal. related_notes walks entity chains between notes;
context_bundle composes hybrid search and graph context in one call.
Agents write freely only under Claude/ (sessions, concepts, inbox, tasks,
decisions, graph). Everything else is the human's: readable always, writable
only with explicit confirmation — enforced in code, not by convention. The
human-readable rules live in the vault as a constitution.
New notes are filed into the existing folder taxonomy by embedding neighbor-vote (≥0.7 agreement moves the note; less queues a human proposal). Every move is journaled and reversible.
A single scheduled caretaker loop (python -m tesseract_mcp.librarian <vault>)
runs indexing, organizing, cache maintenance, throttled consolidation
proposals, and health checks in one pass — replacing separate indexer and
organizer cron jobs. Sweep reports land in Claude/Librarian.md; the
librarian_status MCP tool reads the last run.
python -m tesseract_mcp.provision <path-to-vault> installs a pinned plugin
set, seeds settings (embed model pinned to what the search stack reads), and
installs the agent conventions tree. --check reports version drift.
Claude Code skills turn the vault into a memory you can question:
/recall (researched answers, every claim cited as a [[wikilink]]),
/digest (the review ritual), /resume (project briefings), and
/connections (graph serendipity). /recall files every answer into
Claude/Answers/, where the Librarian indexes it like any note — so
answers become retrieval sources and the vault compounds from asking
questions, not just ingesting. Five further rituals are drafted and
awaiting their acceptance runs: /clip (compile one external source
into a cited Inbox note), /distill (promote repeated captures into
evergreen concepts), /decide (the append-only decision ledger),
/rounds (read-only caretaker health inspection), and /dossier
(deep multi-section briefings). Skills live in skills/ and
install with python -m tesseract_mcp.skill_sync (additive; --check
reports drift; existing skills are never modified without --force).
flowchart LR
Q[query] --> F[candidate filter<br/>tags / folder]
F --> B[BM25L keyword rank<br/>top 50]
F --> E[cosine similarity<br/>Smart Connections vectors,<br/>bge-micro-v2 fallback<br/>top 50]
B --> R[Reciprocal Rank Fusion<br/>k = 60]
E --> R
F -.->|only when BM25 is empty| SUB[substring rank]
SUB -.-> R
R --> H[ranked hits with excerpts]
Every query runs both rankers over the filtered candidate set, and RRF merges them by rank position — so BM25 scores and cosine similarities never need to be normalized against each other. The substring ranker is a fallback signal for queries BM25's tokenizer can't match (single characters, punctuation), never a third competitor. Full detail in ARCHITECTURE.md.
| success@5 | success@10 | recall@5 | recall@10 | MRR |
|---|---|---|---|---|
| 0.88 | 0.94 | 0.88 | 0.94 | 0.89 |
Sixteen golden queries — keyword, paraphrase, title, tag, entity, and
degenerate-input traps — run against the production hybrid_search path with
real bge-micro-v2 embeddings over the synthetic 20-note corpus committed in
evals/ (synthetic because this repo is public; a private
golden set lives in the vault itself). Reproduce with
python -m tesseract_mcp.evals.
| Tool | Purpose | |
|---|---|---|
| Orient | onboard |
Call first in a new session — constitution, routing, cheat-sheet, graph status |
| Retrieve | search_brain |
Hybrid search (BM25 + vector, RRF-fused), optional tag/folder filters |
context_bundle |
One call: search hits + their graph entities + related notes | |
recall_bundle |
Digest/resume raw material for the recall skills — one read-only call | |
read_note |
Read any note | |
query_notes |
Query notes by frontmatter metadata | |
get_backlinks |
Notes whose [[wikilinks]] point at a note |
|
list_recent |
Recently modified notes | |
list_tasks |
Checkbox tasks across the vault | |
| Write | log_session |
Session log into Claude/Sessions/ |
capture |
Quick thought into Claude/Inbox/ |
|
upsert_concept |
Evergreen notes in Claude/Concepts/ |
|
write_note |
General write — quarantined to Claude/ unless confirmed |
|
add_task |
Checkbox task in Claude/Tasks.md (Obsidian Tasks format) |
|
| Graph | index_brain |
Extract entities from new/changed notes |
find_entity |
Look up entities by name/alias | |
related_notes |
GraphRAG: notes connected via shared entities, with the chain | |
graph_stats |
Entity/edge/mention counts | |
consolidate_graph |
Merge duplicate entities (dry-run default) | |
| Organize | organize_vault |
Autonomous filing sweep (dry-run default) |
librarian_status |
Last caretaker sweep + health report (read-only) | |
undo_move |
Revert a journaled move |
git clone <repo> ; cd tesseract-mcp
python -m venv .venv
.venv\Scripts\pip install -e .
# Provision a fresh vault (plugins, settings, conventions)
python -m tesseract_mcp.provision <path-to-vault>
# Register the curated MCP server set (tesseract + web/paper ingest)
$env:TESSERACT_VAULT_PATH = "<path-to-vault>"
.venv\Scripts\python -m tesseract_mcp.mcp_syncThe manifest lives in mcp-servers.json; sync is additive-only (existing
entries are never modified or removed).
Then open the vault once in Obsidian (disable Restricted Mode, complete
LiveSync setup) and run the index_brain tool.
The Librarian is the single scheduled task for vault upkeep:
python -m tesseract_mcp.librarian <path-to-vault>It drains the index backlog, runs the organizer sweep, rebuilds the graph
cache when needed, proposes consolidation merges (dry-run only), and writes
a health report to Claude/Librarian.md. Use librarian_status to read
the last sweep without running it.
First run against a real vault must use --dry-run and be human-reviewed
before scheduling unattended applies — same operational rule as the
organizer. Dry-run prints the formatted report and JSON result without
writing state, moves, or throttle baselines.
Model selection for the claude extractor backend (ignored by codex):
| Env var | Default | Used for |
|---|---|---|
TESSERACT_EXTRACT_MODEL |
haiku |
Per-note entity extraction |
TESSERACT_CONSOLIDATE_MODEL |
sonnet |
Consolidation merge proposals |
- Architecture deep dive — retrieval pipeline, graph design, module map.
- Server deployment — CouchDB + Caddy for LiveSync.
