vault-search-mcp
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 |
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.