markdown-vault-mcp
A generic markdown vault MCP server with FTS5 full-text search, semantic vector search, frontmatter-aware indexing, incremental reindexing, and non-markdown attachment support.
Documentation | Config wizard | PyPI | Docker
Point it at a directory of Markdown files (an Obsidian vault, a docs folder, a Zettelkasten, a PARA vault) and it exposes search, read, write, and edit tools over the Model Context Protocol.
Features
- Full-text search — SQLite FTS5 with BM25 scoring, porter stemming
- Semantic search — cosine similarity over embedding vectors (FastEmbed, Ollama, or OpenAI)
- Hybrid search — Reciprocal Rank Fusion combining FTS5 and vector results
- Diversity-aware ranking — each search result list caps a single document at 2 chunks (configurable), downweights chunks of long documents, and returns sentence-scale snippets — bounded LLM context cost per query, with full-section recovery via
read(path, section=heading) - Adaptive heading-level chunking — long sections are recursively re-split at deeper heading levels (H1 → H6) until each chunk fits a configurable word budget, improving retrieval precision on synthesising essays without manual restructuring
Upgrading. As of this release,
searchreturns query-relevant snippets in thecontentfield by default (approximately 200 words). Passsnippet_words=0to recover the prior full-chunk behaviour, or useread(path, section=heading)to fetch the full section after seeing a snippet. Documents are also re-chunked on nextreindexto honour the adaptiveMARKDOWN_VAULT_MCP_MAX_CHUNK_WORDSthreshold (default 400).
- Frontmatter-aware — indexes YAML frontmatter fields, supports required field enforcement
- Incremental reindexing — hash-based change detection, only re-processes modified files; an automatic boot-time reconciliation pass picks up changes made while no server was running, and the vector index converges to the reconciled chunk set (embedding exactly the delta)
- Write operations — create, edit, delete, rename documents, and move entire folder subtrees with automatic index updates
- Folder conventions: per-folder
_conventions.mdfiles carry your authoring rules, such as "reference notes stay self-contained"; the server surfaces them to LLM clients at write time via theget_conventionstool and inwrite/editresults, without interpreting them - Attachment support — read, write, delete, and list non-markdown files (PDFs, images, etc.)
- Git integration — optional auto-commit and push on every write via
GIT_ASKPASS - OIDC authentication — optional token-based auth for HTTP deployments (Authelia, Keycloak, etc.)
- MCP tools: 33 LLM-visible tools including search, read, write, edit, delete, rename,
move_folder, git history, manual git sync, one-time transfer links, and admin operations; plus 6 app-only tools for MCP Apps clients - MCP resources — 9 resources exposing vault configuration, statistics, tags, folders, document outlines, similar notes, recent notes, and an interactive SPA
- MCP prompts — 7 prompt templates including template-driven note creation
What you can do with it
With this server mounted in Claude, you can:
- Capture a URL as a note. "Fetch , summarize as a Resource note under
3-Resources/, and link any existing notes on the topic." — Claude composesfetch+search+write. - Research a topic into your vault. "Research product security regulations, compare them, and create a set of interlinked notes — one per regulation, plus a map-of-content." — Claude composes web-search tools (client-side) +
writewith wikilinks. See the Research workflows guide for the full loop. - Distill today's thinking. "Summarize today's conversations into Inbox notes." — Claude.ai only; uses
conversation_search+recent_chats+write. Thepara-capture-chatsprompt is the one-click version. - Find missing links. Fire the
propose-linksprompt from the+menu — it scans recently-modified notes, proposes meaningful connections, and writes them on confirmation. - Split or merge captures. "Split this Inbox note into two." / "Merge this into
<existing note>instead of duplicating." — Claude composesread+write+delete.
No external scheduler, no separate capture app — the vault sits behind your conversations and absorbs their output.
Installation
From PyPI
pip install markdown-vault-mcp
With optional dependencies:
pip install markdown-vault-mcp[mcp] # FastMCP server
pip install markdown-vault-mcp[embeddings-api] # Ollama/OpenAI embeddings via API
pip install markdown-vault-mcp[embeddings] # FastEmbed local embeddings
pip install markdown-vault-mcp[all] # MCP + FastEmbed + API embeddings
From source
git clone https://github.com/pvliesdonk/markdown-vault-mcp.git
cd markdown-vault-mcp
uv sync --all-extras --all-groups
Docker
docker pull ghcr.io/pvliesdonk/markdown-vault-mcp:latest
The Docker image uses [all] (MCP + FastEmbed + API embeddings). By default, semantic search works locally with FastEmbed and can switch to Ollama/OpenAI when configured. A compose.yml ships at the repo root as a starting point — copy .env.example to .env, edit, and docker compose up -d.
To attach a remote Python debugger (development only; the protocol is unauthenticated), see Remote debugging.
Linux packages (.deb / .rpm)
Download .deb or .rpm packages from the GitHub Releases page. Both install a hardened systemd unit; env configuration is sourced from /etc/markdown-vault-mcp/env (copy from the shipped /etc/markdown-vault-mcp/env.example). See the systemd deployment guide for details.
Claude Desktop (.mcpb bundle)
Download the .mcpb bundle from the GitHub Releases page. Double-click to install, or run:
mcpb install markdown-vault-mcp-<version>.mcpb
Claude Desktop opens a GUI wizard that prompts for required env vars — no manual JSON editing needed. See Step 0 of the Claude Desktop guide for details.
Claude Code plugin
/plugin marketplace add pvliesdonk/claude-plugins
/plugin install markdown-vault-mcp@pvliesdonk
Installs the MCP server and the vault-workflow skill. See the Claude Code plugin guide for details.
Quick Start
As a library
from pathlib import Path
from markdown_vault_mcp.vault import Vault
vault = Vault(source_dir=Path("/path/to/vault"))
vault.index.build_index()
results = vault.reader.search("query text", limit=10)
As an MCP server
export MARKDOWN_VAULT_MCP_SOURCE_DIR=/path/to/vault
markdown-vault-mcp serve
With Docker Compose
-
Copy an example env file:
cp examples/obsidian-readonly.env .env -
Edit
.envto setMARKDOWN_VAULT_MCP_SOURCE_DIRto the absolute path of your vault on the host. -
Start the service:
docker compose up -d -
Check the logs:
docker compose logs -f markdown-vault-mcp
Example env files
| File | Description |
|---|---|
examples/obsidian-readonly.env |
Obsidian vault, read-only, Ollama embeddings |
examples/obsidian-readwrite.env |
Obsidian vault, read-write with git auto-commit |
examples/obsidian-oidc.env |
Obsidian vault, read-only, OIDC authentication (Authelia) |
examples/ifcraftcorpus.env |
Strict frontmatter enforcement, read-only corpus |
For reverse proxy (Traefik) and deployment setup, see docs/deployment.md.
Server info
The server registers a built-in get_server_info tool (via fastmcp_pvl_core.register_server_info_tool) so operators can confirm the deployed version with a single MCP call. The response carries server_name, server_version, and core_version.
Configuration
All configuration is via environment variables with the MARKDOWN_VAULT_MCP_ prefix (except embedding provider settings, which use their own conventions).
- Configuration Generator: in-browser config / Docker / systemd builder
Core
| Variable | Default | Required | Description |
|---|---|---|---|
MARKDOWN_VAULT_MCP_SOURCE_DIR |
— | Yes | Path to the markdown vault directory |
MARKDOWN_VAULT_MCP_READ_ONLY |
true |
No | Set to false to enable write operations |
MARKDOWN_VAULT_MCP_INDEX_PATH |
in-memory | No | Path to the SQLite FTS5 index file; set for persistence across restarts |
MARKDOWN_VAULT_MCP_EMBEDDINGS_PATH |
disabled | No | Path to the numpy embeddings file; required to enable semantic search |
MARKDOWN_VAULT_MCP_STATE_PATH |
{SOURCE_DIR}/.markdown_vault_mcp/state.json |
No | Path to the change-tracking state file |
MARKDOWN_VAULT_MCP_INDEXED_FIELDS |
(none) | No | Comma-separated frontmatter fields to promote to the tag index for structured filtering. Changing it cold-rebuilds the index once on next startup (and, when SEARCHABLE_FIELDS is unset, also re-embeds the vault, since the searchable set inherits this value) |
MARKDOWN_VAULT_MCP_REQUIRED_FIELDS |
— | No | Comma-separated frontmatter fields required on every document; documents missing any are excluded from the index |
MARKDOWN_VAULT_MCP_EXCLUDE |
— | No | Comma-separated glob patterns to exclude from scanning (e.g. .obsidian/**,.trash/**) |
MARKDOWN_VAULT_MCP_TITLE_FIELD |
title |
No | Frontmatter field used as the document title (falls back to title, the first H1, then the filename). Changing it cold-rebuilds the index once on next startup |
MARKDOWN_VAULT_MCP_SEARCHABLE_FIELDS |
defaults to INDEXED_FIELDS |
No | Comma-separated frontmatter fields whose text values become keyword-searchable, and also enrich first-chunk embedding input (independent of EMBED_CONTEXT). Defaults to INDEXED_FIELDS when unset; set explicitly to diverge, or to none for no searchable fields at all. Setting or changing it cold-rebuilds the index and re-embeds the vault once on next startup |
MARKDOWN_VAULT_MCP_TEMPLATES_FOLDER |
_templates |
No | Relative folder path where note templates live (used by the create_from_template prompt) |
MARKDOWN_VAULT_MCP_PROMPTS_FOLDER |
— | No | Path to a directory of .md prompt files that extend or override built-in prompts (see User-defined prompts) |
MARKDOWN_VAULT_MCP_CONVENTIONS_FILE |
_conventions.md |
No | Filename of the per-folder conventions files surfaced to clients at write time (bare .md filename without glob characters; none disables). Convention files are excluded from the search index but stay readable. Existing notes matching the configured name are removed from the index on the next boot reconcile; set none to keep them indexed |
MARKDOWN_VAULT_MCP_DRAIN_TIMEOUT_S |
60 |
No | Maximum seconds an index-querying read tool waits for the IndexWriter to drain when called with wait_for_pending_writes=True. On timeout the tool answers from the current index rather than raising and reports index_stale=True in the response's _meta. |
MARKDOWN_VAULT_MCP_BUILD_TIMEOUT_S |
60 |
No | Maximum seconds a relational/FTS-backed tool or resource waits for the index to become queryable during a cold-start background build before raising IndexUnavailableError(reason="timeout"). Increase for very large vaults. |
Server identity
| Variable | Default | Description |
|---|---|---|
MARKDOWN_VAULT_MCP_SERVER_NAME |
markdown-vault-mcp |
MCP server name shown to clients; useful for multi-instance setups |
MARKDOWN_VAULT_MCP_INSTRUCTIONS |
(auto) | System-level instructions injected into LLM context; defaults to a description that reflects read-only vs read-write state |
MARKDOWN_VAULT_MCP_DISABLE_APPS_UI |
false |
Hide MCP-Apps UI tools (browse_vault, show_context) from the tool listing for clients that do not render MCP Apps panels (saves a few listing tokens) |
MARKDOWN_VAULT_MCP_HTTP_PATH |
/mcp |
HTTP endpoint path for streamable HTTP transport (used by serve --transport http) |
MARKDOWN_VAULT_MCP_KV_STORE_URL |
file:///data/state |
Unified key-value backend for HTTP session persistence (the events keyspace is namespaced inside the directory). file:///path (default) survives restarts; memory:// for dev (lost on restart). Preferred over EVENT_STORE_URL. |
MARKDOWN_VAULT_MCP_EVENT_STORE_URL |
(unset) | Legacy alias for KV_STORE_URL; honoured only when KV_STORE_URL is unset, and logs a one-shot deprecation warning. Prefer KV_STORE_URL. |
MARKDOWN_VAULT_MCP_APP_DOMAIN |
(auto) | Override the Claude app domain used for MCP Apps iframe sandboxing. Auto-computed from BASE_URL when not set. |
FASTMCP_LOG_LEVEL |
INFO |
Log level for FastMCP internals (DEBUG, INFO, WARNING, ERROR). App loggers default to INFO. -v overrides both to DEBUG. |
FASTMCP_ENABLE_RICH_LOGGING |
true |
Rich key=value text by default. Set to false for one-JSON-object-per-record output — recommended for production / log-aggregator deployments. |
Search and embeddings
| Variable | Default | Description |
|---|---|---|
MARKDOWN_VAULT_MCP_EMBEDDING_PROVIDER |
auto-detect | Embedding provider: openai, ollama, or fastembed |
OLLAMA_HOST |
http://localhost:11434 |
Ollama server URL (not MARKDOWN_VAULT_MCP_-prefixed) |
OPENAI_API_KEY |
— | OpenAI API key for the OpenAI embedding provider (not MARKDOWN_VAULT_MCP_-prefixed) |
MARKDOWN_VAULT_MCP_OPENAI_BASE_URL / OPENAI_BASE_URL |
https://api.openai.com/v1 |
OpenAI-compatible API base URL for embeddings |
MARKDOWN_VAULT_MCP_OPENAI_EMBEDDING_MODEL / OPENAI_EMBEDDING_MODEL |
text-embedding-3-small |
OpenAI-compatible embedding model name |
MARKDOWN_VAULT_MCP_OLLAMA_MODEL |
nomic-embed-text |
Ollama embedding model name |
MARKDOWN_VAULT_MCP_OLLAMA_CPU_ONLY |
false |
Force Ollama to use CPU only |
MARKDOWN_VAULT_MCP_FASTEMBED_MODEL |
BAAI/bge-small-en-v1.5 |
FastEmbed model name |
MARKDOWN_VAULT_MCP_FASTEMBED_CACHE_DIR |
FastEmbed default | FastEmbed model cache directory (in Docker, stored under /data/state/fastembed) |
MARKDOWN_VAULT_MCP_EMBED_CONTEXT |
false |
Enrich embedding input with the note title, chunk heading, and (first chunk) searchable-field values. Flipping it re-embeds the whole vault once on next startup |
MARKDOWN_VAULT_MCP_MAX_CHUNK_WORDS |
400 |
Word cap per chunk; the adaptive chunker splits at deeper heading levels, then paragraph/word boundaries, to respect it. Match it to the embedding model's context. A non-zero CHUNK_OVERLAP_WORDS can push an overlapped fragment past this cap. A reindex applies a new value. |
MARKDOWN_VAULT_MCP_MAX_CHUNK_CHARS |
(bounded default) | Character cap the chunker enforces alongside MAX_CHUNK_WORDS to bound token-dense chunks (CJK, code, tables) that fit the word cap yet exceed the model's token context. Unset → min(1500, round(context_length × 2.8)), so the default stays retrieval-sized and memory-safe regardless of model context. The default BAAI/bge-small-en-v1.5 (512 tokens) derives ~1434 chars, below the ceiling and unchanged; a longer-context model (context above ~536 tokens) is capped at 1500; an unknown context falls back to 1500. Set a positive value to force an exact cap. Set -1 to scale with the model's full context with no ceiling, or 1500 when the context is unknown, which can OOM the fastembed/ONNX path on a long-context model. A reindex applies a new value. |
MARKDOWN_VAULT_MCP_CHUNK_OVERLAP_WORDS |
40 |
Words of overlap between adjacent budget-split fragments of the same heading section (0 disables). Applies to new/re-indexed notes; run reindex to apply to an existing vault. |
MARKDOWN_VAULT_MCP_CHUNKS_PER_FILE |
2 |
Maximum chunks returned per document in search results. |
MARKDOWN_VAULT_MCP_SNIPPET_WORDS |
200 |
Width of the snippet window (words) in search results; 0 returns full chunk content. |
MARKDOWN_VAULT_MCP_LENGTH_DOWNWEIGHT_ALPHA |
0.25 |
Down-weights longer chunks in ranking (score / (1 + alpha · log(chunk_count))). |
MARKDOWN_VAULT_MCP_FOLDER_WEIGHTS |
none | Folder-prefix score multipliers (prefix:weight,..., weights > 0) applied to all search modes; the deepest matching prefix wins (sessions:0.5 demotes sessions/**). |
MARKDOWN_VAULT_MCP_FTS_WEIGHTS |
all 1.0 |
Per-column BM25 weights (column:weight,..., weights ≥ 0) for keyword ranking. Columns: path, title, folder, heading, content, summary. |
Note: the chunker's character cap (
MARKDOWN_VAULT_MCP_MAX_CHUNK_CHARS) is derived from the embedding model's context length, so changing the embedding model re-chunks the FTS index — not just the embeddings — and triggers an automatic cold rebuild of the index on the next startup. The defaults stay memory-light (BAAI/bge-small-en-v1.5for FastEmbed,nomic-embed-textfor Ollama); long-context models —nomic-ai/nomic-embed-text-v1.5(8192 tokens) for FastEmbed, orbge-m3:latestfor Ollama — are opt-in and need substantially more RAM/VRAM during indexing.
Git integration
Git integration has three modes:
- Managed mode (
MARKDOWN_VAULT_MCP_GIT_REPO_URLset): server owns repo setup. On startup it clones intoSOURCE_DIRwhen empty, or validates existingorigin. Pull loop + auto-commit + deferred push are enabled. - Unmanaged / commit-only mode (no
GIT_REPO_URL): writes are committed to a local git repo ifSOURCE_DIRis already a git checkout. No pull, no push. - No-git mode: if
SOURCE_DIRis not a git repo, git callbacks are no-ops.
When token auth is used (MARKDOWN_VAULT_MCP_GIT_TOKEN), remotes must be HTTPS.
SSH remotes (for example [email protected]:owner/repo.git) are rejected with a startup error.
Fix with: git -C /path/to/vault remote set-url origin https://github.com/owner/repo.git
Backward compatibility: MARKDOWN_VAULT_MCP_GIT_TOKEN without GIT_REPO_URL still works (legacy mode) but logs a deprecation warning.
| Variable | Default | Description |
|---|---|---|
MARKDOWN_VAULT_MCP_GIT_REPO_URL |
— | HTTPS remote URL for managed mode; enables clone/remote validation on startup |
MARKDOWN_VAULT_MCP_GIT_USERNAME |
x-access-token |
Username for HTTPS auth prompts (x-access-token for GitHub, oauth2 for GitLab, account name for Bitbucket) |
MARKDOWN_VAULT_MCP_GIT_TOKEN |
— | Token/password for HTTPS auth (GIT_ASKPASS) |
MARKDOWN_VAULT_MCP_GIT_PULL_INTERVAL_S |
600 |
Seconds between git fetch + ff-only update attempts; 0 disables periodic pull |
MARKDOWN_VAULT_MCP_GIT_PUSH_DELAY_S |
30 |
Seconds of write-idle time before pushing; 0 = push only on shutdown |
MARKDOWN_VAULT_MCP_GIT_COMMIT_NAME |
markdown-vault-mcp |
Git committer name for auto-commits; set this in Docker where git config user.name is empty |
MARKDOWN_VAULT_MCP_GIT_COMMIT_EMAIL |
noreply@markdown-vault-mcp |
Git committer email for auto-commits |
MARKDOWN_VAULT_MCP_GIT_COMMIT_NAME_CLAIM |
— | OIDC claim key to use as the commit author name (e.g. name); overrides GIT_COMMIT_NAME per-request when an OIDC token is present |
MARKDOWN_VAULT_MCP_GIT_COMMIT_EMAIL_CLAIM |
— | OIDC claim key to use as the commit author e-mail (e.g. email); overrides GIT_COMMIT_EMAIL per-request when an OIDC token is present |
MARKDOWN_VAULT_MCP_GIT_LFS |
true |
Enable Git LFS — runs git lfs pull on startup to fetch LFS-tracked attachments (PDFs, images). Set to false for repos without LFS. |
MARKDOWN_VAULT_MCP_GITHUB_WEBHOOK_SECRET |
— | Shared secret for GitHub push-event webhook; when set, mounts POST /github-webhook on HTTP/SSE transports to trigger immediate pull + reindex on push events |
File Watcher
| Variable | Default | Description |
|---|---|---|
MARKDOWN_VAULT_MCP_FILE_WATCHER |
true |
Enable filesystem-event watcher for external changes; auto-disabled when git pull or webhook is active |
MARKDOWN_VAULT_MCP_FILE_WATCHER_DEBOUNCE_S |
2.0 |
Seconds of quiet after the last event before triggering reindex |
MARKDOWN_VAULT_MCP_FILE_WATCHER_ROOT_FLOOR |
true |
Keep the non-recursive watch on the vault root; set false to register zero source-directory-rooted FSEvents streams (avoids repeated macOS access prompts on a $HOME-rooted vault), at the cost of root-level files relying on scans |
Requires the watchdog optional extra: pip install 'markdown-vault-mcp[file-watcher]'. Automatically disabled when GIT_PULL_INTERVAL_S > 0 or GITHUB_WEBHOOK_SECRET is set. The watcher scopes one recursive watch per non-excluded top-level directory (not a single recursive watch on the root), so excluded directories are never registered and content under a deliberately watched dot-directory delivers its own edits. See docs/configuration.md for details.
Attachments
Non-markdown file support. See Attachments for details.
| Variable | Default | Description |
|---|---|---|
MARKDOWN_VAULT_MCP_ATTACHMENT_EXTENSIONS |
(built-in list) | Comma-separated allowed extensions without dot (e.g. pdf,png,jpg); use * to allow all non-.md files |
MARKDOWN_VAULT_MCP_MAX_ATTACHMENT_SIZE_MB |
1.0 |
Maximum attachment size in MB returned by read() / accepted by write(); 0 disables the limit |
MARKDOWN_VAULT_MCP_MAX_NOTE_READ_BYTES |
262144 (256 KB) |
Maximum bytes returned by full-document read() for .md files; raises ValueError if exceeded. Use read(path, section=...) for partial reads. 0 disables the limit. |
Bearer token authentication
Simple static token auth for HTTP deployments. Set a single env var — clients must send Authorization: Bearer <token>.
| Variable | Required | Description |
|---|---|---|
MARKDOWN_VAULT_MCP_BEARER_TOKEN |
Yes | Static bearer token; any non-empty string enables auth |
OIDC authentication
Full OAuth 2.1 authentication for HTTP deployments. OIDC activates when all four required variables are set. See Authentication for setup details.
Multi-auth: If both
BEARER_TOKENand all OIDC variables are set, the server accepts either credential — a valid bearer token or a valid OIDC session. This is useful when different clients use different auth flows (e.g. Claude web via OIDC and Claude Code via bearer token).
| Variable | Required | Description |
|---|---|---|
MARKDOWN_VAULT_MCP_BASE_URL |
Yes | Public base URL of the server (e.g. https://mcp.example.com; include prefix if mounted under subpath, e.g. https://mcp.example.com/vault). Used for OIDC auth and to auto-compute the MCP Apps domain. |
MARKDOWN_VAULT_MCP_OIDC_CONFIG_URL |
Yes | OIDC discovery endpoint (e.g. https://auth.example.com/.well-known/openid-configuration) |
MARKDOWN_VAULT_MCP_OIDC_CLIENT_ID |
Yes | OIDC client ID registered with your provider |
MARKDOWN_VAULT_MCP_OIDC_CLIENT_SECRET |
Yes | OIDC client secret |
MARKDOWN_VAULT_MCP_OIDC_JWT_SIGNING_KEY |
No | JWT signing key; required on Linux/Docker — the default is ephemeral and invalidates tokens on restart. Generate with openssl rand -hex 32 |
MARKDOWN_VAULT_MCP_OIDC_AUDIENCE |
No | Expected JWT audience claim; leave unset if your provider does not set one |
MARKDOWN_VAULT_MCP_OIDC_REQUIRED_SCOPES |
No | Comma-separated required scopes; default openid |
MARKDOWN_VAULT_MCP_OIDC_VERIFY_ACCESS_TOKEN |
No | Set true to verify the upstream access token as a JWT instead of the id token. Only needed when your provider issues JWT access tokens and you require audience-claim validation on that token. Default: verify the id token (works with all providers, including opaque-token issuers like Authelia) |
CLI Reference
markdown-vault-mcp <command> [options]
serve
Start the MCP server.
markdown-vault-mcp serve [--transport {stdio|sse|http}] [--host HOST] [--port PORT] [--http-path PATH]
| Flag | Default | Description |
|---|---|---|
--transport |
stdio |
MCP transport: stdio (stdin/stdout, default), sse (Server-Sent Events), http (streamable-HTTP). Use http for Docker with a reverse proxy or when OIDC is enabled. |
--host |
127.0.0.1 |
Bind host for the http transport (ignored for stdio and sse); pass 0.0.0.0 to bind all interfaces inside Docker |
--port |
8000 |
Port for the http transport (ignored for stdio and sse) |
--http-path (alias --path) |
env MARKDOWN_VAULT_MCP_HTTP_PATH or /mcp |
MCP HTTP path for http transport; useful for reverse-proxy subpath mounting (e.g. /vault/mcp). The legacy --path spelling is still accepted. |
Reverse Proxy Subpath Mounts
By default, HTTP transport serves MCP on /mcp. You can run it under a subpath:
markdown-vault-mcp serve --transport http --http-path /vault/mcp
Equivalent env-based config:
MARKDOWN_VAULT_MCP_HTTP_PATH=/vault/mcp
For reverse proxies, you can either:
- Keep app path at
/mcpand use proxy rewrite/strip-prefix middleware. - Set app path directly to the public path (
/vault/mcp) and route without rewrite.
When OIDC is enabled under a subpath, the configuration is different: the subpath goes in BASE_URL only, and HTTP_PATH stays at /mcp. See OIDC subpath deployments.
Then your redirect URI is:
https://mcp.example.com/vault/auth/callback
index
Build the full-text search index.
markdown-vault-mcp index [--source-dir PATH] [--index-path PATH] [--force]
search
Search the vault from the CLI.
markdown-vault-mcp search <query> [-n LIMIT] [-m {keyword|semantic|hybrid}] [--folder PATH] [--json]
reindex
Incrementally reindex the vault (only processes changed files). When semantic search is configured, the vector index is converged to the updated chunk set — exactly the changed documents are re-embedded and orphaned vectors dropped, never the whole corpus.
markdown-vault-mcp reindex [--source-dir PATH] [--index-path PATH]
MCP Tools
| Tool | Description |
|---|---|
search |
Hybrid full-text + semantic search with optional frontmatter filters |
read |
Read a document or attachment by relative path |
write |
Create or overwrite a document or attachment |
edit |
Replace text in a document — exact match, line-range, or scoped match with normalized fallback |
delete |
Delete a document or attachment and its index entries |
rename |
Rename/move a document or attachment, updating all index entries; pass update_links=true to also rewrite backlinks in other notes |
move_folder |
Move an entire folder subtree to a new prefix, rewriting all vault links that point into the moved subtree in one call |
list_documents |
List indexed documents; pass include_attachments=true to also list non-markdown files |
list_folders |
List all folder paths in the vault |
list_tags |
List all unique frontmatter tag values |
reindex |
Force a full reindex of the vault |
stats |
Get vault statistics (document count, chunk count, link health metrics, etc.) |
build_embeddings |
Build or rebuild vector embeddings for semantic search |
embeddings_status |
Check embedding provider and index status |
get_index_status |
Check background FTS build state (queryable / building / failed) |
get_backlinks |
Find all documents that link to a given document |
get_outlinks |
Find all links from a document, with existence check |
get_broken_links |
Find all links pointing to non-existent documents |
get_similar |
Find semantically similar notes by document path |
get_toc |
Heading outline for a note or a folder subtree |
get_recent |
Get the most recently modified notes |
get_context |
Get a consolidated context dossier for a note (backlinks, outlinks, similar, folder peers, tags, modified time) |
get_orphan_notes |
Find all notes with no inbound or outbound links |
get_most_linked |
Find the most-linked-to notes ranked by backlink count |
get_connection_path |
Find the shortest path between two notes via BFS on the undirected link graph (max 10 hops) |
summarize |
Summarize a note, a set of notes, or a folder subtree with an LLM; the synthesis references the individual source notes by path. A slow summary is promoted to a background job (retrieved via get_summary) so the tool never hangs. Hidden unless an OpenAI-compatible backend is configured (OPENAI_API_KEY or a base URL). Sends note content to the model provider. |
get_summary |
Retrieve a summary that summarize promoted to a background job, by its job_id. Registered alongside summarize. |
get_history |
List commits that touched a note, attachment, or the whole vault (git-backed vaults only) |
get_diff |
Return a diff of a note or attachment between a reference commit/timestamp and HEAD; binary attachments return a --stat size summary instead of a unified patch (git-backed vaults only) |
git_sync |
Force an immediate git pull / push / both, bypassing the periodic loops. Returns structured state (SHAs, commit counts, Syncthing-style conflict file paths if any). Hidden when MARKDOWN_VAULT_MCP_GIT_REPO_URL isn't set or READ_ONLY=true. |
fetch |
Download a file from a URL and save it to the vault as a note or attachment (MCP-to-MCP transfer) |
create_download_link |
Mint a one-time capability URL to download a vault note or attachment (HTTP/SSE only; BASE_URL required) |
create_upload_link |
Mint a one-time capability URL to upload bytes to a fixed vault path (HTTP/SSE only; BASE_URL required; hidden when READ_ONLY=true) |
browse_vault |
Open the vault explorer SPA in a supporting MCP Apps client |
show_context |
Open the Context Card for a specific note in a supporting MCP Apps client |
Write tools (write, edit, delete, rename, move_folder, fetch, git_sync, create_upload_link) are only available when MARKDOWN_VAULT_MCP_READ_ONLY=false. git_sync additionally requires managed git mode (MARKDOWN_VAULT_MCP_GIT_REPO_URL set).
summarize is registered only when a summarization backend is configured: an OPENAI_API_KEY, or an OpenAI-compatible base URL for local endpoints that need no key. It needs the openai SDK (pip install 'markdown-vault-mcp[summarize]') and sends note content to the model provider. Any OpenAI-compatible endpoint works: OpenAI itself, a local Ollama (http://localhost:11434/v1, no key needed), the Anthropic compatibility endpoint (https://api.anthropic.com/v1), vLLM, and others. See Configuration for provider recipes.
browse_vault and show_context are LLM-visible in all clients; when called in an MCP Apps-capable client they open the interactive SPA. Six additional internal tools (vault_context, vault_list, vault_read, vault_search, vault_graph_neighborhood, vault_graph_hubs) use visibility="app" and are used by the SPA only — they are never visible to the LLM.
Resources
MCP resources expose vault metadata as structured JSON that clients can read directly without invoking tools.
| URI | Description |
|---|---|
config://vault |
Current vault configuration (source dir, indexed fields, read-only state, etc.) |
stats://vault |
Vault statistics (document count, chunk count, embedding count, etc.) |
tags://vault |
All frontmatter tag values grouped by indexed field |
tags://vault/{field} |
Tag values for a specific indexed frontmatter field (template) |
folders://vault |
All folder paths in the vault |
toc://vault/{path} |
Table of contents (heading outline) for a specific document (template) |
similar://vault/{path} |
Top 10 semantically similar notes for a document (template) |
recent://vault |
20 most recently modified notes with ISO timestamps |
ui://markdown_vault_mcp/app.html |
Interactive vault explorer SPA for MCP Apps clients |
Prompts
Prompt templates guide the LLM through multi-step workflows using the vault tools.
| Prompt | Parameters | Description |
|---|---|---|
summarize |
path |
Read a document and produce a structured summary with key themes and takeaways |
research |
topic |
Search for a topic, synthesize findings, and create a new note at research/{topic}.md |
discuss |
path |
Analyze a document and suggest improvements using edit (not write) |
create_from_template |
template_name (optional) |
Discover templates (if needed), read a template, gather user values, and write a new note |
related |
path |
Find related notes via search and suggest cross-references as markdown links |
compare |
path1, path2 |
Read two documents and produce a side-by-side comparison |
propose-links |
scope (optional), per_note_limit (optional) |
Scan a candidate set of notes (a folder, recent, or all), propose meaningful links between semantically-close notes that aren't already connected, and write them on confirmation |
Write prompts (research, discuss, create_from_template, propose-links) are only available when MARKDOWN_VAULT_MCP_READ_ONLY=false.
Templates are regular markdown files. If placeholder template text pollutes search results, add your templates folder to MARKDOWN_VAULT_MCP_EXCLUDE (for example _templates/**).
User-defined prompts
Mount a directory of .md prompt files to override or extend the built-in prompts. Set MARKDOWN_VAULT_MCP_PROMPTS_FOLDER to the path. Each file's frontmatter defines description, arguments (a list of objects, each with name, description, and required fields), and optional tags. A user prompt with the same name as a built-in replaces it.
For a complete example — including Zettelkasten capture, development, and review prompts — see the Zettelkasten guide. For an alternative action-oriented workflow — Projects, Areas, Resources, Archive with triage, kickoff, and weekly review prompts — see the PARA guide.
MCP Apps
The server ships four browser-based views that MCP clients supporting the MCP Apps protocol can render inline or in fullscreen. They are delivered as a single HTML resource at ui://markdown_vault_mcp/app.html and registered using visibility="app" so they appear only in supporting clients and do not clutter the standard tool list. See the MCP Apps guide for details.
| View | Description |
|---|---|
| Context Card | Displays a note dossier (backlinks, outlinks, similar notes, tags) for the note currently in focus |
| Graph Explorer | Interactive force-directed link graph of the vault, powered by vis-network |
| Vault Browser | Searchable, filterable file tree for navigating the vault without issuing tool calls |
| Note Preview | Full-width markdown preview with a Contents popover, collapsible frontmatter properties and tags, copy-markdown / copy-vault-link controls, and a "Send to Claude" button |
The two primary tools exposed to MCP Apps clients are:
| Tool | Description |
|---|---|
browse_vault |
Returns the vault tree structure for the Vault Browser view |
show_context |
Returns the full context dossier for a given note path (used by the Context Card view) |
Domain configuration: MCP Apps iframes are sandboxed to a specific Claude app domain. The domain is auto-computed from MARKDOWN_VAULT_MCP_BASE_URL. Override with MARKDOWN_VAULT_MCP_APP_DOMAIN if your deployment is hosted on a custom domain or behind a proxy that changes the apparent hostname.
Vendored dependencies (JavaScript libraries bundled at build time, no runtime CDN): vis-network (graph rendering), marked.js (markdown rendering), DOMPurify (XSS sanitization), ext-apps SDK (MCP Apps lifecycle). The one runtime network dependency is web fonts (Newsreader, Public Sans, IBM Plex Mono), loaded from Google Fonts with system-font fallbacks.
One-Time Transfer Links
create_download_link and create_upload_link mint short-lived capability URLs so vault files can move to a browser or another service without inflating th
No comments yet
Be the first to share your take.