vault-search-mcp
Provides hybrid vector, text, and graph search over local Obsidian vaults, along with tools for navigating backlinks, tags, folders, and performing note CRUD operations with frontmatter validation.
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., "@vault-search-mcp find notes about MCP with related graph links"
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.
vault-search-mcp
Local hybrid search for Obsidian vaults and other Markdown knowledge bases. The server combines vector retrieval, full-text search, reranking, and graph relationships behind a single MCP interface while keeping the vault under the operator's control.
Project status: alpha. The MCP surface has contract tests, but it may change before version 1.0.
What makes it different
Capability | How it works | Practical result |
Hybrid retrieval | Vectors, FTS, and reranking share one derived index | Semantic matches do not erase names, acronyms, or rare exact terms |
Connected knowledge | Backlinks, tags, folders, and graph relationships are first-class MCP operations | Clients can search the vault and navigate its structure |
Local boundary | MCP uses | Notes and indexes stay on the machine in the default setup |
Rebuildable state | The vault is primary; LanceDB, the catalog, and caches are derived | A damaged index never becomes the only copy of a note |
Verifiable contracts | CI checks types, tests, packages, links, and the MCP registry | Documentation and code fail together when they drift |
Related MCP server: Obsidian MCP Assistant
Why this project exists
Exact-text search misses semantic relationships. Embedding-only search can miss names, acronyms, and uncommon terms. vault-search-mcp runs both retrieval paths and lets an MCP client select the right operation for each question.
It also treats a vault as a living knowledge base:
indexes Markdown, MDX, text, PDF, and Obsidian Canvas files;
follows filesystem changes without making the index authoritative;
navigates links, tags, folders, backlinks, and graph relationships;
creates and updates notes with optional frontmatter validation;
assigns UUID v7 identifiers during note creation and incremental reindexing;
optionally keeps models resident in a local daemon to avoid repeated loading.
Trust boundary
The default configuration is intended for one local operator.
The vault and derived indexes remain on the operator's machine.
The daemon binds to
127.0.0.1by default and rejects non-loopback hosts.External frontmatter enrichment starts disabled and requires explicit consent.
Retrieved notes may contain hostile instructions. MCP clients must treat note content as untrusted data, never as system instructions.
The project does not provide authentication, multi-tenant isolation, or quotas for public network exposure.
Read SECURITY.md and the threat model before using shared or untrusted sources.
Architecture in 30 seconds
flowchart LR
V[Local vault] --> P[Parsers and chunking]
P --> I[Indexer]
I --> L[(LanceDB)]
L --> S[Vector and text search]
D[Local model daemon] <--> S
S --> M[43 MCP tools and 6 resources]
M --> C[MCP client]
W[Filesystem watcher] --> IThe vector index and auxiliary catalog are rebuildable from the vault. Notes remain the primary source. See the architecture overview and decision records.
Requirements
Component | Support |
Python 3.14 or newer | Required |
Supported environment and package manager | |
macOS or Linux | Covered by the daemon installation scripts |
Tesseract | Optional; used only for OCR on scanned PDFs |
CPU | Reproducible backend selected by the lockfile |
CUDA or MPS | Used when the installed PyTorch distribution exposes the backend |
Windows does not yet have a daemon installer or CI coverage.
Quick start
1. Prepare the environment
Clone the repository and install the locked dependency set:
git clone https://github.com/everton-dgn/vault-search-mcp.git
cd vault-search-mcp
uv sync --locked
cp config.example.yaml config.yaml
uv run vault-search-configThe lockfile selects the CPU distribution of PyTorch to avoid downloading CUDA packages on machines without a compatible GPU. For CUDA, choose a compatible index using the official uv PyTorch guide and regenerate the lockfile. The default macOS distribution retains MPS support.
Edit paths.vault_path in config.yaml. Local configuration files are ignored
by Git.
paths:
vault_path: "vaults/obsidian_vault"
data_dir: "data"The vault may live outside the repository. An environment override is also supported:
export VAULT_SEARCH_VAULT_PATH="$PWD/vaults/obsidian_vault"2. Build the index
uv run python -m vault_search.core.indexerThe first run may download models. Transfer size and duration depend on the resolved versions, platform, and local caches.
3. Start the MCP server
uv run vault-search
# Equivalent module entry point:
uv run python -m vault_searchThe public transport is stdio. Configure the MCP client to execute the
command from the repository root. For clients that accept JSON:
{
"mcpServers": {
"vault-search": {
"command": "uv",
"args": ["run", "vault-search"]
}
}
}The client must launch the process with the repository as its working directory, or provide its equivalent working-directory option. See the installation guide for OCR, daemon setup, and environment verification.
MCP surface
The current registry contains 43 tools and 6 resources. CI derives those counts from the server decorators so the published catalog cannot silently drift from the code.
Group | Count | Examples |
Search | 7 |
|
Navigation | 10 |
|
Indexing | 6 |
|
CRUD and frontmatter | 13 |
|
Graph | 4 |
|
System | 3 |
|
Navigable resources
URI | Returns |
| Summarized index state |
| Folder tree |
| Snapshot of up to 5,000 notes with |
| Note content by relative path |
| Recently modified notes |
| Tag distribution |
The complete catalog groups every tool by domain and links to its detailed contract.
vault://notes has no cursor or offset. For catalogs larger than 5,000
entries, use list_notes and advance through its tool-level pagination.
Example prompts
After an MCP client registers the server, natural-language requests can select the appropriate tools:
Find notes related to eventual consistency and return the five most useful.
Search for "RFC 9562" in the architecture folder using hybrid retrieval.
List orphan notes and suggest possible connections without editing the vault.
Show files modified during the last seven days.The client should confirm write operations with the user. delete_note moves a
note into the vault's .trash directory.
Model execution modes
Mode | Best fit | Operational tradeoff |
MCP process | Development and occasional use | Models may reload between sessions |
Local daemon | Frequent use or several local clients | Models remain resident in memory |
Required daemon | Controlled operation without local fallback | Requests fail while the daemon is unavailable |
Install the daemon only after validating the local configuration:
# macOS
./scripts/install-daemon.sh
# Linux with user-level systemd
./scripts/install-daemon-linux.sh
curl --fail http://127.0.0.1:9847/healthFor a manual run without installing a service, use uv run vault-search-daemon
or uv run python -m vault_search daemon. Lifecycle and recoverable removal
are documented in the daemon guide.
Performance claims require evidence
This README intentionally publishes no context-free latency numbers. Hardware, vault size, chunk count, cache state, device, and model versions all affect the result.
Use the benchmark_search tool or the protocol in
docs/performance/benchmarking.md. A publishable report records:
project version and commit;
operating system, CPU, RAM, and device;
vault size, note count, and chunk count;
cold or warm model and index state;
sample count, median, and p95;
the command or tool used to reproduce the measurement.
Configuration contract
config.example.yaml is the canonical public reference. Configuration is
resolved in this order:
VAULT_SEARCH_CONFIG, when it points to an existing file;config.yamlin the working directory;config.ymlin the working directory;config.yamlorconfig.ymlin the installation root, when different;package-level Pydantic defaults.
Relative paths are resolved from the selected YAML file. Without a file, the defaults use the working directory.
The schema rejects unknown fields and contradictory combinations before
startup. FTS defaults to language-neutral tokenization for multilingual
vaults; language-specific stemming is opt-in. Metadata folders such as .git,
.obsidian, and .trash are ignored by default.
Operational environment overrides are listed in docs/config/variables.md. Restart the process after a configuration change because configuration is captured on first import.
Development
ShellCheck is required when daemon scripts change.
uv sync --locked
uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
bash -n scripts/*.sh && shellcheck scripts/*.sh
uv run mypy src/vault_search
uv run pytest -m "not slow" --cov=vault_search --cov-report=term \
--cov-fail-under=65
uv run python scripts/check_publication.py
uv build
uv run python scripts/check_publication.py --require-distRuff covers source, tests, and Python scripts. mypy checks the complete package. The coverage gate begins at 65%. The testing guide explains what each gate proves and what remains outside its scope.
The final publication check opens wheel and sdist archives without extracting them. It rejects local configuration, vault data, unsafe paths, secrets, and other private artifacts inside the packages.
Documentation map
Goal | Document |
Install and verify | |
Configure the service | |
Integrate an MCP tool | |
Understand the system | |
Operate the model daemon | |
Diagnose failures | |
Measure performance | |
Evaluate risk | |
Contribute |
The full index lives in docs/README.md.
Known limitations
The daemon's HTTP protocol is internal and must not be exposed to a network.
Remote daemon access is unsupported. TLS, authentication, quotas, and a dedicated threat analysis for that boundary are absent.
The server does not neutralize instructions embedded inside notes.
ML models and dependencies require meaningful disk and memory capacity.
The 0.x series does not promise stability for schemas, return values, or tool names between releases.
Compatibility documentation covers macOS and Linux. Other systems still need automated evidence.
Community and security
Read CONTRIBUTING.md before sending changes. Usage questions belong in SUPPORT.md or GitHub Discussions. Report vulnerabilities through the private channel in SECURITY.md. Never attach real vault content, credentials, or local machine paths.
License
Distributed under the MIT license.
Available Tools
43 toolsappend_noteA
Append content to an existing Markdown note.
Only .md is supported.
Inputs: path (required), content (required), separator. Returns: result
benchmark_searchA
Measure local search latency.
Results describe only this process, data set, configuration, and runtime.
Inputs: query, iterations. Returns: result
compact_indexA
Compact the LanceDB index.
This merges small fragments and removes obsolete versions after incremental mutations.
Returns: Compaction statistics.
Inputs: no named parameters. Returns: result
create_noteA
Create a Markdown note and fail if it already exists.
Only .md is supported.
Inputs: path (required), content (required), frontmatter. Returns: result
daily_noteB
Return information about the daily note for one date.
Daily notes use the Obsidian YYYY-MM-DD.md convention in the selected folder.
Inputs: date, folder. Returns: result
delete_noteA
Delete a .md, .pdf, or .canvas note by moving it to vault trash.
Permanent deletion is unsupported. Files remain recoverable in .trash.
Inputs: path (required). Returns: result
enrich_frontmatterA
Enqueue required-frontmatter enrichment in the background.
Returns a job id immediately. Use enrich_frontmatter_status to inspect it.
Inputs: path, limit, paths, folder. Returns: result
enrich_frontmatter_statusB
Return the status of frontmatter enrichment jobs.
Inputs: limit, job_id. Returns: result
find_bridge_notesB
Find articulation points in the note graph.
Removing an articulation point increases the number of connected components.
Inputs: limit, folder. Returns: result
find_broken_linksA
Find links that point to missing notes.
Broken links have is_resolved=false in the rebuildable link index.
Inputs: limit, folder. Returns: result
find_link_clustersA
Detect clusters of densely connected notes.
Uses connected components to find isolated knowledge groups.
Inputs: folder, min_cluster_size. Returns: result
find_orphan_notesB
Find notes with no backlinks.
These notes are isolated from incoming graph edges.
Inputs: limit, folder. Returns: result
find_similar_notesA
Find notes similar to one specific note.
Averages the note's chunk embeddings and searches for semantically similar content.
Inputs: path (required), top_k. Returns: result
folder_treeA
Return the vault folder structure as a hierarchy.
Uses the SQLite catalog without scanning the filesystem.
Inputs: max_depth, include_counts. Returns: result
generate_missing_idsA
Add UUIDv7 ids to Markdown notes that lack a frontmatter id.
UUIDv7 values follow RFC 9562 and are time-ordered.
Inputs: folder, dry_run. Returns: result
get_backlinksB
Find notes that link to one target note.
Uses the rebuildable link index instead of rereading vault files.
Inputs: path (required), include_context. Returns: result
get_note_metadataA
Return Markdown note metadata without the body.
Returns parsed frontmatter, extracted tags, and file metadata for .md.
Inputs: path (required). Returns: result
get_outlinksB
List every indexed link from one note.
Uses the same rebuildable link index as get_backlinks.
Inputs: path (required). Returns: result
get_recent_notesB
Return recently modified notes.
Results are ordered by modification time, newest first.
Inputs: days, limit, folder. Returns: result
graph_dataA
Export link-graph data for visualization.
The node-and-edge format can feed D3.js, Obsidian Graph, or Gephi.
Inputs: folder, include_orphans. Returns: result
health_checkA
Return a health snapshot for monitoring.
Checks the index, catalog, model state, and measured latency alerts.
Returns: Overall status and component details.
Inputs: no named parameters. Returns: object
link_statsB
Return vault link statistics.
Includes totals, most-referenced notes, and notes with most outlinks.
Inputs: limit. Returns: result
list_notesA
List vault notes with filters and pagination.
Lists .md, .pdf, and .canvas. Only .md can be read with read_note. For PDF and Canvas files, use search_vault.
Inputs: limit, folder, offset, extension. Returns: result
move_noteA
Move or rename a note.
Source and destination extensions must match, and ignored folders are blocked.
Inputs: to_path (required), from_path (required). Returns: result
random_noteA
Return a random vault note.
Uses SQLite's random ordering over the filtered catalog.
Inputs: folder, extension. Returns: result
read_noteA
Read a complete Markdown note with parsed frontmatter.
Only .md is supported. Use search_vault for PDF or Canvas content.
Inputs: path (required). Returns: result
reindex_noteA
Reindex one note incrementally.
Inputs: path (required). Returns: result
reindex_vaultA
Rebuild the complete vault index.
Use this after broad reorganizations or when the rebuildable index must be recreated.
Inputs: dry_run, require_daemon. Returns: result
search_advancedB
Run semantic search with structured filters.
Every optional filter is combined with AND.
Inputs: query (required), tags, top_k, folder, +10 more. Returns: result
search_by_folderA
Search semantically within one vault folder.
Descendant folders are included by the search backend.
Inputs: query (required), folder (required), top_k. Returns: result
search_by_tagsA
Find notes by exact tags without semantic search.
Use tag_stats to discover available tags before filtering.
Inputs: tags (required), limit, match_all. Returns: result
search_duplicatesC
Find groups of duplicate or highly similar notes.
Compares note embeddings and groups notes above the similarity threshold.
Inputs: folder, max_notes, threshold. Returns: result
search_vaultB
Search vault notes semantically with cross-encoder reranking.
Flow: query embedding, vector retrieval, then cross-encoder reranking.
Inputs: query (required), top_k. Returns: result
search_vault_hybridB
Combine semantic and keyword search.
This can recover exact technical terms, names, and acronyms alongside semantically related content.
Inputs: query (required), top_k. Returns: result
suggest_linksA
Suggest links for a note based on semantic similarity.
Finds similar notes that are not already linked.
Inputs: path (required), limit, min_similarity. Returns: result
sync_vaultA
Synchronize vault files with the index.
Detects new, modified, and deleted files.
Use this after files changed while the server was stopped.
Inputs: dry_run, require_daemon. Returns: object
system_statsA
Return measured operation and subsystem statistics.
Includes observed p50/p95 latency, cache counters, catalog totals, and vector-index statistics.
Inputs: reset. Returns: object
tag_statsA
Return vault tag usage statistics.
Each tag is counted once per note.
Inputs: limit, folder. Returns: result
update_frontmatterA
Update YAML frontmatter on an existing Markdown note.
Only .md is supported. Merge is shallow, so arrays and objects are replaced.
Inputs: path (required), metadata (required), merge. Returns: result
validate_frontmatterA
Validate note frontmatter or a supplied dictionary against the schema.
Use this before creating or updating notes.
Inputs: path, frontmatter. Returns: result
vault_statsA
Return search-index statistics.
Returns: Totals for chunks and notes, plus the last modification time.
Inputs: no named parameters. Returns: object
vector_index_statusA
Return ANN vector-index status.
The index is created when the configured chunk threshold is reached.
Returns: Current existence, threshold, chunk count, eligibility, and auto-creation setting.
Inputs: no named parameters. Returns: object
write_noteA
Overwrite or create a Markdown note from complete content.
Use this when the caller already has the complete .md content.
Inputs: path (required), content (required). Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 43 tools
Many tools have overlapping purposes, especially the search family: search_vault, search_vault_hybrid, search_advanced, search_by_folder, search_by_tags, and search_duplicates all blur together. Similar overlaps exist among stats/status tools and maintenance operations like sync_vault, reindex_vault, and compact_index, making it difficult to choose the correct tool.
Most tools follow a verb_noun snake_case pattern, but the verbs are inconsistent across similar actions (search_ vs. find_ vs. get_ vs. list_ vs. read_). A few names like graph_data and vector_index_status break the pattern, and the proliferation of search_* variants makes naming feel less systematic.
With 43 tools, this server is well beyond the ideal 3-15 range and even past the 25+ threshold. While the domain is broad, many tools are highly specialized or redundant, so the large count feels excessive rather than well-scoped.
The tool surface covers CRUD for notes, search, tags, folders, graph export, frontmatter validation/enrichment, and index maintenance. It is quite complete for an Obsidian vault server, though some redundancy suggests the same capabilities could have been consolidated without major gaps.
Maintenance
Related MCP Connectors
Token-efficient MCP memory for Markdown vaults. Tiered search, GraphRAG, AI memories.
Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.
Hosted MCP server for Obsidian Sync vaults: read, search, edit notes. Not affiliated with Obsidian.
Search and reason over your Obsidian-style Markdown vault, right from ChatGPT.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables natural language interaction with Obsidian vaults through an MCP server, providing hybrid search, file management, and AI-powered analysis.2-
- FlicenseNot gradedqualityCmaintenanceEnables AI-powered chat and file operations on Obsidian vaults with local indexing, hybrid search, and MCP access.-
- FlicenseNot gradedqualityDmaintenanceEnables semantic search and related-note discovery across an Obsidian vault via MCP, providing RAG context blocks for natural language queries.-
- AlicenseNot gradedqualityBmaintenanceEnables hybrid search, reading, writing, and managing a local Obsidian vault through MCP tools and a REST API, including section patching, appending, moving notes with link rewriting, and frontmatter updates.AGPL 3.0