MCP server for semantic search and file management over Obsidian vaults using Qdrant and local embeddings
Project description
Obsidian Qdrant Search
MCP server for semantic search and file management over an Obsidian vault. Uses Qdrant as vector store and FastEmbed for local embeddings. Provides a complete set of tools for AI agents to read, write, search, and manage vault content โ no external Obsidian plugins required.
Table of Contents
| Section | Description | |
|---|---|---|
| ? | Why? | The problem this solves |
| โจ | Features | Full feature list |
| โก | Quick Start | Installation and setup |
| ๐ | LLM Wiki Pattern | Karpathy-inspired knowledge base model |
| ๐ค | Agent Skills | Skill and agent for Claude Code |
| ๐ง | MCP Tools | All 27 tools โ Search, Read, Write, Discover, Graph, Batch, Log, Health, Maintenance |
| ๐ป | CLI Commands | Command-line interface for any agent |
| ๐๏ธ | Architecture | How it works under the hood |
| ๐ | Project Structure | File layout |
Why?
Standard text search in Obsidian (and MCP tools like mcp-obsidian) is keyword-based โ it only finds exact matches. This means:
- Searching for "API logs" won't find a section titled "Execution tracking endpoints"
- Searching for "how does authentication work" returns nothing unless those exact words appear
- Typos, synonyms, and rephrased concepts are invisible to keyword search
obsidian-qdrant-search uses vector embeddings to understand the meaning of your query and match it against the meaning of your documentation. It finds relevant results even when the wording is completely different.
Additionally, it provides full CRUD file operations directly on the vault filesystem, so AI agents can read, create, update, and manage notes without relying on external Obsidian community plugins like Local Rest API.
Features
- LLM Wiki pattern โ Karpathy-inspired three-layer architecture for persistent, compounding knowledge bases
- Semantic search โ find docs by meaning, not just keywords
- Full file management โ read, create, update, append, patch, and delete vault files
- Targeted patching โ modify specific sections by heading or frontmatter field
- Text search โ case-insensitive keyword search across all markdown files
- Markdown-aware chunking โ tables and code blocks are never split mid-block
- Frontmatter filters โ narrow results by project, document type, or tags
- Context expansion โ fetch adjacent chunks around a search result
- Wikilink graph โ navigate backlinks, outgoing links, find broken links and orphan files
- Vault health checks โ comprehensive lint with broken links, orphans, stale docs, missing metadata
- Operation log โ chronological record of ingest, query, lint, and maintenance actions
- Vault migration โ non-destructive upgrade of existing vaults to the LLM Wiki conventions
- Multi-agent CLI โ 7 CLI commands with
--jsonoutput for any agent (Codex, OpenCode, etc.) - Vault map โ visualize directory structure with file counts
- Frontmatter schema discovery โ see all fields, types, and usage across the vault
- Tag discovery โ list all tags (frontmatter + inline) with occurrence counts
- Recent changes โ track recently modified files
- Batch operations โ update frontmatter or rename tags across multiple files at once
- Incremental indexing โ only re-embeds changed files
- Auto-reindex on write โ modified files are automatically re-indexed (best-effort)
- Auto-start Qdrant โ Docker container is managed automatically
- Local embeddings โ no API keys needed, runs entirely on your machine
- Path security โ all file operations are validated to prevent access outside the vault
Prerequisites
- Python 3.11+
- Docker (for Qdrant)
- uv (recommended) or pip
Quick start
1. MCP Configuration
Add to your .mcp.json (project root or Claude Code settings):
{
"mcpServers": {
"obsidian-qdrant-search": {
"command": "uvx",
"args": ["obsidian-qdrant-search"],
"env": {
"VAULT_PATH": "/absolute/path/to/your/vault"
}
}
}
}
Qdrant is started automatically via Docker when needed. If a qdrant container already exists, it will be reused.
2. Initial indexing
VAULT_PATH=/path/to/your/vault uvx --from obsidian-qdrant-search vault-index --full
LLM Wiki Pattern
Inspired by Karpathy's LLM Wiki, this project supports a three-layer architecture for building persistent, compounding knowledge bases maintained by LLMs:
| Layer | Directory | Purpose |
|---|---|---|
| Raw sources | raw/ |
Immutable source documents โ articles, papers, transcripts, web clips. The LLM reads from these but never modifies them. |
| Wiki | wiki/ |
LLM-maintained pages โ entities, concepts, summaries, syntheses. The LLM owns this layer entirely. |
| Schema | CLAUDE.md / AGENTS.md |
Conventions that tell the LLM how the wiki is structured and what workflows to follow. |
Operations
- Ingest โ Drop a new source into
raw/, the LLM reads it, creates/updates wiki pages, adds wikilinks, and logs the operation - Query โ Search the wiki, synthesize answers, and optionally file valuable results back as new wiki pages
- Lint โ Run
lint_vaultfor a comprehensive health check (broken links, orphans, stale docs, missing metadata)
Migration from older versions
Existing vaults can be upgraded with the migration tool. From Claude Code (or any MCP client), just ask in natural language:
"migrate my vault to the LLM Wiki pattern"
Claude Code will call the migrate_vault MCP tool automatically โ first in preview mode, then ask for confirmation before applying.
From the command line (for other agents or manual use):
# Preview what would change
VAULT_PATH=/path/to/vault uvx --from obsidian-qdrant-search vault-search-migrate
# Apply changes (creates raw/ and wiki/ dirs, adds missing frontmatter, initializes log)
VAULT_PATH=/path/to/vault uvx --from obsidian-qdrant-search vault-search-migrate --apply
The migration is non-destructive (never moves or deletes files) and idempotent (safe to run multiple times). See CLAUDE.md for the full document structure rules and conventions.
Agent Skills
This repo includes Claude Code skills and agents in .claude/. Copy the .claude/ directory into your project to make them available. Claude will automatically discover and use them based on context.
/vault-search โ Skill (.claude/skills/vault-search/)
Guides the agent through semantic search, text search, file reading, and knowledge graph navigation. Claude can invoke it automatically or you can use it as a slash command:
/vault-search how does authentication work
doc-manager โ Agent (.claude/agents/)
An autonomous documentation agent that creates, updates, organizes, and maintains vault documentation. Includes templates, conventions, health check workflows, and restructuring procedures. Claude dispatches it as a subagent when documentation tasks are needed.
/doc-manager document the new authentication module in projects/core
/doc-manager run a vault health check
/doc-manager create an ADR for switching to PostgreSQL
Environment variables
| Variable | Default | Description |
|---|---|---|
VAULT_PATH |
Current working directory | Path to the Obsidian vault directory |
QDRANT_URL |
http://localhost:6333 |
Qdrant server URL |
COLLECTION_NAME |
vault_docs |
Qdrant collection name |
VAULT_LOG_FILE |
_log.md |
Operation log filename |
MCP Tools
Search
search_vault
Semantic search over the vault documentation.
| Parameter | Type | Default | Description |
|---|---|---|---|
query |
string | required | Natural language search query |
project |
string | null | Filter by project name (e.g. "core") |
doc_type |
string | null | Filter by document type (e.g. "api-contract", "service-layer") |
tag |
string | null | Filter by frontmatter tag (e.g. "kubernetes", "database") |
top_k |
int | 5 | Number of results to return |
simple_search
Case-insensitive text search across all markdown files.
| Parameter | Type | Default | Description |
|---|---|---|---|
query |
string | required | Text to search for |
context_length |
int | 100 | Characters of context around each match |
get_chunk_context
Expand context around a search result by fetching adjacent chunks.
| Parameter | Type | Default | Description |
|---|---|---|---|
file_path |
string | required | The file_path from a search result |
chunk_index |
int | required | The chunk_index from a search result |
window |
int | 1 | Number of chunks before/after to include |
Read
get_file_contents
Read the raw content of a vault file.
| Parameter | Type | Default | Description |
|---|---|---|---|
filepath |
string | required | Path relative to vault root |
get_file_metadata
Get frontmatter metadata, tags, and file stats (size, dates).
| Parameter | Type | Default | Description |
|---|---|---|---|
filepath |
string | required | Path relative to vault root |
list_files_in_dir
List files and subdirectories in a vault directory (non-recursive).
| Parameter | Type | Default | Description |
|---|---|---|---|
dirpath |
string | "" |
Relative directory path (empty for root) |
list_files_in_vault
List all top-level files and directories in the vault root.
Write
create_or_update_file
Create a new file or overwrite an existing one. Parent directories are created automatically.
| Parameter | Type | Default | Description |
|---|---|---|---|
filepath |
string | required | Path relative to vault root |
content |
string | required | Full file content |
append_content
Append content to a file (creates the file if it doesn't exist).
| Parameter | Type | Default | Description |
|---|---|---|---|
filepath |
string | required | Path relative to vault root |
content |
string | required | Content to append |
patch_content
Targeted modification of a specific section by heading or frontmatter field.
| Parameter | Type | Default | Description |
|---|---|---|---|
filepath |
string | required | Path relative to vault root |
operation |
string | required | "append", "prepend", or "replace" |
target_type |
string | required | "heading" or "frontmatter" |
target |
string | required | Heading text (e.g. "Setup" or "Setup::Installation") or frontmatter field name |
content |
string | required | Content to insert or replace with |
delete_file
Delete a file from the vault.
| Parameter | Type | Default | Description |
|---|---|---|---|
filepath |
string | required | Path relative to vault root |
confirm |
bool | false | Must be true to actually delete (safety guard) |
Discover
list_projects
Lists all indexed projects with file and chunk counts.
list_tags
Lists all tags (frontmatter + inline #tag) with occurrence counts across the vault.
get_recent_changes
Returns recently modified markdown files sorted by modification date.
| Parameter | Type | Default | Description |
|---|---|---|---|
days |
int | 14 | Only include files modified within this many days |
limit |
int | 10 | Maximum number of results |
get_vault_map
Get the vault's directory structure as a tree with file counts.
| Parameter | Type | Default | Description |
|---|---|---|---|
max_depth |
int | 3 | Maximum directory depth to show |
get_frontmatter_schema
Discover all frontmatter fields used across the vault with types, frequency, and examples.
Graph
get_backlinks
Find all files that contain wikilinks pointing to the given file.
| Parameter | Type | Default | Description |
|---|---|---|---|
filepath |
string | required | Relative path to the target file |
get_outgoing_links
List all files that the given file links to via wikilinks.
| Parameter | Type | Default | Description |
|---|---|---|---|
filepath |
string | required | Relative path to the source file |
find_broken_links
Find all wikilinks in the vault that point to non-existent files.
find_orphan_files
Find files that have no incoming wikilinks from other files.
Batch
batch_update_frontmatter
Update a frontmatter field across multiple files matching a filter.
| Parameter | Type | Default | Description |
|---|---|---|---|
filter_type |
string | required | "project", "tag", or "glob" |
filter_value |
string | required | Filter value (project name, tag, or glob pattern) |
field |
string | required | Frontmatter field to update |
value |
string | required | Value to set/append/remove (YAML parsed) |
operation |
string | "set" |
"set", "append", or "remove" |
confirm |
bool | false | Set to true to apply (default returns preview) |
batch_rename_tag
Rename a tag across all vault files (both frontmatter and inline #tags).
| Parameter | Type | Default | Description |
|---|---|---|---|
old_tag |
string | required | Tag to rename (without #) |
new_tag |
string | required | New tag name (without #) |
confirm |
bool | false | Set to true to apply (default returns preview) |
Log
log_operation
Append a structured entry to the vault operation log.
| Parameter | Type | Default | Description |
|---|---|---|---|
operation_type |
string | required | "ingest", "query", "lint", or "maintenance" |
title |
string | required | Short title for the entry |
summary |
string | "" |
Optional description |
pages_touched |
list | [] |
Optional list of modified file paths |
source |
string | "" |
Optional source file path (for ingest) |
get_operation_log
Read recent entries from the operation log.
| Parameter | Type | Default | Description |
|---|---|---|---|
last_n |
int | 20 | Number of recent entries to return |
filter_type |
string | "" |
Filter by operation type (e.g. "ingest") |
Health
lint_vault
Comprehensive vault health check. Reports broken wikilinks (critical), orphan files (warning), missing frontmatter (warning), stale documents (info), stub documents (info), and isolated pages (info).
| Parameter | Type | Default | Description |
|---|---|---|---|
stale_days |
int | 90 | Flag files not modified within this many days |
Maintenance
reindex_vault
Re-indexes the vault into Qdrant. After upgrading, run with full=true to rebuild the index.
| Parameter | Type | Default | Description |
|---|---|---|---|
full |
bool | false | If true, drops and recreates the collection |
migrate_vault
Migrate an existing vault to the LLM Wiki pattern. Creates raw/ and wiki/ directories, adds missing frontmatter fields with sensible defaults, and initializes the operation log. Non-destructive and idempotent.
| Parameter | Type | Default | Description |
|---|---|---|---|
confirm |
bool | false | Set to true to apply (default returns preview) |
CLI Commands
MCP Server & Indexing
| Command | Description |
|---|---|
obsidian-qdrant-search |
Start the MCP server (stdio transport) |
vault-index [--full] |
Run indexing (incremental by default, --full drops and recreates) |
Multi-Agent CLI
All commands support --json for structured output that any agent can parse.
| Command | Description |
|---|---|
vault-search-search "<query>" [--project X] [--top-k 5] [--json] |
Semantic search |
vault-search-read <filepath> |
Read a vault file |
vault-search-write <filepath> --content "..." |
Create or update a file |
vault-search-lint [--stale-days 90] [--json] |
Vault health check |
vault-search-log <type> "<title>" [--summary "..."] [--source "..."] |
Log an operation |
vault-search-log --read [--last 20] [--filter <type>] [--json] |
Read log entries |
vault-search-map [--depth 3] [--json] |
Show vault structure |
vault-search-migrate [--apply] [--json] |
Migrate vault to LLM Wiki pattern |
These CLI commands make the vault accessible to any agent that can shell out (Codex, OpenCode, etc.), not just MCP-aware clients. See AGENTS.md for the full agent-oriented reference.
Architecture
Obsidian vault (.md files)
|
v
indexer โโ chunk by H2/H3 sections โโ> Qdrant (vector DB)
| preserves tables |
| preserves code blocks |
v v
fastembed (BAAI/bge-small-en-v1.5) server (MCP stdio)
local embeddings, 384 dim search / CRUD / reindex
Chunking strategy: Documents are split by ## headings, then ### if needed. Tables and fenced code blocks are never split mid-block. Large sections fall back to a block-aware sliding window with overlap.
Incremental indexing: Files are tracked by SHA-256 hash. Only changed files are re-embedded on reindex_vault(). Deleted files are automatically cleaned up.
Auto-reindex on write: When files are created, updated, or deleted via MCP tools, the search index is automatically updated for the affected file. This is best-effort โ if Qdrant is unavailable, the write still succeeds.
Project structure
obsidian-qdrant-search/
โโโ pyproject.toml
โโโ docker-compose.yml
โโโ CHANGELOG.md
โโโ README.md
โโโ CLAUDE.md # schema layer for Claude Code (auto-loaded)
โโโ AGENTS.md # schema layer for other agents (Codex, OpenCode)
โโโ .claude/
โ โโโ skills/
โ โ โโโ vault-search/SKILL.md # /vault-search slash command
โ โโโ agents/
โ โโโ doc-manager.md # documentation manager agent
โโโ tests/
โ โโโ test_path_utils.py
โ โโโ test_vault_ops.py
โ โโโ test_indexer.py
โ โโโ test_log.py
โ โโโ test_lint.py
โ โโโ test_migrate.py
โโโ src/
โโโ vault_search/
โโโ __init__.py
โโโ __main__.py # python -m vault_search
โโโ cli.py # CLI entry points (index + 7 commands)
โโโ config.py # env-based configuration
โโโ path_utils.py # path security & validation
โโโ vault_ops.py # CRUD, batch, log, lint operations
โโโ migrate.py # vault migration to LLM Wiki pattern
โโโ qdrant.py # auto-start Qdrant Docker container
โโโ indexer.py # markdown parsing, chunking, wikilinks, embedding
โโโ server.py # MCP server + 27 tools
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file obsidian_qdrant_search-0.4.1.tar.gz.
File metadata
- Download URL: obsidian_qdrant_search-0.4.1.tar.gz
- Upload date:
- Size: 882.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"25.10","id":"questing","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a30997e00b0d63c8b8546897c6b91285ad3dbc7260aa2695ad712e08b3c3dfa1
|
|
| MD5 |
e1fbaa09ee06deb89e5e242bf5955fdc
|
|
| BLAKE2b-256 |
9af1a396f6ff982044e93131cdb81b243b713f2ab6117f319b9e5a1a8ead0c38
|
File details
Details for the file obsidian_qdrant_search-0.4.1-py3-none-any.whl.
File metadata
- Download URL: obsidian_qdrant_search-0.4.1-py3-none-any.whl
- Upload date:
- Size: 39.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"25.10","id":"questing","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0377a0fb5c7e5873c80efb34e6cf449505583cd8e4828a34d6912c22a14b3ff8
|
|
| MD5 |
d83459184a03a0649bdae8b7f062aa0b
|
|
| BLAKE2b-256 |
f7d883564229aee69afa9882865d781878a1c84a34781a3f7c15a352b534c7be
|