Provides hybrid search capabilities over local markdown knowledge bases, combining BM25 keyword search and vector semantic search with tools for querying, retrieving, indexing, and listing markdown files.
Integrates with OpenAI's text-embedding-3-small model via OpenRouter API to generate vector embeddings for semantic search capabilities.
Uses SQLite with FTS5 for keyword search indexing and BLOB storage for vector embeddings to enable persistent storage and querying of markdown content.
Click on "Install 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., "@QMD - Query Markdownsearch for how to set up API authentication"
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.
QMD - Query Markdown
A containerized MCP (Model Context Protocol) server that provides hybrid search over your local markdown knowledge base. Works with Claude Code, Claude Desktop, Cursor, and other MCP-compatible agents.
Features
Dual-mode communication: STDIO for local agents (Claude Code), HTTP/SSE for remote agents
Stateless STDIO architecture: Temporary containers (
docker run --rm) with persistent volume storageHybrid search: Combines BM25 keyword search and vector semantic search with RRF fusion
Vector embeddings: OpenRouter API for high-quality embeddings (text-embedding-3-small)
SQLite persistence: FTS5 for keyword search, BLOB storage for vectors in named Docker volume
Zero-config deployment: Automated setup script (
setup-qmd-mcp.sh) configures everythingNo long-running containers: For STDIO mode, containers auto-remove after each tool call
Deployment Modes
STDIO Mode (Claude Code, MCP Agents)
Architecture: Stateless temporary containers
Lifecycle: Container created per tool call, auto-removed after execution
Persistence: Named Docker volume (
qmd-cache) stores SQLite DB + embeddingsSetup: Run
./setup-qmd-mcp.shor manually configure~/.claude.jsonUse case: Local development with Claude Code or other MCP-compatible agents
HTTP Mode (Remote Agents, Web Services)
Architecture: Long-running persistent container
Lifecycle: Managed via
docker compose up/downPersistence: Host directory mount via
.envconfigurationSetup: Configure
.envand rundocker compose up -dUse case: Remote agents, web services, or when you need HTTP/SSE transport
Quick Start
STDIO Mode (Recommended for Claude Code)
The setup script will:
✓ Clean up old containers/volumes
✓ Configure
~/.claude.jsonwith correct MCP settings✓ Verify Docker image and notes path
✓ Test embeddings are enabled
See
HTTP Mode (For Remote Agents)
1. Configure Environment
.env
2. Build the Image
3. Run HTTP Server
4. Test STDIO Mode (Optional)
Test the MCP server directly before configuring Claude Code:
Expected output includes: "serverInfo":{"name":"qmd","version":"0.1.0"} and Embeddings: enabled
Claude Code Integration via STDIO
QMD uses a stateless container architecture for MCP/STDIO mode. Each tool call launches a fresh temporary container that executes and auto-removes (--rm). Persistence is achieved through a named Docker volume.
Deployment Architecture
Key Components:
Temporary Containers: Each MCP tool call = new container with
--rmflag (auto-cleanup)Named Volume (: Persists SQLite database and embeddings across all container runs
Read-Only Mount: Your markdown files mounted at
/app/kb:ro(read-only)Stateless Design: No long-running containers, all state in the persistent volume
Setup with Automated Script
Use the provided setup script for automatic configuration:
This will:
Clean up old containers and volumes
Update
~/.claude.jsonwith correct MCP configurationVerify Docker image and notes path
Test embeddings are enabled
After running, restart Claude Code to load the new configuration.
Manual Configuration
Edit ~/.claude.json and add the QMD MCP server:
Important:
Replace
sk-or-v1-your-key-herewith your OpenRouter API keyReplace
/path/to/your/markdown/noteswith your actual notes directoryThe API key must be in
argsvia-eflag (not a separateenvsection)After editing, restart Claude Code for changes to take effect
Volume Persistence
The qmd-cache named volume ensures your indexed data persists:
Data persists across:
Container restarts
Docker daemon restarts
System reboots
To start fresh:
MCP Tools
Tool | Description |
| Hybrid search combining BM25 keyword + vector semantic search |
| Vector-only semantic search for conceptual similarity |
| Trigger ingestion pipeline for new/modified files |
| Retrieve full content of a specific file |
| List all indexed files in the knowledge base |
Usage Examples
Ingestion: Index Your Knowledge Base
After adding or modifying markdown files, trigger the ingestion pipeline:
MCP Tool Call:
Response:
Force re-index all files:
Hybrid Search: Find Relevant Content
Combines keyword matching (BM25) with semantic similarity (vectors) using RRF fusion:
MCP Tool Call:
Response:
Semantic Search: Conceptual Similarity
Use vector-only search when looking for conceptually related content:
MCP Tool Call:
Response:
Retrieve Full Document
Get the complete content of a specific file:
MCP Tool Call:
Response:
List All Indexed Files
See what's in your knowledge base:
MCP Tool Call:
Response:
Real-World Workflow Examples
Example 1: Research a topic across your notes
Example 2: Cross-reference project documentation
Example 3: Find related content by concept
Volume Mappings
STDIO Mode (Claude Code)
Container Path | Purpose | Type | Example |
| Your markdown files | Host directory (ro) |
|
| SQLite DB + embeddings | Named volume (rw) |
|
Why named volume for cache?
Persists across all container runs
Survives system reboots
No filesystem permission issues
Fast I/O performance
Why read-only for markdown files?
Prevents accidental modifications
Security best practice
QMD only reads, never writes to
/app/kb
HTTP Mode (Docker Compose)
In HTTP mode, volumes are configured via .env file:
Mounting Multiple Folders (Advanced)
You can mount multiple directories into /app/kb:
All directories will be indexed and searchable together.
Instructing Agents to Use QMD via STDIO
For Claude Code
Once configured in ~/.claude.json, simply ask Claude naturally:
Claude Code will automatically invoke the appropriate MCP tools.
For Other MCP-Compatible Agents
Any agent supporting MCP over STDIO can use QMD. Configure the agent's MCP settings with:
Command: docker
Args:
Available Tools:
qmd_list- List indexed filesqmd_refresh_index- Index/re-index filesqmd_query- Hybrid search (BM25 + vector)qmd_vsearch- Vector-only semantic searchqmd_get- Retrieve full document content
Persistence Across Sessions
The qmd-cache named volume ensures:
Indexed documents persist between agent sessions
Embeddings are generated once, reused forever
No re-indexing needed unless files change
Fast search (no cold start)
First run:
Agent calls
qmd_refresh_index→ generates embeddings (~30 sec for 100 docs)Agent calls
qmd_query→ instant search results
Subsequent runs:
Agent calls
qmd_query→ instant results (no re-indexing)
Environment Variables
Variable | Default | Description |
| (required) | OpenRouter API key for embeddings |
|
| Embedding model to use |
|
| Transport mode: |
|
| HTTP server port |
|
| Knowledge base path inside container |
|
| Cache directory for SQLite DB |
|
| Tokens per chunk |
|
| Overlap tokens between chunks |
Docker Compose Configurations
Production (HTTP Mode)
Development (Hot Reload)
Custom Knowledge Base Path
Development
Local Development (without Docker)
Project Structure
Troubleshooting
STDIO Mode (Claude Code)
QMD tools not showing up in Claude Code
Check MCP configuration exists:
cat ~/.claude.json | jq '.mcpServers.qmd'Verify configuration has correct structure (type, command, args)
Restart Claude Code after any config changes
Embeddings not enabled
Check API key is in args (not env):
cat ~/.claude.json | jq '.mcpServers.qmd.args' | grep OPENROUTER_API_KEYVerify API key is valid:
curl https://openrouter.ai/api/v1/models \ -H "Authorization: Bearer sk-or-v1-your-key"Test container directly:
docker run --rm -e OPENROUTER_API_KEY="your-key" qmd:latest env | grep OPENROUTER
No files found / Empty knowledge base
Check notes path is correct in
~/.claude.jsonVerify path is accessible:
ls -la "/path/to/your/notes"Check files are visible in container:
docker run --rm -v "/path/to/notes:/app/kb:ro" qmd:latest ls -la /app/kb/
Index not persisting
Verify named volume exists:
docker volume ls | grep qmd-cacheCheck database exists in volume:
docker run --rm -v qmd-cache:/cache qmd:latest ls -lh /cache/Verify data in database:
docker run --rm -v qmd-cache:/cache qmd:latest \ sqlite3 /cache/qmd.db "SELECT COUNT(*) FROM documents;"
Containers left running
This shouldn't happen with --rm flag, but check:
HTTP Mode (Docker Compose)
Container won't start
Health check failing
General Issues
Docker image not found
Permission issues
Architecture
STDIO Mode (Claude Code)
HTTP Mode (Remote Agents)
For remote agents or when you need a persistent HTTP endpoint:
HTTP mode differences:
Long-running container (no
--rm)HTTP/SSE transport instead of STDIO
Managed via docker-compose
Volume mounts from
.envconfiguration
Cost Estimate (OpenRouter)
Item | Cost |
| ~$0.02 per 1M tokens |
Initial indexing (100 docs) | < $0.01 |
Per-query cost | ~$0.000002 (negligible) |
License
MIT