Skip to main content
Glama
AlexMost
by AlexMost
README.md
# Neuro Vault MCP

> πŸ§ πŸ’Ύ **Make your personal vault usable by agents.** Low-token retrieval, explicit provenance, and safe writes for your Obsidian notes β€” in Claude Code, Cursor, Windsurf, and any MCP client.

[https://github.com/user-attachments/assets/3f27fc94-36d3-44bb-aec5-35f6ce941bc2](https://github.com/user-attachments/assets/3f27fc94-36d3-44bb-aec5-35f6ce941bc2)

[![npm version](https://img.shields.io/npm/v/neuro-vault-mcp)](https://www.npmjs.com/package/neuro-vault-mcp)
[![Node.js](https://img.shields.io/node/v/neuro-vault-mcp)](https://nodejs.org)
[![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](LICENSE)
[![Changelog](https://img.shields.io/badge/Changelog-CHANGELOG.md-orange.svg)](./CHANGELOG.md)

Your second brain stops being a folder you open between contexts and becomes a first-class participant in every project. Agents can recall the right notes, inspect the evidence, and write back through vault-aware operations β€” without grepping the whole folder or flooding the context window.

> _"What did I write about that idea last month?"_ β€” and now your assistant can actually answer.

---

## ✨ Why Neuro Vault?

- 🧠 **Hybrid search that already knows your vault** β€” a semantic leg runs on an embedding index the server builds and owns from your notes (no plugin, no API keys β€” the first run indexes in the background), and a lexical leg catches exact names, codes, and terms embeddings miss. One call, both answers; a note hit by both is the strongest relevance signal.
- 🎯 **Quick or deep, your call** β€” `effort: "quick"` for fast direct lookups, `effort: "deep"` for exploration with related-note expansion; `mode: "lexical"` when you want exact text matching only (works even without embeddings).
- 🧾 **Context with provenance, not mystery memory** β€” results come back with paths, matched queries, block-level snippets, and backlink counts so the assistant can show where an answer came from.
- 🧭 **A real navigation toolkit for your agent** β€” instead of grepping files and opening notes one by one, your assistant walks the vault like a database: filter by tags and properties, batch-read metadata, traverse the wikilink graph, discover the structure, jump to semantic neighbours.
- πŸ”Ž **Ask structured questions in plain language** β€” _"active projects tagged #ai"_, _"todo tasks with a deadline this week"_, _"meeting notes from `Work/` newest first"_. Your assistant turns them into a **MongoDB-style filter** over frontmatter, tags, and backlink counts β€” one call, ranked answer, no chains of reads.
- ✍️ **Full write surface for your notes** β€” create, in-place replace, or rewrite the whole body; manage frontmatter, tags, and daily notes. Every write goes directly to disk; if you have Obsidian open, its own file watcher picks up the change on its usual cadence.
- ⚑ **Zero infrastructure** β€” local stdio MCP server, no database, no API keys. One shared background process per machine holds the embedding model and each vault's index for every session; it starts on demand for a shared session with any registered vault, `"semantic": false` included (not under `--no-semantic` or `--no-shared`), keeps one text-search cache per vault, keeps the index fresh with a file watcher, and leaves when idle β€” nothing to run, nothing to babysit.
- πŸͺΆ **Obsidian optional** β€” a vault is just a directory of Markdown files, and the server reads and writes it headlessly: no plugin, no running app, nothing to install beyond this server. Obsidian-flavoured conventions (`[[wikilinks]]`, YAML frontmatter, inline `#tags`) are understood wherever they appear. The one tool that genuinely wants Obsidian is `read_daily` β€” it reads the Daily Notes plugin's `.obsidian/daily-notes.json` to learn your folder and date format, and returns `DAILY_NOTES_NOT_CONFIGURED` without it.
- πŸ”Œ **Drop-in for any MCP client** β€” Claude Code, Cursor, Windsurf β€” configuration is a single JSON block.

---

## 🧰 Two superpowers, one server

Most "vault MCP" servers give you one or the other. Neuro Vault gives you both, and lets your assistant pick the right one per question:

|                  | πŸ”­ **Hybrid recall**                                                                                                              | πŸ›  **Vault operations**                                                                                      |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| **What it does** | Finds notes by meaning _and_ by exact wording β€” semantic + lexical legs in one response. Surfaces neighbours and duplicates.       | Reads, writes, edits notes (in-place replace and full-body rewrite); manages frontmatter, tags, daily notes. |
| **Best for**     | _"What did I think about X?"_, fuzzy recall, exploratory research β€” and exact names, codes, terms the embeddings don't know.      | Structured queries, capturing decisions, updating tasks, batch reads.                                        |
| **Powered by**   | Embeddings the server builds and owns from your vault + direct text matching over titles, headings, and bodies. | Direct reads and writes against the vault directory on disk β€” no Obsidian install or running instance needed. |

The two work together: hybrid search finds the right region of the vault, vault operations let the assistant actually _do something_ with what it found.

---

## ✨ What it looks like in practice

**Before:** _"Could you check my notes about that LangGraph experiment?"_
β†’ Assistant lists `Notes/`, opens 12 files, greps for "LangGraph", gives up halfway, you paste the relevant note manually.

**After:** _"Could you check my notes about that LangGraph experiment?"_
β†’ One hybrid search β€” semantic matches plus exact "LangGraph" hits in the same response β€” follow-up question already grounded in your own writing.

A few more questions Neuro Vault makes one-shot:

> _"What are my active projects tagged #ai with a deadline this quarter?"_
> _"Show meeting notes from `Work/` from the last two weeks, newest first."_
> _"Find notes similar to this one I'm reading."_
> _"Append today's decision to the daily note."_
> _"What's on my agenda today β€” and what did I capture in other notes since this morning?"_
> _"What did past-me write about retrieval policy before I started building it?"_

One question, one answer. Your assistant stops being a file browser and starts being an actual second brain.

---

### πŸ”­ One search, both legs

`search_notes` is the flagship: **one call runs a semantic leg and a lexical leg and fuses them into a single ranked list.** No picking a strategy up front, no cross-referencing two result sets by hand.

Pass up to eight queries at once β€” synonyms, jargon, translations. The semantic leg works across languages, so a note written in one language is found by a query in another:

```json
{ "query": ["retrieval policy", "ΠΏΠΎΠ»Ρ–Ρ‚ΠΈΠΊΠ° ΠΏΠΎΡˆΡƒΠΊΡƒ", "RRF"], "effort": "deep" }
```

Each match comes back with **its own provenance and evidence** β€” not just a path and a score:

```json
{
  "path": "Notes/embeddings.md",
  "found_in": ["semantic", "lexical:title"],
  "similarity": 0.82,
  "blocks": [{ "heading": "Notes/embeddings.md#What is an embedding", "lines": [3, 20] }],
  "lexical": [{ "matched_in": "title", "snippet": "embeddings" }],
  "backlink_count": 4
}
```

`found_in` names every leg that surfaced the note β€” here both of them, which is the strongest relevance signal the server can give you. `blocks[]` points at the specific section and line range that matched, so the assistant can pull twenty lines instead of the whole note: that is where the low-token part of "low-token retrieval" actually comes from.

**Fusion is the point.** A note found by two legs is lifted in the merged order automatically (reciprocal rank fusion) and still appears exactly once β€” there is no caller-side merging to write, and no way to double-count a note that both legs liked.

**It degrades instead of failing.** While the embedding index is still building β€” or if you turned semantic off entirely β€” `search_notes` keeps answering from its lexical leg alone and reports `semantic_status` so the assistant knows which legs actually ran. Add `mode: "lexical"` to ask for exact text matching only, and `effort: "quick" | "deep"` to trade breadth for speed.

β†’ Full reference: [docs/guide/finding-notes.md#search_notes](./docs/guide/finding-notes.md#search_notes)

---

### πŸ” Pre-filter: scope search with structural filters

`search_notes` accepts an optional `filter` to narrow the candidate set **before** ranking β€” combining the precision of `query_notes` with the recall of hybrid search. The filter applies identically to every leg: only notes that pass it can appear in the fused `matches[]` list. Useful when domain-relevant notes are crowded out by larger narrative clusters.

```json
{ "query": "trading lessons", "filter": { "tags": ["trading"] } }
```

`filter` accepts `path_prefix` (string or array), `exclude_path_prefix` (string or array β€” drops matched subtrees), `tags` (ANY-of), and a `frontmatter` sift filter. Composition is include β†’ exclude β†’ tags β†’ frontmatter, then each leg ranks within the allowed set (`threshold` further cuts the semantic leg only). See the [Finding Notes guide](./docs/guide/finding-notes.md#pre-filter-filter-parameter) for full details.

---

### πŸ—ƒ Query your vault like a database

`query_notes` runs a **MongoDB-style filter** over every note's frontmatter, tags, path, and backlink count. It's syntax your assistant already knows from training data β€” nothing to teach it β€” and it collapses N+1 patterns like _"list tags β†’ read each note β†’ filter in my head"_ into a single call.

```json
{
  "filter": {
    "$and": [
      { "tags": "ai" },
      { "$or": [{ "frontmatter.status": "active" }, { "frontmatter.status": "wip" }] },
      { "frontmatter.deadline": { "$exists": true } }
    ]
  },
  "path_prefix": "Projects/",
  "sort": { "field": "frontmatter.deadline", "order": "asc" }
}
```

Operators: `$eq` `$ne` `$in` `$nin` `$gt` `$gte` `$lt` `$lte` `$exists` `$regex` `$and` `$or` `$nor` `$not`. They run through [sift](https://github.com/crcn/sift.js) behind a strict allow-list β€” `$where` and `$function` are rejected, so a filter can never become code execution on your machine. Notes are scanned in bounded batches with an early exit, so _"the first 100 active projects"_ stops reading the moment it has them.

β†’ Full filter reference: [docs/guide/finding-notes.md#query_notes](./docs/guide/finding-notes.md#query_notes)

---

## πŸ— How it works

```mermaid
flowchart LR
    You([You]) --> AI[AI assistant]
    AI <-->|MCP| NV[Neuro Vault]
    NV <--> Vault[("Vault<br/>markdown files")]
    NV <--> Corpus[(".neuro-vault/corpus<br/>embedding index")]
    Vault -.->|watched, reindexed| Corpus
```

You ask, the assistant calls Neuro Vault, Neuro Vault reads your vault β€” the semantic leg ranks against an embedding index the server builds and keeps in `<vault>/.neuro-vault/corpus/`, the lexical leg reads notes straight from disk (through a text-search cache the same process keeps per vault), and vault operations read and write the vault directory directly on disk too. No database, no Obsidian install required, and the only background process is the shared one that holds the embedding model and each vault's index and text-search cache β€” its watcher reconciles the index after ~10 seconds of quiet, so it never falls far behind what's on disk.

For module wiring and internal data flow, see [docs/architecture/module-structure.md](./docs/architecture/module-structure.md).

---

## ⚑ Quickstart

```bash
npm install -g neuro-vault-mcp
```

### Single vault

Add to your MCP client config (here: Claude Code's `~/.claude/settings.json`):

```json
{
  "mcpServers": {
    "neuro-vault": {
      "command": "neuro-vault-mcp",
      "args": ["--vault", "/absolute/path/to/your/vault"]
    }
  }
}
```

> **Vault directory names** must match `^[a-zA-Z0-9_-]{1,64}$` β€” ASCII letters, digits, `_`, or `-`; 1–64 chars. Spaces and Unicode are rejected. The MCP-side alias is the directory basename, so a vault you think of as "My Vault" β€” the name Obsidian would display, for instance β€” must live in a directory called `My_Vault` or similar.

### πŸ—‚ Multi-vault β€” two vaults, one server

Pass `--vault` once per vault:

```bash
neuro-vault-mcp \
  --vault /Users/me/Vaults/Sandbox \
  --vault /Users/me/Vaults/TeamWiki
```

Two vaults registered, with names `Sandbox` and `TeamWiki`. In your MCP config:

```json
{
  "mcpServers": {
    "neuro-vault": {
      "command": "neuro-vault-mcp",
      "args": ["--vault", "/Users/me/Vaults/Sandbox", "--vault", "/Users/me/Vaults/TeamWiki"]
    }
  }
}
```

Two vaults cannot share the same directory basename β€” the basename doubles as the alias and must be unique. If you have a basename collision, rename one of the directories.

With multiple vaults registered:

- **Every tool** accepts an optional `vault: "<name>"` parameter to target a specific vault.
- **`search_notes`, `query_notes`, `get_vault_overview`, `list_tags`, and `list_properties`** fan out across all registered vaults when `vault` is omitted. The response shape switches to `results_by_vault: [...]` (one entry per vault) plus `failed_vaults: [...]` for per-vault runtime errors (`{ vault, error: { code, message, details? } }`) β€” a single failed vault does not abort the whole call. The envelope also always includes `skipped_vaults: [...]`, reserved for a future fan-out tool that pre-filters vaults; today it's always empty, since nothing skips a vault.
- **All other tools** (writes, reads of specific paths, single-vault diagnostics) require an explicit `vault` in multi-vault mode. Omitting it returns `VAULT_REQUIRED`.
- **A vault whose semantic index is still building (or disabled) still participates** in `search_notes` fan-out β€” it contributes `matches[]` fused from its lexical leg alone and reports `semantic_status` so the caller knows why; no vault is skipped. Targeting such a vault explicitly with the embeddings-only tools (`get_similar_notes`, `find_duplicates`) returns `SEMANTIC_INDEX_BUILDING` while indexing, `SEMANTIC_DISABLED` if the vault opted out, or `SEMANTIC_INDEX_NOT_FOUND` if the index is genuinely unavailable.

Then ask your assistant:

> "What did I write about building AI agents?"

On first run the embedding model downloads automatically (`harrier-270m` β‰ˆ 344 MB, `granite-97m` β‰ˆ 98 MB, `harrier-0.6b` β‰ˆ 706 MB) into `~/.cache/neuro-vault/models`. Subsequent starts are fast. The default model is `harrier-270m`, a multilingual model that is markedly better than the lite one on Ukrainian notes; add `--model granite-97m` for the lite one (smaller, faster to index, a good fit for small hosts), or `--model harrier-0.6b` when quality matters more than index time ([which to pick, with benchmarks](./docs/guide/embedding-models.md); [bake-off report](./docs/research/2026-10-embedding-model-bakeoff.md)). 🎚️ Similarity thresholds are calibrated per model, so switching needs no tuning β€” and switching indexes the vault once for the new model, beside the old model's index (nothing is discarded; `index --prune` removes the others).

To run another model, put `--model` in the server's `args`:

```json
{
  "mcpServers": {
    "neuro-vault": {
      "command": "neuro-vault-mcp",
      "args": ["--vault", "/absolute/path/to/your/vault", "--model", "granite-97m"]
    }
  }
}
```

Indexing then runs in the background β€” the server boots immediately and `search_notes` answers from its lexical leg until the first pass finishes. To warm the index up front instead (before a demo, or for a large vault, or after upgrading β€” an upgrade from a release with a different default model indexes every vault once with the new one; an explicit `threshold` or `expansion_floor` you tuned on the old model, say `0.5`, is on a different scale under `harrier-270m` β€” its query-to-note similarities sit mostly below about 0.44 on the measured vault β€” so re-tune or drop it):

```bash
neuro-vault-mcp index --vault /absolute/path/to/your/vault --model granite-97m
```

Give `index` the same `--model` the server runs with (omit both for the default, `harrier-270m`) so it warms the index the server serves; each model keeps its own corpus in the vault, so another value builds a separate one and never mixes with it. Add `--prune` to delete the other models' corpora after indexing (it is skipped if the vault's indexing threw or indexed no note at all, and it refuses β€” failing that vault β€” if the selected model's corpus is missing when it runs, so neither a failed run nor a concurrent prune by another model ever leaves you without an index; if another process holds the vault, it prints the same waiting line as indexing).

For other clients (Cursor / Windsurf / npx), see [docs/guide/installation.md](./docs/guide/installation.md).

---

## πŸ“š Documentation

> **Every tool accepts an optional `vault` parameter.** In multi-vault mode, `search_notes`, `query_notes`, `get_vault_overview`, `list_tags`, and `list_properties` fan out across all registered vaults when `vault` is omitted.

User guide lives in [`docs/guide/`](./docs/guide/README.md):

- [Installation](./docs/guide/installation.md)
- [Finding Notes](./docs/guide/finding-notes.md) β€” `search_notes` (hybrid semantic + lexical), structured queries (`query_notes`), `get_similar_notes`, `find_duplicates`, `get_note_links`
- [Reading & Modifying](./docs/guide/reading-and-modifying.md) β€” note CRUD, daily notes, properties, tags, vault snapshot (`get_vault_overview`)
- [Routing Between Tools](./docs/guide/routing.md)
- [Embedding Models](./docs/guide/embedding-models.md) β€” choosing and configuring the embedding model (`--model`), switching cost, per-model thresholds, benchmarks
- [Configuration](./docs/guide/configuration.md) β€” CLI args, vault conventions for external agents, troubleshooting, limitations, development

Architecture / internals: [`docs/architecture/`](./docs/architecture/).

πŸ“ What changed between versions β€” including every `⚠ BREAKING CHANGES` block β€” lives in [`CHANGELOG.md`](./CHANGELOG.md) (shipped inside the npm package too). The same notes are published per version on [GitHub Releases](https://github.com/AlexMost/neuro-vault/releases).

---

### Vault-specific conventions for external agents

Drop a `<vault>/.neuro-vault/for-external-agents.md` into your vault to teach external agents the rules that cannot be derived from a structural snapshot β€” closed sets of frontmatter `type` values, folders that are off-limits for writes, how you scope notes to a project. The file is optional; without it the server still ships sane defaults plus a pointer to `get_vault_overview`.

It is delivered one way:

- πŸ“¦ **In every `get_vault_overview` response**, as a `conventions` field β€” and in the `vault://overview` resource (`vault://<vault-name>/overview` when several vaults are registered). It is read at call time (so edits take effect on the next call, **no server restart**), it reaches sub-agents, and no client we have measured truncates it. The field is simply absent when you have no such file. The MCP `instructions` string carries only a pointer telling the agent to make that call β€” never a copy of your file ([ADR-0012](./docs/adr/0012-conventions-leave-the-instructions-channel.md)).

Keep the file under **8,000 characters**. Past that the `conventions` field carries a trimmed slice and sets `conventions_truncated: true` β€” you never lose the call, but you do lose the tail, so compact rules beat exhaustive ones.

With several vaults registered, each one carries its own file: every entry in a fanned-out `results_by_vault` gets its own vault's `conventions`.

---

### πŸ™ˆ Keeping folders out of search

Note discovery β€” the lexical leg of `search_notes`, `query_notes`, tag/property listings, `get_vault_overview` counts, backlinks, note-name resolution β€” skips dot-directories, `Templates/`, and every entry in the vault root's `.gitignore`. `read_notes` by explicit path still reads them.

To exclude more, drop a `<vault>/.neuro-vault/config.json` with `{ "exclusions": ["Archive/**"] }`. Full rules, including the `.gitignore` subset that is honoured: [Configuration β†’ Excluding paths from discovery](./docs/guide/configuration.md#excluding-paths-from-discovery).

---

## πŸ“„ License

ISC β€” see [LICENSE](LICENSE).

Changelog: [CHANGELOG.md](./CHANGELOG.md) Β· [GitHub Releases](https://github.com/AlexMost/neuro-vault/releases)

Maintenance

ActivityActive
ResponsivenessResponsive