Labsco
flowing-abyss logo

Obsidian Hybrid Search

β˜… 84

from flowing-abyss

Local-first MCP server for searching private Obsidian vaults with hybrid full-text, fuzzy, semantic, and wikilink graph retrieval.

πŸ”₯πŸ”₯πŸ”₯βœ“ VerifiedFreeAdvanced setup

Obsidian Hybrid Search

Your Obsidian vault already contains your best thinking. Obsidian Hybrid Search makes that thinking easier to find, reuse, and bring into AI-assisted work.

It gives your vault one retrieval engine and three practical ways to use it. The native Obsidian plugin gives you fast search, previews, similar notes, link discovery, and graph views while you write. The MCP server lets AI agents search and read your notes as tool calls. The CLI gives power users the same engine for indexing, filtering, reranking, reading, and scripting.

The search understands how real vaults are built. It combines semantic search, BM25 full text, fuzzy title and alias matching, tags, folders, frontmatter, wikilinks, backlinks, and similar-note lookup. You can search by idea, phrase, title, relationship, or metadata without remembering the exact words you wrote.

That turns Obsidian into a stronger personal knowledge system and a better starting point for AI work. Agents can begin from your own notes, pull cited context from source files, follow related material, and work with knowledge you already trust. OHS runs locally by default with SQLite, FTS5, sqlite-vec, RRF ranking, and optional OpenAI-compatible embedding APIs.

Search quality

Evaluated on the Obsidian Help vault (171 notes, 58 queries, local model):

OHS (this project) qmd nDCG@5 0.733 0.659 MRR 0.788 0.665 Hit@1 0.724 0.500 Avg query time 571 ms ΒΉ 754 ms Β² Model download ~117 MB ~2.2 GB

ΒΉ CPU (Apple Silicon), hybrid mode, no rerank. Β² GPU (Apple Silicon Metal), LLM query expansion + reranking.

OHS uses Xenova/multilingual-e5-small. How to reproduce β†’ Β· Full benchmark β†’

Real knowledge-vault benchmark

OHS is also evaluated on Andy Matuschak’s public evergreen notes, converted into an Obsidian vault with title-based note filenames, source URLs in frontmatter, local attachments, and 5,000+ internal note links across 1,357 notes.

The curated golden set includes 78 hand-judged queries across known-item lookup, paraphrases, quote fragments, ambiguous topics, citation lookup, and multi-note evidence.

Using the default local embedding model, OHS performs strongly on this dense note network.

Metric Value nDCG@5 0.722 nDCG@10 0.753 MRR 0.874 Hit@1 0.795 Hit@5 0.974 Recall@10 0.972 AllRel@10 0.949

The benchmark exercises retrieval over a highly connected real-world knowledge vault, including queries that do not simply repeat note titles.

Result JSON Β· Reproduce and interpret β†’

Large memory benchmark

To test retrieval on a larger public dataset, LongMemEval-S was converted into a 22,419-note Obsidian-style vault with 470 retrieval queries. Using baai/bge-m3 embeddings, OHS ranked the answer-bearing notes strongly:

Metric Value nDCG@5 0.895 MRR 0.920 Hit@1 0.889 Hit@5 0.968 Recall@10 0.950 AllRel@10 0.904

For this benchmark, each query uses the LongMemEval-provided haystack as its search scope. That makes the result reproducible and easy to inspect query by query, while still exercising retrieval over a large generated memory vault.

Result JSON Β· Reproduce and interpret β†’

