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 |
| ๐ค | Agent Skills | Skill and agent for Claude Code |
| ๐ง | MCP Tools | All 23 tools โ Search, Read, Write, Discover, Graph, Batch, Maintenance |
| ๐ป | CLI Commands | Command-line interface |
| ๐๏ธ | 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
- 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 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
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 |
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) |
Maintenance
reindex_vault
Re-indexes the vault into Qdrant. After upgrading to v0.3.0, run with full=true to index wikilink data.
| Parameter | Type | Default | Description |
|---|---|---|---|
full |
bool | false | If true, drops and recreates the collection |
CLI Commands
| Command | Description |
|---|---|
obsidian-qdrant-search |
Start the MCP server (stdio transport) |
vault-index |
Run indexing from the command line |
vault-index --full |
Full reindex (drops existing data) |
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/
โ โโโ 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
โโโ src/
โโโ vault_search/
โโโ __init__.py
โโโ __main__.py # python -m vault_search
โโโ cli.py # vault-index CLI
โโโ config.py # env-based configuration
โโโ path_utils.py # path security & validation
โโโ vault_ops.py # CRUD & batch file operations
โโโ qdrant.py # auto-start Qdrant Docker container
โโโ indexer.py # markdown parsing, chunking, wikilinks, embedding
โโโ server.py # MCP server + 23 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.3.0.tar.gz.
File metadata
- Download URL: obsidian_qdrant_search-0.3.0.tar.gz
- Upload date:
- Size: 863.8 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 |
1f35857ed49dfe5427a7c57a72626b6939f6d01f2cede65cac99da808e373878
|
|
| MD5 |
9b1bfe72cc08c9ac2e2ec2887de1dd20
|
|
| BLAKE2b-256 |
7cd7737829c4685f58013229c908050bc38710c5732638fea88b55b0b09600e2
|
File details
Details for the file obsidian_qdrant_search-0.3.0-py3-none-any.whl.
File metadata
- Download URL: obsidian_qdrant_search-0.3.0-py3-none-any.whl
- Upload date:
- Size: 29.2 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 |
ad17ccf92cd59e8c179c0b81dc1c8c8bfa19daf92c6a1b093900a9f1e93a1f3b
|
|
| MD5 |
dfb47c344def0f8428736f27e4aac4f7
|
|
| BLAKE2b-256 |
c6828874be2b57d100cfaf772c5a51bfa6d2e004ad4f51942b87aad4a5e035c3
|