Obsidian Hybrid RAG MCP Server
This server lets you search and retrieve content from an Obsidian vault using hybrid RAG (semantic + lexical search with reranking), read notes with pagination, and keep the index in sync.
Search your vault with natural language queries via
search_vault, returning top-k ranked passages with titles, paths, headings, line numbers, relevance scores, and markdown text.Filter search by heading using an optional substring filter (e.g., "Architecture", "Runbook").
Read full notes with
get_note, specifying relative path, offset line, and line limits for paginated, bounded context retrieval.Sync the vault incrementally with
sync_vaultto index new, modified, or deleted notes (dense vectors + FTS5 updated).Integrate with MCP clients like Claude Desktop, Hermes Agent, or Cursor over stdio or SSE.
Provides hybrid RAG search over an Obsidian vault, enabling semantic and keyword search across notes, retrieving note content with line-numbered excerpts, and triggering incremental vault synchronization.
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 Hybrid RAG MCP Serversearch my vault for agile retro formats and show top matches"
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 Hybrid RAG MCP Server
A production-grade, zero-bloat Model Context Protocol (MCP) server providing Two-Stage Hybrid RAG (Retrieval-Augmented Generation) for Obsidian markdown knowledge vaults.
Engineered with SOTA dual-model AI (BAAI/bge-m3 1024-dim dense embeddings + jinaai/jina-reranker-v2 cross-encoder) and in-process C-extensions (sqlite-vec + SQLite FTS5 BM25), eliminating heavy external vector database daemons (Qdrant, Milvus, Chroma) and bulky abstraction frameworks (LangChain, LlamaIndex).
๐๏ธ Architecture Overview
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Obsidian Vault (.md) โ
โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โ (Heading-Aware Chunker)
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Single Embedded SQLite Database โ
โ โโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ
โ โ SQLite FTS5 โ โ sqlite-vec โ โ
โ โ (BM25 Lexical) โ โ (1024-dim Dense) โ โ
โ โโโโโโโโโโโฌโโโโโโโโโโโ โโโโโโโโโโโฌโโโโโโโโโ โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโ
โ โ
[BM25 Lexical Hits] [Cosine Distance Hits]
โ โ
โโโโโโโโโโโโฌโโโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Stage 1: Reciprocal Rank Fusion (RRF, k=60) โ
โ Merges lexical + semantic into Top 15 pool โ
โโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Stage 2: Cross-Encoder Neural Reranker โ
โ (jina-reranker-v2-base-multilingual ONNX) โ
โ Deep query-document attention scoring โ
โโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ Top K Results
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FastMCP Stdio Interface โ
โ (Hermes Agent / Claude Desktop / Cursor) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโRelated MCP server: obsidian-rag-mcp
โจ Key Features
Anti-Bloat, In-Process Architecture:
Runs entirely inside a single Python process and a single SQLite file (
vault-index.db).Native C-extension vector similarity via
sqlite-vec. Zero Docker containers, zero network ports, zero external vector service management.
State-of-the-Art Dual-Model Pipeline:
Dense Bi-Encoder:
BAAI/bge-m3(1024 dimensions, 8,192 token context window, multi-lingual SOTA).Cross-Encoder Reranker:
jinaai/jina-reranker-v2-base-multilingualexecuted via FastEmbed ONNX Runtime with 2 execution threads.
Heading-Aware Hierarchy Chunking:
Preserves document outline semantics (
#,##,###,####).Context is injected with parent breadcrumbs (
Title > Heading > Subheading) so vector embeddings never lose high-level context.Preserves line-number traceability (
L45-L78) for fast file jumps.
Two-Stage Hybrid Search with Reciprocal Rank Fusion (RRF):
Stage 1A: SQLite FTS5 evaluates exact keyword matches, code identifiers, and acronyms using BM25.
Stage 1B:
sqlite-vecretrieves semantically analogous concepts across multilingual vocabularies.Fusion: RRF ($score = \sum \frac{1}{60 + rank}$) merges candidates into the top 15 pool.
Stage 2: Cross-encoder computes pairwise joint attention between the query and candidate passages, returning precision-ranked results.
Sub-millisecond IPC:
FastMCP Stdio communication with JSON-RPC over stdin/stdout. Zero HTTP overhead.
โก Performance & Benchmarks
Tested on Linux x86_64, 4 vCPU (Intel Xeon / AMD EPYC), 4 GB RAM:
Metric | Measured Value | Note |
Query Latency (Stage 1 Hybrid) | ~28 ms | FTS5 BM25 + sqlite-vec 1024-dim |
Query Latency (Stage 2 Rerank) | ~245 ms | Jina Reranker v2 ONNX 15 candidates |
End-to-End Query Latency | < 280 ms | Cold cache to final JSON response |
Incremental Sync Time | < 0.1 s | Unmodified notes bypassed via SHA256 hashes |
Peak Inference Memory (RSS) | ~2.4 GB | PyTorch CPU (BGE-M3) + FastEmbed ONNX |
Indexing Throughput | ~8-10 chunks/sec | 4-thread CPU batch matrix computation |
๐ Installation & Quickstart
1. Prerequisites
Python 3.10, 3.11, or 3.12
Recommended:
uvpackage manager for ultra-fast virtualenv management.
2. Clone and Setup
git clone https://github.com/mpandudc/obsidian-hybrid-rag-mcp.git
cd obsidian-hybrid-rag-mcp
# Create dedicated virtualenv
uv venv .venv
source .venv/bin/activate
# Install dependencies (CPU optimized)
uv pip install torch --index-url https://download.pytorch.org/whl/cpu
uv pip install -e .3. Environment Configuration
Create a .env or set environment variables:
# Path to your local Obsidian vault root
export VAULT_PATH="/path/to/your/obsidian-vault"
# Path to the persistent SQLite index database
export INDEX_DB_PATH="$HOME/.config/obsidian-mcp/vault-index.db"4. Build Initial Index
Run the indexer to parse markdown notes, generate BGE-M3 dense embeddings, and populate FTS5 indices:
# Full initial build
vault-indexer --vault-path "/path/to/your/obsidian-vault" --rebuild
# Or incremental sync
vault-indexer --vault-path "/path/to/your/obsidian-vault"๐ MCP Client Configuration
Claude Desktop (claude_desktop_config.json)
On macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
On Windows: %APPDATA%\Claude\claude_desktop_config.json
On Linux: ~/.config/Claude/claude_desktop_config.json
{
"mcpServers": {
"obsidian-vault": {
"command": "/path/to/obsidian-hybrid-rag-mcp/.venv/bin/python",
"args": ["-m", "src.server"],
"env": {
"VAULT_PATH": "/path/to/your/obsidian-vault",
"INDEX_DB_PATH": "/path/to/obsidian-hybrid-rag-mcp/vault-index.db"
}
}
}
}Hermes Agent CLI / Ecosystem
Standard Stdio:
hermes mcp add vault --command "/path/to/obsidian-hybrid-rag-mcp/.venv/bin/python -m src.server"Shared SSE Daemon Mode (Recommended for Multi-Agent setups): Run as a background daemon or systemd service to keep models pre-warmed and share a single ~2.1 GB RAM footprint across multiple profiles or sessions:
# Start daemon listening on SSE
vault-mcp --transport sse --host 127.0.0.1 --port 8765 --preloadRegister with Hermes:
hermes config set mcp_servers.vault.url http://127.0.0.1:8765/sse
hermes config set mcp_servers.vault.transport sseOr configure via systemd user service (~/.config/systemd/user/vault-mcp.service):
[Unit]
Description=Obsidian Hybrid RAG FastMCP Daemon (SSE)
After=network.target
[Service]
Type=simple
ExecStart=/path/to/.venv/bin/python -m src.server --transport sse --host 127.0.0.1 --port 8765 --preload
Restart=always
RestartSec=5
[Install]
WantedBy=default.target๐ ๏ธ MCP Tools Exposed
1. search_vault
Executes Two-Stage Hybrid RAG query across your notes.
Parameters:
query(string, required): Natural language question or search phrase.top_k(integer, default:5): Number of highest-ranked passages.heading_filter(string, optional): Substring filter for specific note headings (e.g.Architecture,Runbook).
Response: Array of candidate objects with
title,rel_path,heading,lines,relevance_score, and markdowntext.
2. get_note
Reads full note content with line numbering and bounded pagination to prevent context window overflow.
Parameters:
rel_path(string, required): Relative note path (e.g.,projects/cuantum.md).offset_line(integer, default:1): Start line number.limit_lines(integer, default:250): Maximum lines to return.
3. sync_vault
Triggers incremental background synchronization to detect newly modified or deleted notes.
Parameters: None.
๐งช Testing
Run the included test suite:
pytest tests/๐ License
This project is licensed under the terms of the MIT License.
Developed with โค๏ธ by Muhammad Pandu Dwi Cahyo.
Available Tools
3 toolsget_noteGet NoteA
Retrieve full markdown content of a specific note with line number pagination.
| Name | Required | Description | Default |
|---|---|---|---|
| rel_path | Yes | Note path relative to vault root (e.g. 'trading/cuantum-setup.md') | |
| limit_lines | No | Maximum lines to return in single request | |
| offset_line | No | Line number to start reading from (1-indexed) |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the burden of disclosing behavior. It clearly signals a read-only operation ('Retrieve') and describes pagination behavior, which helps an agent understand it may receive partial content with limit/offset. It does not discuss error cases or side effects, but for a read tool the core behavioral traits are covered.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, front-loaded sentence with no filler. It states the action, resource, and key pagination behavior in a compact way that is easy to parse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a straightforward read tool with a complete input schema and an output schema, the description covers the essential behavior. It could be more complete by explicitly noting that full content may require multiple paginated calls and by referencing sibling tools, but the current definition is sufficient for correct invocation in most cases.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the input schema already explains rel_path, limit_lines, and offset_line. The description's mention of 'line number pagination' adds conceptual context but not new parameter-level details, so the baseline score of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Retrieve'), a clear resource ('full markdown content of a specific note'), and a distinctive mechanism ('line number pagination'). This makes it easy to distinguish from sibling tools search_vault and sync_vault, which are clearly different operations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies the tool should be used when you know the exact note path and need its full content, but it does not explicitly say when to prefer it over search_vault or sync_vault. There is no mention of alternatives or exclusions, so guidance is only implicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_vaultSearch VaultA
Search your Obsidian knowledge base using state-of-the-art Hybrid RAG. Combines SQLite FTS5 (BM25 lexical), BAAI/bge-m3 (1024-dim dense semantic vector), Reciprocal Rank Fusion (RRF), and Jina Reranker v2 Cross-Encoder scoring.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Natural language question or search phrase | |
| top_k | No | Number of highest-ranking passages to return (default 5) | |
| heading_filter | No | Optional substring filter for document section headers |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the burden for behavioral disclosure. It does reveal the retrieval pipeline (BM25, dense embeddings, RRF, reranking), which is useful behavioral context. However, it does not explicitly state that the tool is read-only, mention any network/local dependencies, or note potential latency from reranking.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences with a clear front-loaded purpose. The second sentence lists the technical underpinnings, which is informative but slightly dense with jargon; it still earns its place by explaining what 'Hybrid RAG' means. No redundant filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a search tool with a full input schema and an output schema, the description covers the core behavior and mechanism sufficiently. The main gap is the missing guidance on tool selection relative to siblings, and the lack of explicit safety/read-only context due to absent annotations.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema fully documents all three parameters (query, top_k, heading_filter) with descriptions and defaults, so schema coverage is 100%. The description adds no parameter-level detail, but it doesn't need to since the schema already covers it.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Search your Obsidian knowledge base.' It clearly identifies the tool as a search operation, distinct from the sibling tools get_note and sync_vault. The additional technical detail (Hybrid RAG) reinforces its specialized role.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit guidance about when to use search_vault versus get_note or sync_vault. The description states what the tool does but provides no alternatives, exclusions, or contextual conditions that would help an agent decide between the siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sync_vaultSync VaultB
Trigger incremental sync on the Obsidian vault. Scans for created, modified, or deleted notes and updates dense vectors & FTS5 index.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden of behavioral disclosure. The description says it triggers a sync and updates vectors and indexes, but it doesn't mention whether it's safe to call (read-only vs mutating), what side effects occur (e.g., does it modify the vault or just the index?), or any performance implications. An agent cannot infer the safety profile from this sparse description.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise, with two sentences: the first states the primary action, and the second elaborates on scope and effects. It's front-loaded with the key verb, and there's no wasted words. However, it could have been structured to include a leading phrase for when to use it, but it's still efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given that this tool has no parameters and an output schema exists, the description doesn't need to explain return values. However, the description lacks context on when to use it relative to siblings (e.g., after editing notes) and what the impact is. It's adequate for a simple trigger tool but could be improved by mentioning that it's for keeping search indexes up to date, which is implied but not explicit.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Since there are no parameters, the description needs to explain the scope of the sync, which it does by mentioning it's incremental and scans for created/modified/deleted notes. With zero parameters, the baseline is 4, and the description doesn't need to compensate for parameter documentation; it provides clear semantics for what the tool does without any input.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's verb ('Trigger incremental sync') and the resource ('Obsidian vault'). It also specifies what the sync does: scanning for created, modified, or deleted notes and updating dense vectors & FTS5 index. This is specific and distinguishes it from search_vault and get_note, which are read operations, though it doesn't explicitly name them as alternatives.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies when to use this tool: whenever the vault's notes have changed and you need indexes updated. However, it doesn't explicitly state when not to use it (e.g., for full re-index) or compare it to search_vault or get_note as alternatives for different purposes. The context that it's for syncing is clear, but exclusions are absent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v1.0.0- First observed
get_note - First observed
search_vault - First observed
sync_vault
TDQS
Scored across 3 tools
Each tool has a clearly distinct purpose: search_vault queries, get_note retrieves a specific note, and sync_vault updates the index. There is no overlap or ambiguity between them.
All tool names follow a consistent verb_noun snake_case pattern: search_vault, get_note, sync_vault. The naming is predictable and uniform.
Three tools is well-scoped for a focused Obsidian RAG server: search, retrieve, and sync. Each tool serves a necessary function without redundancy.
The core workflow of searching, retrieving, and syncing is covered. A minor gap is the lack of an explicit listing or status tool, but users can still work around this via search.
Maintenance
Related MCP Connectors
Search your Obsidian vault to quickly find notes by title or keyword, summarize related content, aโฆ
Search and reason over your Obsidian-style Markdown vault, right from ChatGPT.
Search everything you save: YouTube, articles, podcasts, PDFs, Notion, Obsidian. API key or OAuth.
Personal context for every AI: search, read, and write back to your private Markdown library.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides semantic search and keyword search over Obsidian notes, along with direct note retrieval, allowing external AI agents to query and access the vault.19BSD Zero Clause
- AlicenseNot gradedqualityCmaintenanceEnables semantic search over an Obsidian vault using natural language, retrieving relevant notes and extracted conclusions.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 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.5 npmMIT