π Markdown Vault MCP Server
Headless semantic MCP server for Obsidian, Logseq, Dendron, Foam, and any folder of markdown files.
npm install and point it at a folder. Hybrid search, AST editing, zero-config embeddings. No app, no plugins, no API keys.
π‘ Why this server?
TL;DR β One
npxcommand. No running app. No plugins. No vector DB. Semantic search works out of the box.
| Differentiator | Details | |
|---|---|---|
| π« | No app or plugins required | Most Obsidian MCP servers (mcp-obsidian, obsidian-mcp-server) need Obsidian running with the Local REST API plugin. This server reads and writes .md files directly β point it at a folder and go. |
| π§ | Built-in semantic search, zero setup | Hybrid search: cosine-similarity vectors + TF-IDF + word proximity. Local embeddings (@huggingface/transformers, all-MiniLM-L6-v2, 384d) download on first run. No API keys, no external services. Ollama optional for higher quality. |
| π¬ | Surgical AST-based editing | remark AST pipeline patches specific headings or block IDs without touching the rest of the file. Freeform line-range & string replace as fallback. Levenshtein fuzzy matching handles LLM typos. |
| π | Tool-agnostic | Obsidian vaults, Logseq graphs, Dendron workspaces, Foam, or any plain folder of .md files. If it's markdown, it works. |
| π¦ | Single package, no infrastructure | Unlike Python alternatives that need ChromaDB or other vector stores, everything runs in one Node.js process. npx @wirux/mcp-markdown-vault and you're running. Docker image available. |
π Obsidian Β· π Logseq Β· π³ Dendron Β· π«§ Foam Β· π Any .md folder
β¨ Features
| Feature | Description | |
|---|---|---|
| ποΈ | Headless vault ops | Read, create, update, edit, delete .md notes with strict path traversal protection |
| π | Read by heading | Read a single section by heading title β returns only content under that heading (up to the next same-level heading), saving context window space |
| π¦ | Bulk read | Read multiple files and/or heading-scoped sections in a single call β reduces MCP round-trips with per-item fault tolerance |
| π¬ | Surgical editing | AST-based patching targets specific headings or block IDs β never overwrites the whole file |
| π | Fragment retrieval | Heading-aware chunking + TF-IDF + proximity scoring returns only relevant sections |
| π | Scoped search | Optional directory filter for global_search and semantic_search β restrict results to specific folders to reduce noise |
| π§ | Semantic search | Hybrid vector + lexical search with background auto-indexing |
| β‘ | Zero-setup embeddings | Built-in local embeddings via @huggingface/transformers β Ollama optional |
| π | Workflow tracking | Petri net state machine with contextual LLM hints |
| π | Dual transport | Stdio (single client) or SSE over HTTP (multi-client, Docker-friendly) |
| βοΈ | Freeform editing | Line-range replacement and string find/replace as AST fallback |
| π·οΈ | Frontmatter management | AST-based read and update of YAML frontmatter β safely manage tags, statuses, and metadata without corrupting file structure |
| π | Dry-run / diff preview | Preview any edit operation as a unified diff without saving β set dryRun=true on any edit action |
| π | Templating / scaffolding | Create new notes from template files with {{variable}} placeholder injection β refuses to overwrite existing files |
| πΊοΈ | Vault overview | Structural map of the vault β total file count, recursive folder tree with file counts and last modification dates per folder |
| π¦ | Batch edit | Apply multiple edit operations in a single call β sequential execution, stops on first error, supports dryRun, max 50 ops |
| π | Backlinks index | Find all notes linking to a given path β supports wikilinks and markdown links with line numbers and context snippets |
| π― | Typo resilience | Levenshtein-based fuzzy matching for edit operations |
π οΈ MCP Tools
| Tool | Actions | Description |
|---|---|---|
| π vault | list read create update delete stat create_from_template |
Full CRUD for vault notes + template scaffolding |
| βοΈ edit | append prepend replace line_replace string_replace frontmatter_set batch |
AST-based patching + freeform fallback + frontmatter update + batch edit (supports dryRun diff preview) |
| ποΈ view | search global_search semantic_search outline read frontmatter_get bulk_read backlinks |
Fragment retrieval, cross-vault search, hybrid semantic search, read by heading, frontmatter read, bulk read, backlinks |
| π workflow | status transition history reset |
Petri net state machine control |
| βοΈ system | status reindex overview |
Server health, indexing info, vault structure overview |
All tool responses include contextual hints based on the current workflow state.
π Quick Start
Prerequisites
π¦ Install from NPM
npm install -g @wirux/mcp-markdown-vault
Then run directly:
VAULT_PATH=/path/to/your/vault markdown-vault-mcp
π MCP Client Configuration
Add to your MCP client config (e.g. Claude Desktop, Claude Code):
{
"mcpServers": {
"markdown-vault": {
"command": "npx",
"args": ["-y", "@wirux/mcp-markdown-vault"],
"env": {
"VAULT_PATH": "/path/to/your/vault"
}
}
}
}
npx -yauto-installs the package if not already present β no global install needed.
Try it in the browser: You can test this server directly at β no local install required.
π³ Docker
Pull the pre-built multi-arch image from GitHub Container Registry:
docker pull ghcr.io/wirux/mcp-markdown-vault:latest
Or use Docker Compose:
docker compose up
Edit docker-compose.yml to point at your markdown vault directory. The default compose file uses SSE transport on port 3000.
π οΈ Development (from source)
git clone https://github.com/Wirux/mcp-obsidian.git
cd mcp-obsidian
npm install
npm run build
VAULT_PATH=/path/to/your/vault node dist/index.js
π Transport Modes
| Mode | Use case | How it works |
|---|---|---|
π‘ stdio (default) |
Single-client desktop apps (Claude Desktop) | Reads/writes stdin/stdout; 1:1 connection |
π sse |
Multi-client setups (Docker, Claude Code) | HTTP server with SSE streams; one connection per client |
SSE starts an HTTP server on PORT (default 3000):
GET /sseβ establishes an SSE stream (one per client)POST /messages?sessionId=...β receives JSON-RPC messages
MCP_TRANSPORT_TYPE=sse PORT=3000 VAULT_PATH=/path/to/vault npx @wirux/mcp-markdown-vault
Each SSE client gets its own workflow state. Shared resources (vault, vector index, embedder) are reused across all connections.
π§ Embedding Providers
The server selects an embedding provider automatically:
OLLAMA_URL set? |
Ollama reachable? | Provider used |
|---|---|---|
| β No | β | π Local (@huggingface/transformers, all-MiniLM-L6-v2, 384d) |
| β Yes | β Yes | π¦ Ollama (nomic-embed-text, 768d) |
| β Yes | β No | π Local (fallback with warning) |
No configuration needed for local embeddings β the model downloads on first use and is cached automatically.
βοΈ Configuration
| Variable | Default | Description |
|---|---|---|
VAULT_PATH |
/vault |
Markdown vault directory |
MCP_TRANSPORT_TYPE |
stdio |
stdio (single client) or sse (multi-client HTTP) |
PORT |
3000 |
HTTP port (SSE mode only) |
OLLAMA_URL |
(unset) | Set to enable Ollama embeddings |
OLLAMA_MODEL |
nomic-embed-text |
Ollama embedding model name |
OLLAMA_DIMENSIONS |
768 |
Ollama embedding vector dimensions |
ποΈ Architecture
Clean Architecture with strict layer separation:
src/
βββ domain/ π· Errors, interfaces (ports), value objects
βββ use-cases/ πΆ Business logic (AST, chunking, search, workflow)
βββ infrastructure/ π’ Adapters (file system, Ollama, vector store)
βββ presentation/ π£ MCP tool bindings, transport layer (stdio/SSE)
See CLAUDE.md for detailed architecture docs and CHANGELOG.md for implementation history.
π’ CI/CD & Release
Fully automated via GitHub Actions and Semantic Release:
| Workflow | Trigger | What it does |
|---|---|---|
| PR Check | Pull request to main |
Lint β Build β Test |
| Release | Push to main |
Lint β Test β Semantic Release (NPM + GitHub Release) β Docker build & push to ghcr.io |
- Versioning follows Conventional Commits β
feat:= minor,fix:= patch,feat!:/BREAKING CHANGE:= major - Docker images are built for
linux/amd64andlinux/arm64via QEMU - NPM package published as
@wirux/mcp-markdown-vault - Docker image available at
ghcr.io/wirux/mcp-markdown-vault
π§ͺ Testing
318 tests across 31 files, written test-first (TDD).
npm test # Run all tests
npx vitest run src/use-cases/ast-patcher.test.ts # Single file
npm run test:watch # Watch mode
npm run test:coverage # Coverage report
Tests use real temp directories for file system operations and in-memory MCP transport for integration tests. No external services required.
π Security
- π‘οΈ All file paths validated through
SafePathvalue object before any I/O - π« Blocks path traversal:
../, URL-encoded (%2e%2e), double-encoded (%252e), backslash, null bytes - βοΈ Atomic file writes (temp file + rename) prevent partial writes
- π€ Docker container runs as non-root user
π License
MIT