Features

  • Hybrid search

  • BM25 + fuzzy title + semantic embeddings, fused with RRF

  • Alias search

  • notes with aliases: in frontmatter are indexed and searchable by any alias; alias matches are boosted in BM25 (weight 5Γ—) and fuzzy title scoring

  • Four search modes

  • hybrid, semantic, fulltext, title (for text queries)

  • Similar note lookup

  • pass --path to find semantically related notes using stored chunk embeddings, with a title + content fallback

  • Graph traversal

  • --path --related shows linked notes at configurable depth; filter by --direction outgoing|backlinks|both

  • Links & backlinks

  • every result includes outgoing links and backlinks

  • Scope filtering

  • restrict to subfolder(s); supports multiple values and exclusions (-notes/dev/)

  • Tag filtering

  • filter by tag(s); supports multiple values and exclusions (-category/cs)

  • Snippet control

  • --snippet-length sets the context window; empty snippets always fall back to note content

  • Extended output

  • --extended adds a TAGS/ALIASES column to the CLI table showing frontmatter tags (#tag) and aliases

  • Incremental indexing

  • only re-indexes changed files; watches for edits in real time

  • Multi-query fan-out

  • pass multiple queries at once (ohs "q1" "q2" or queries[] in MCP); results are merged via RRF β€” a note that ranks well in any one query floats to the top; useful when the note may use different vocabulary than the query

  • Cross-encoder reranking

  • --rerank re-scores results with bge-reranker-v2-m3 (ONNX int8, ~570 MB download once); improves precision for conceptual and multilingual queries; applied after multi-query merge

  • Local embeddings

  • works offline via @huggingface/transformers (no API key required); default model: Xenova/multilingual-e5-small, 100+ languages

  • Remote embeddings

  • OpenAI-compatible API (OpenRouter, Ollama, etc.)

  • Note reading

  • read fetches one or more notes by vault-relative path; returns full content with title, aliases, tags, links, and backlinks; on path miss returns top-3 fuzzy suggestions

  • Ignore patterns

  • exclude folders, extensions, or specific files

  • Obsidian plugin

  • native search modal inside Obsidian powered by the same CLI β€” see obsidian-hybrid-search-plugin

MCP server

Most AI assistants operate without access to your personal knowledge β€” they can only work with what you paste into the conversation. Adding this server gives any MCP-compatible assistant a persistent, searchable index of your entire vault. It becomes a tool call, not a copy-paste session: the assistant queries your notes the same way it calls any other tool, gets ranked results with snippets and links, and can navigate your knowledge graph on request.

Add to your MCP config (.mcp.json, claude_desktop_config.json, or equivalent for your client).

Minimal config (local embeddings, no API key)

Uses the built-in Xenova/multilingual-e5-small model β€” works fully offline, supports 100+ languages. Downloads ~117 MB on first run.

Copy & paste β€” that's it
{
 "mcpServers": {
 "obsidian-hybrid-search": {
 "command": "npx",
 "args": ["-y", "-p", "obsidian-hybrid-search@latest", "obsidian-hybrid-search-mcp"],
 "env": {
 "OBSIDIAN_VAULT_PATH": "/path/to/your/vault"
 }
 }
 }
}

Full config (OpenRouter)

Copy & paste β€” that's it
{
 "mcpServers": {
 "obsidian-hybrid-search": {
 "command": "npx",
 "args": ["-y", "-p", "obsidian-hybrid-search@latest", "obsidian-hybrid-search-mcp"],
 "env": {
 "OBSIDIAN_VAULT_PATH": "/path/to/your/vault",
 "OBSIDIAN_PREFIX": "myvault_",
 "OBSIDIAN_RESPECT_GITIGNORE": "true",
 "OBSIDIAN_IGNORE_PATTERNS": ".obsidian/**,templates/**,*.canvas",
 "OBSIDIAN_INCLUDE_PATTERNS": "private/notes/**",
 "OPENAI_API_KEY": "sk-or-v1-...",
 "OPENAI_BASE_URL": "https://openrouter.ai/api/v1",
 "OPENAI_EMBEDDING_MODEL": "openai/text-embedding-3-small"
 }
 }
 }
}

Note: On first run, npx will install the package automatically. Ignore patterns are persisted in the database and restored on every subsequent startup even if the env var is missing.

Shared HTTP server

Use this when multiple MCP clients should share one long-lived search/indexing process.

Start or reuse the background server:

Copy & paste β€” that's it
OBSIDIAN_VAULT_PATH="/path/to/your/vault" obsidian-hybrid-search serve

serve starts the MCP server over HTTP by default; serve --http is the explicit equivalent. The command prints the server URL, PID, log path, and a client config snippet. The default bind address is 127.0.0.1:3939.

Then add this to a URL-based MCP client config (.mcp.json, claude_desktop_config.json, or equivalent):

Copy & paste β€” that's it
{
 "mcpServers": {
 "obsidian-hybrid-search": {
 "url": "http://127.0.0.1:3939/mcp"
 }
 }
}

Manage the server:

Copy & paste β€” that's it
obsidian-hybrid-search serve status
obsidian-hybrid-search serve stop
obsidian-hybrid-search serve --foreground
obsidian-hybrid-search serve --http --foreground

HTTP mode uses MCP Streamable HTTP. If port 3939 is already in use, the command exits with an error instead of choosing another port automatically. Use --port for separate vaults.

When binding beyond localhost, add the client-facing Host header with --allowed-host <host[:port]> or OBSIDIAN_MCP_ALLOWED_HOSTS; --allow-any-host disables Host-header protection for trusted networks.

Available MCP tools

The MCP server exposes four tools:

Tool Description search Search the vault. Use query for text search (mode: hybrid/semantic/fulltext/title) or path for semantic similarity. Combine path with related: true for graph traversal. Pass queries[] for multi-query fan-out (parallel search, RRF merge). Supports scope, tag, limit, threshold, depth, direction, snippet_length, rerank read Fetch one or more notes by vault-relative path. Returns full content, title, aliases, tags, links, and backlinks. On path miss: returns found: false with top-3 fuzzy suggestions. Accepts a single path or an array. Use snippet_length to cap content size reindex Reindex the vault or a specific file status Show total notes, indexed count, last indexed time

If OBSIDIAN_PREFIX is set, tool names are prefixed in the MCP list (for example myvault_search, myvault_read). By default OBSIDIAN_PREFIX is empty, so tool names remain search, read, reindex, status.

How it works

  • Indexing β€” notes are chunked by headings (with sliding-window fallback), embedded, and stored in SQLite with FTS5 and sqlite-vec.

  • Search β€” BM25 (with column weights: title 10Γ—, aliases 5Γ—, content 1Γ—), fuzzy trigram title/alias search, and vector KNN search run in parallel; results are fused with RRF and scored 0–1 (higher = more relevant).

  • Links β€” wikilinks ([[note]]) are resolved to note paths and stored; every search result includes links and backlinks arrays.

  • Watcher β€” chokidar watches for file changes and incrementally re-indexes in the background.

Development

Copy & paste β€” that's it
npm install
npm test # run test suite
npm run build # compile TypeScript

Tests use fake embeddings (no API key required) and run against a temporary vault. All tests cover chunking, BM25 scoring, fuzzy search, links/backlinks, tag filtering, scope filtering, related-mode traversal, direction/score logic, snippet fallback, and ignore pattern matching.

License

MIT