Install
$ agentstack add mcp-pvliesdonk-markdown-vault-mcp ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ● Filesystem access Used
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
markdown-vault-mcp
[](https://github.com/pvliesdonk/markdown-vault-mcp/actions/workflows/ci.yml) [](https://codecov.io/gh/pvliesdonk/markdown-vault-mcp) [](https://pypi.org/project/markdown-vault-mcp/) [](https://pypi.org/project/markdown-vault-mcp/) [](LICENSE) [](https://github.com/pvliesdonk/markdown-vault-mcp/pkgs/container/markdown-vault-mcp) [](https://pvliesdonk.github.io/markdown-vault-mcp/) [](https://pvliesdonk.github.io/markdown-vault-mcp/latest/llms.txt) [](https://deepwiki.com/pvliesdonk/markdown-vault-mcp) [](https://github.com/pvliesdonk/fastmcp-server-template)
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, search returns query-relevant snippets in the content field by default (approximately 200 words). Pass snippet_words=0 to recover the prior full-chunk behaviour, or use read(path, section=heading) to fetch the full section after seeing a snippet. Documents are also re-chunked on next reindex to honour the adaptive MARKDOWN_VAULT_MCP_MAX_CHUNK_WORDS threshold (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
- 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 — 32 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. The [para-capture-chats](examples/para/prompts/para-capture-chats.md) prompt 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 `
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 HTTP
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](docs/deployment/docker.md#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-.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 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:
``bash 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:
``bash docker compose up -d ``
- Check the logs:
``bash 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](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 | — | No | Comma-separated frontmatter fields to promote to the tag index for structured filtering | | 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_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](#user-defined-prompts)) | | 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_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 reindex applies a new value. | | MARKDOWN_VAULT_MCP_MAX_CHUNK_CHARS | (derived from model context) | 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 → round(context_length × 2.8) (e.g. 8192-token model → 22938 chars; the default BAAI/bge-small-en-v1.5 model → ~1434 chars); unknown context → 6000. Set to override. A reindex applies a new value. | | 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))). |
> 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.5 for FastEmbed, nomic-embed-text for Ollama); long-context models — nomic-ai/nomic-embed-text-v1.5 (8192 tokens) for FastEmbed, or bge-m3:latest for Ollama — are opt-in and
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: pvliesdonk
- Source: pvliesdonk/markdown-vault-mcp
- License: MIT
- Homepage: https://pvliesdonk.github.io/markdown-vault-mcp/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.