About this project
# QMD - Query Markup Documents
QMD is an on-device search engine designed for personal knowledge bases, documentation, meeting notes, and any markdown-based content. It runs entirely locally, combining BM25 full-text search, vector semantic search, and LLM-based reranking to deliver high-quality results without sending data to external services.
## Core Features
- **Hybrid Search Pipeline**: Combines BM25 (FTS5), vector similarity search, and LLM reranking. Query expansion generates typed sub-queries (`lex` for keywords, `vec` for dense vectors, `hyde` for hypothetical document embeddings) that are routed to appropriate backends, fused via Reciprocal Rank Fusion (RRF), and reranked by an LLM.
- **Context Tree**: Add hierarchical context to collections (e.g., `qmd://notes` → "Personal notes and ideas") that is returned alongside matching documents, helping LLMs make better contextual decisions.
- **Local Models**: Uses GGUF models downloaded from HuggingFace and cached locally. Default embedding model is `embeddinggemma-300M-Q8_0` (~300MB). Custom models can be set via `QMD_EMBED_MODEL` environment variable (e.g., for multilingual corpora).
- **AST-Aware Chunking**: Optional tree-sitter-based chunking for code files (TypeScript, JavaScript, Python, Go, Rust) produces higher-quality chunks; other file types use regex-based chunking.
- **MCP Server**: Exposes a Model Context Protocol server with tools for querying, retrieving documents, batch retrieval, and status checks. Supports stdio and HTTP transports with security features (origin/host validation to prevent DNS rebinding attacks).
- **SDK**: Programmatic access via a TypeScript/JavaScript SDK with methods for search, document retrieval, context management, and query expansion.
## Quick Start
```sh
# Install globally (Node or Bun)
npm install -g @tobilu/qmd
# or
bun install -g @tobilu/qmd
# Create collections
qmd collection add ~/notes --name notes
qmd collection add ~/Documents/meetings --name meetings
# Add context
qmd context add qmd://notes "Personal notes and ideas"
# Generate embeddings
qmd embed
# Search
qmd search "project timeline" # Fast keyword search
qmd vsearch "how to deploy" # Semantic search
qmd query "quarterly planning process" # Hybrid + reranking (best quality)
```
## CLI Commands
- `qmd collection add <path> --name <name> [--mask <glob>]` — Add a collection
- `qmd collection show <name>` — Show collection details
- `qmd collection include/exclude <name>` — Toggle collection inclusion
- `qmd collection update-cmd <name> '<command>'` — Set update command
- `qmd embed [--chunk-strategy auto]` — Generate vector embeddings
- `qmd search <query> [-c <collection>] [--json] [--files] [--min-score <n>]` — Keyword search
- `qmd vsearch <query>` — Semantic search
- `qmd query <query>` — Hybrid search with reranking
- `qmd get <path|docid>` — Retrieve a document
- `qmd multi-get <glob>` — Retrieve multiple documents
- `qmd mcp [--http] [--port <n>] [--host <addr>] [--daemon]` — Start MCP server
- `qmd status` — Show index health and MCP status
## MCP Server
Exposed tools:
- `query` — Search with typed sub-queries, RRF fusion, and optional reranking
- `get` — Retrieve document by path, docid, or line range
- `multi_get` — Batch retrieve by glob, comma-separated list, or docids
- `status` — Index health and collection info
HTTP transport (default port 8181) provides:
- `POST /mcp` — MCP Streamable HTTP
- `POST /query` (alias `/search`) — Structured search without MCP protocol
- `GET /health` — Liveness check
Security: Requests with non-loopback `Origin` headers are rejected (403). `Host` validation prevents DNS rebinding. Environment variables `QMD_ALLOWED_ORIGINS` and `QMD_ALLOWED_HOSTS` can extend allowed origins/hosts.
## SDK Usage
```js
const { QmdStore } = require('@tobilu/qmd')
const store = new QmdStore({ dbPath: './qmd.db', collections: { notes: { path: '/path/to/notes' } } })
// Simple search (auto-expanded)
const results = await store.search({ query: 'authentication flow' })
// Structured query with typed sub-queries
const results2 = await store.search({
queries: [
{ type: 'vec', query: 'why do database connections time out under load' },
{ type: 'lex', query: 'connection timeout' }
],
collections: ['docs', 'notes']
})
// Disable reranking for speed
const fast = await store.search({ query: 'auth', rerank: false })
// Metadata filtering
const published = await store.search({
query: 'typescript',
filter: { key: 'topics', operator: 'all', value: ['typescript'] }
})
// Direct backend access
const bm25Results = await store.bm25Search('auth')
const vectorResults = await store.vectorSearch('auth')
// Query expansion
const expanded = await store.expandQuery('auth flow', { intent: 'user login' })
// Document retrieval
const doc = await store.get('docs/readme.md')
const body = await store.getDocumentBody('docs/readme.md', { maxLines: 100 })
// Context management
await store.addContext('docs', '/api', 'REST API reference documentation')
await store.removeContext('docs', '/api')
```
## Search Pipeline Details
1. **Query Expansion**: Original query (weighted ×2) + 1 LLM variation
2. **Parallel Retrieval**: Each query searches both FTS and vector indexes
3. **Top-Rank Bonus**: Documents ranking #1 in any list get +0.05, #2-3 get +0.02
4. **Top-K Selection**: Take top 30 candidates for reranking
5. **Re-ranking**: LLM scores each document (yes/no with logprobs confidence)
Score ranges: 0.0–0.2 low relevance, higher values indicate better matches.
## Model Configuration
Default models:
- Embedding: `embeddinggemma-300M-Q8_0` (~300MB)
- Reranking: LLM-based (downloaded on demand)
Custom embedding model example:
```sh
export QMD_EMBED_MODEL="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf"
```
Note: Changing embedding models requires re-embedding all collections since vectors are not cross-compatible.
## Requirements
- Node.js or Bun runtime
- Sufficient local storage for models and embeddings
- Optional: GPU/VRAM for faster LLM inference (models stay loaded in VRAM across requests)
## License
Open-source software. See repository for details.
Comments
0 Rating appears after 10 ratings
Sign in to join the discussion.