obsidian-rag-mcp
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., "@obsidian-rag-mcpwhat did we decide about shipping the battery?"
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.
obsidian-rag-mcp
Local hybrid search over an Obsidian vault, exposed to Claude Code through the Model Context Protocol.
Ask Claude "what did we decide about shipping the battery?" and it searches your notes, reads the relevant ones, and answers with citations like Projects/Solar Car Logistics.md#Decisions, without the notes leaving your machine.
Hybrid retrieval: semantic vector search and BM25 keyword search, merged with reciprocal rank fusion.
On-device by default: embeddings run locally (
bge-small-en-v1.5via transformers.js). OpenAI is optional.Incremental: only changed notes are re-read, and only edited sections are re-embedded.
Obsidian-aware: heading-aware chunks, YAML frontmatter, tags, aliases, and
[[wiki links]]that can pull linked notes into results.Measured: every ranking change was benchmarked on a reproducible sample vault and on a real 171-note vault.
Sandboxed: Claude can only read Markdown inside the vault and never excluded or hidden folders.
How it works
flowchart LR
subgraph Index["Indexing (sync)"]
A[Vault .md files] --> B{size / mtime<br/>changed?}
B -- no --> S[skip, no read]
B -- yes --> C{content hash<br/>changed?}
C -- no --> S
C -- yes --> D[Parse frontmatter,<br/>tags, links, aliases]
D --> E[Split by heading,<br/>fence-aware, with overlap]
E --> F{chunk hash<br/>seen before?}
F -- yes --> G[reuse stored vector]
F -- no --> H[embed]
G & H --> I[(LanceDB<br/>vectors + BM25 index)]
endflowchart LR
Q[Query] --> V[Vector search<br/>cosine]
Q --> K[BM25 search<br/>title, aliases, heading, text]
V --> R[Reciprocal rank fusion<br/>max 2 chunks per note]
K --> R
R --> L{expandLinks?}
L -- yes --> W[Append best chunk of<br/>wiki-linked notes]
L -- no --> O[Results with citations]
W --> OChunking. Notes are split at headings, and each chunk keeps its heading path (Projects > Freight > Sea). Headings inside fenced code blocks are ignored, and a heading with no text of its own is merged into its first subsection. Sections longer than RAG_CHUNK_SIZE are cut at paragraph or sentence boundaries, with overlap between neighbouring pieces.
What gets indexed. Each chunk's search text is its note title, aliases, heading path and body. The same string is both embedded and keyword-indexed, so a note is found by its title or alias even when the body never repeats it.
Ranking. Vector and keyword results are merged with reciprocal rank fusion: each result scores weight / (60 + rank) in each list it appears in. Keyword hits get weight 1.15, a value chosen by measurement (see Design decisions). At most two chunks per note are returned, so one long note cannot fill every slot.
Incremental sync. A manifest records each file's size, mtime and content hash. An idle sync only calls stat. When a note changes, chunks whose search text is unchanged keep their stored vector, so editing one paragraph re-embeds one chunk. The index rebuilds itself when the embedding model, chunk settings, schema or vault path changes.
Related MCP server: obsidian-mcp
Quick start
Requires Node.js 22.9 or newer.
git clone https://github.com/AdrianBodrug/obsidian-rag-mcp.git
cd obsidian-rag-mcp
npm install
cp .env.example .env # then set OBSIDIAN_VAULT_PATH
npm run rag:sync # first run downloads the ~33 MB embedding model
npm run rag:search -- "how do we ship the battery" --expand-linksConnect Claude Code
Claude Code picks up the bundled .mcp.json when started in this directory. To use the vault from any project:
claude mcp add obsidian-rag -- node --env-file-if-exists=/path/to/obsidian-rag-mcp/.env /path/to/obsidian-rag-mcp/rag/server.jsThe server speaks plain MCP over stdio, so any MCP client can run it with the same command.
MCP tools
Tool | Purpose |
| Hybrid search. Options: |
| Read the full note behind a search result. |
| Incremental sync, or |
| Index size, model and last sync time. |
The server's instructions tell Claude to cite sources and to treat note text as data, never as instructions.
Benchmarks
npm run bench indexes a vault into a throwaway database and reports hit@1 (the right note ranks first), hit@6, recall@6 (share of all relevant notes retrieved) and MRR (mean reciprocal rank).
The baseline is the original retrieval logic before the fixes and improvements in this repository's history, measured with the same evaluator.
Sample vault: examples/vault, 16 notes, 40 queries in examples/evals.jsonl. Reproducible with npm run bench.
Embeddings | Version | hit@1 | hit@6 | recall@6 | MRR |
local (bge-small) | baseline | 0.925 | 1.000 | 1.000 | 0.956 |
local (bge-small) | current | 0.975 | 1.000 | 1.000 | 0.988 |
local-hash (keyword only) | baseline | 0.775 | 0.950 | 0.938 | 0.850 |
local-hash (keyword only) | current | 0.850 | 0.975 | 0.975 | 0.900 |
Real vault: a personal vault of 171 notes (~920 KB of course notes, lecture summaries and projects), 24 paraphrased queries. The notes and queries are private, so only aggregates are shown.
Embeddings | Version | hit@1 | hit@6 | recall@6 | MRR |
local (bge-small) | baseline | 0.958 | 1.000 | 0.704 | 0.979 |
local (bge-small) | current | 0.958 | 1.000 | 0.754 | 0.979 |
local (bge-small) | current + | 0.958 | 1.000 | 0.972 | 0.979 |
local-hash (keyword only) | baseline | 0.583 | 0.917 | 0.586 | 0.722 |
local-hash (keyword only) | current | 0.667 | 0.958 | 0.678 | 0.757 |
local-hash (keyword only) | current + | 0.667 | 1.000 | 0.885 | 0.762 |
What moved the numbers:
Indexing titles and aliases fixed alias queries such as "branching strategy" and "ML course": sample-vault hit@1 went from 0.925 to 0.975 with local embeddings.
Capping chunks per note raised real-vault recall from 0.704 to 0.754. Several sections of one long note had been crowding out other relevant notes.
Link expansion raised real-vault recall to 0.972. It returns up to three extra notes on top of the limit, so this is not a like-for-like comparison. It is off by default.
Configuration
Set in .env or the environment. Invalid values fail at startup rather than being silently replaced.
Variable | Default | Purpose |
| required | Vault root |
|
|
|
| per provider |
|
| per provider | 384 / 1536 / 256 |
| Required only for | |
|
| Derived index; safe to delete |
|
| Maximum characters per chunk |
|
| Characters shared with the next chunk (0 allowed) |
|
| Comma-separated, vault-relative; nested paths allowed |
|
| Cap per note in search results |
|
| Drop vector matches below this cosine similarity |
|
| Sync changed notes before each search |
Design decisions
LanceDB runs embedded, with no server, and supports vector search and a BM25 full-text index on the same table. The index is a derived cache and the Markdown files remain the source of truth.
Hybrid instead of vector-only. Embeddings blur exact identifiers such as
UN 3480orArt. 102, while BM25 misses paraphrases. Rank fusion needs no score calibration between the two.Keyword weight 1.15 was measured, not guessed. On the real vault, 1.0 lowered hit@1 from 0.958 to 0.917 (local) and from 0.667 to 0.583 (hash), and 1.5 gave no further gain.
Chunk size stays at 3,500. A sweep from 800 to 3,500 characters moved real-vault hit@1 by at most one query in 24: smaller chunks helped recall slightly and hurt hit@1 slightly. That is noise, so the default was not changed. Note that bge-small reads only the first 512 tokens (~2,000 characters) of a chunk. The rest of a long chunk is still found by keyword search but not by vector search, which is the main reason to lower the size for prose-heavy vaults.
Minimum similarity is off by default. With bge-small, relevant queries score about 0.67–0.74 and off-topic ones about 0.51, so
0.6is a reasonable starting point. The eval set has no unanswerable queries to tune it on.
Security
read_notenormalises the path, resolves symlinks and rejects anything outside the vault, in a hidden folder (.obsidian) or in an excluded folder. Its errors never reveal absolute paths.Folder and tag filters are escaped for SQL string literals and
LIKEwildcards.The server instructions tell Claude to treat retrieved note text as untrusted data. This is the main defence against prompt injection from note content.
With the default provider, no note content leaves the machine.
Development
npm test # 26 tests: parsing, sync, retrieval, sandboxing, MCP end-to-end
npm run lint
npm run bench # sample-vault benchmark, local and local-hash embeddings
npm run bench -- --vault ~/MyVault --evals my-evals.jsonl --expand-linksEvaluation cases are JSON lines: {"query": "...", "expectedSources": ["Folder/Note.md"], "folders": [], "tags": []}. Files under evals/private/ are gitignored.
Roadmap
Rerank the top candidates with a cross-encoder.
Watch the vault for changes instead of syncing before each search.
Add unanswerable queries to the eval set to tune
RAG_MIN_SIMILARITY.
License
This server cannot be deployed
Maintenance
Related MCP Connectors
Search and reason over your Obsidian-style Markdown vault, right from ChatGPT.
Search your Obsidian vault to quickly find notes by title or keyword, summarize related content, a…
Token-efficient MCP memory for Markdown vaults. Tiered search, GraphRAG, AI memories.
Connect AI assistants to your GitHub-hosted Obsidian vault to seamlessly access, search, and analy…
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceEnables semantic search and content retrieval from local Obsidian vaults through the Model Context Protocol. It allows AI assistants to query notes by meaning, filter by tags, and access full note content for enhanced knowledge integration.MIT
- FlicenseNot gradedqualityAmaintenanceEnables semantic search and note management for Obsidian vaults via the Model Context Protocol, allowing LLMs to search, read, and index notes, PDFs, and web pages locally.-
- AlicenseNot gradedqualityBmaintenanceEnables Claude Code to search and retrieve from a local knowledge base of markdown notes using hybrid semantic+keyword search, keeping data entirely offline.6 npmMIT
- AlicenseNot gradedqualityAmaintenanceProvides a hybrid search engine for Obsidian vaults, enabling LLM agents to query notes with BM25 keyword and vector semantic search, metadata filtering, and sibling-document retrieval.442 npmMIT