Baike.dev
All toolsTrendingOpen sourceNewsSubmit
Log in
< 返回工具列表
Q

qmd

> 编程语言
开源

mini cli search engine for your docs, knowledge bases, meeting notes, whatever. Tracking current sota approaches while being all local

28.5K stars0 点赞0 次浏览
访问官网GitHub

工具介绍

mini cli search engine for your docs, knowledge bases, meeting notes, whatever. Tracking current sota approaches while being all local

QMD - Query Markup Documents

An on-device search engine for everything you need to remember. Index your markdown notes, meeting transcripts, documentation, and knowledge bases. Search with keywords or natural language. Ideal for your agentic flows.

QMD combines BM25 full-text search, vector semantic search, and LLM re-ranking—all running locally via node-llama-cpp with GGUF models.

flowchart LR
  Q[User Query] --> X[Query Expansion]
  Q --> FTS[BM25 Search]
  Q --> VS[Vector Search]
  X --> HYDE[HyDE]
  X --> VEC[Vec dense sentences]
  X --> LEX[Lex BM25 keywords]
  HYDE --> VS
  VEC --> VS
  LEX --> FTS
  VS --> RRF[Reciprocal Rank Fusion]
  FTS --> RRF
  RRF --> RR[LLM Reranker]
  RR --> OUT[Final ranked results]

Typed expansions are routed exclusively: lex → BM25/FTS, vec and hyde → vector search. The original query is sent to both backends, then fused with RRF and reranked.

You can read more about QMD's progress in the CHANGELOG.

Quick Start

…

Using with AI Agents

QMD's --json and --files output formats are designed for agentic workflows:

# Get structured results for an LLM
qmd search "authentication" --json -n 10

# List all relevant files above a threshold
qmd query "error handling" --all --files --min-score 0.4

# Retrieve full document content
qmd get "docs/api-reference.md" --full

MCP Server

Although the tool works perfectly fine when you just tell your agent to use it on the command line, it also exposes an MCP (Model Context Protocol) server for tighter integration.

Tools exposed:

  • query — Search with typed sub-queries (lex/vec/hyde), combined via RRF + reranking
  • get — Retrieve a document by path or docid (with fuzzy matching suggestions)
  • multi_get — Batch retrieve by glob pattern, comma-separated list, or docids
  • status — Index health and collection info

Claude Desktop configuration (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "qmd": {
      "command": "qmd",
      "args": ["mcp"]
    }
  }
}

Claude Code — Install the plugin (recommended):

claude plugin marketplace add tobi/qmd
claude plugin install qmd@qmd

Or configure MCP manually in ~/.claude/settings.json:

{
  "mcpServers": {
    "qmd": {
      "command": "qmd",
      "args": ["mcp"]
    }
  }
}

HTTP Transport

By default, QMD's MCP server uses stdio (launched as a subprocess by each client). For a shared, long-lived server that avoids repeated model loading, use the HTTP transport:

# Foreground (Ctrl-C to stop)
qmd mcp --http                    # localhost:8181
qmd mcp --http --port 8080        # custom port
qmd mcp --http --host 0.0.0.0     # bind all interfaces (e.g. container probes)

# Background daemon
qmd mcp --http --daemon           # start, writes PID to ~/.cache/qmd/mcp.pid
qmd mcp stop                      # stop via PID file
qmd status                        # shows "MCP: running (PID ...)" when active

The server binds to localhost by default. Pass --host (or set the QMD_HOST environment variable) to override — --host 0.0.0.0 is useful when the server runs in a container and a liveness probe connects from a non-loopback address.

The HTTP server exposes two endpoints:

  • POST /mcp — MCP Streamable HTTP (JSON responses, stateless)
  • POST /query (alias /search) — structured search without the MCP protocol
  • GET /health — liveness check with uptime
Origin and Host validation

Every request is screened before routing: a request carrying an Origin header that does not name a loopback address is rejected with 403, as is a Host header naming something other than the address the server is bound to. This is what stops a web page you visit from reading your index through DNS rebinding — loopback binding alone does not, since the browser makes the request from your own machine.

Requests without an Origin header — curl, MCP clients, editors — are unaffected, which covers every normal local client.

Variable Effect QMD_ALLOWED_ORIGINS Comma-separated origins to accept in addition to loopback, e.g. https://notes.internal. Set to * to disable the check entirely. QMD_ALLOWED_HOSTS Comma-separated Host values to accept in addition to loopback and the bind address.

--host 0.0.0.0 cannot know which Host values are legitimate, so it skips the host check and warns at startup. Set QMD_ALLOWED_HOSTS to re-enable it, and remember the endpoints are unauthenticated — put your own auth in front of a server that is reachable off-host.

LLM models stay loaded in VRAM across requests. Embedding/reranking contexts are disposed after 5 min idle and transparently recreated on the next request (~1s penalty, models remain loaded).

Point any MCP client at http://localhost:8181/mcp to connect.

MCP Tool Parameters

Tool Parameter Type Notes query searches array Typed sub-queries (lex/vec/hyde), 1–10. Required. First gets 2x weight. query collections string[] Filter by collection names (OR). Array only — singular collection is silently ignored. query intent string Disambiguation context (does not search on its own) query limit number Max results (default 10) query minScore number Minimum relevance 0–1 (default 0) query candidateLimit number Max candidates to rerank (default 40) query rerank boolean Run LLM reranking (default true); set false for RRF-only get file string Path, docid (#abc123), or path:from:count (e.g. #abc123:120:40) get fromLine number Start line (1-indexed); overrides the :from suffix get maxLines number Limit returned lines get lineNumbers boolean Prefix lines with numbers (default true) multi_get pattern string Glob pattern or comma-separated list multi_get maxBytes number Skip files larger than N (default 10240) multi_get maxLines number Limit lines per file multi_get lineNumbers boolean Prefix lines with numbers (default true)

Unknown parameters are silently ignored (not rejected) — double-check names if results seem unscoped. The HTTP /query and /search endpoints return qmd://collection/path URIs in the file field, matching the CLI and MCP output.

SDK / Library Usage

Use QMD as a library in your own Node.js or Bun applications.

Installation

npm install @tobilu/qmd

Quick Start

import { createStore } from '@tobilu/qmd'

const store = await createStore({
  dbPath: './my-index.sqlite',
  config: {
    collections: {
      docs: { path: '/path/to/docs', pattern: '**/*.md' },
    },
  },
})

const results = await store.search({ query: "authentication flow" })
console.log(results.map(r => `${r.title} (${Math.round(r.score * 100)}%)`))

await store.close()

Store Creation

createStore() accepts three modes:

import { createStore } from '@tobilu/qmd'

// 1. Inline config — no files needed besides the DB
const store = await createStore({
  dbPath: './index.sqlite',
  config: {
    collections: {
      docs: { path: '/path/to/docs', pattern: '**/*.md' },
      notes: { path: '/path/to/notes' },
    },
  },
})

// 2. YAML config file — collections defined in a file
const store2 = await createStore({
  dbPath: './index.sqlite',
  configPath: './qmd.yml',
})

// 3. DB-only — reopen a previously configured store
const store3 = await createStore({ dbPath: './index.sqlite' })

Search

The unified search() method handles both simple queries and pre-expanded structured queries:

…

For direct backend access:

// BM25 keyword search (fast, no LLM)
const lexResults = await store.searchLex("auth middleware", { limit: 10 })

// Vector similarity search (embedding model, no reranking)
const vecResults = await store.searchVector("how users log in", { limit: 10 })

// Manual query expansion for full control
const expanded = await store.expandQuery("auth flow", { intent: "user login" })
const results4 = await store.search({ queries: expanded })

Retrieval

// Get a document by path or docid
const doc = await store.get("docs/readme.md")
const byId = await store.get("#abc123")

if (!("error" in doc)) {
  console.log(doc.title, doc.displayPath, doc.context)
}

// Get document body with line range
const body = await store.getDocumentBody("docs/readme.md", {
  fromLine: 50,
  maxLines: 100,
})

// Batch retrieve by glob or comma-separated list
const { docs, errors } = await store.multiGet("docs/**/*.md", {
  maxBytes: 20480,
})

Collections

// Add a collection
await store.addCollection("myapp", {
  path: "/src/myapp",
  pattern: "**/*.ts",
  ignore: ["node_modules/**", "*.test.ts"],
})

// List collections with document stats
const collections = await store.listCollections()
// => [{ name, pwd, glob_pattern, doc_count, active_count, last_modified, includeByDefault }]

// Get names of collections included in queries by default
const defaults = await store.getDefaultCollectionNames()

// Remove / rename
await store.removeCollection("myapp")
await store.renameCollection("old-name", "new-name")

Context

Context adds descriptive metadata that improves search relevance and is returned alongside results:

// Add context for a path within a collection
await store.addContext("docs", "/api", "REST API reference documentation")

// Set global context (applies to all collections)
await store.setGlobalContext("Internal engineering documentation")

// List all contexts
const contexts = await store.listContexts()
// => [{ collection, path, context }]

// Remove context
await store.removeContext("docs", "/api")
await store.setGlobalContext(undefined)  // clear global

Indexing

…

Types

Key types exported for SDK consumers:

…

Utility exports:

import {
  extractSnippet,              // Extract a relevant snippet from text
  addLineNumbers,              // Add line numbers to text
  DEFAULT_MULTI_GET_MAX_BYTES, // Default max file size for multiGet (64KB)
  Maintenance,                 // Database maintenance operations
} from '@tobilu/qmd'

Lifecycle

// Close the store — disposes LLM models and DB connection
await store.close()

The SDK requires explicit dbPath — no defaults are assumed. This makes it safe to embed in any application without side effects.

Architecture

…

Score Normalization & Fusion

Search Backends

Backend Raw Score Conversion Range FTS (BM25) SQLite FTS5 BM25 Math.abs(score) 0 to ~25+ Vector Cosine distance 1 / (1 + distance) 0.0 to 1.0 Reranker LLM 0-10 rating score / 10 0.0 to 1.0

Fusion Strategy

The query command uses Reciprocal Rank Fusion (RRF) with position-aware blending:

  1. Query Expansion: Original query (×2 for weighting) + 1 LLM variation
  2. Parallel Retrieval: Each query searches both FTS and vector indexes
  3. RRF Fusion: Combine all result lists using score = Σ(1/(k+rank+1)) where k=60
  4. Top-Rank Bonus: Documents ranking #1 in any list get +0.05, #2-3 get +0.02
  5. Top-K Selection: Take top 30 candidates for reranking
  6. Re-ranking: LLM scores each document (yes/no with logprobs confidence)
  7. Position-Aware Blending:
    • RRF rank 1-3: 75% retrieval, 25% reranker (preserves exact matches)
    • RRF rank 4-10: 60% retrieval, 40% reranker
    • RRF rank 11+: 40% retrieval, 60% reranker (trust reranker more)

Why this approach: Pure RRF can dilute exact matches when expanded queries don't match. The t

核心特点

  • •query — Search with typed sub-queries (lex/vec/hyde), combined via RRF + reranking
  • •get — Retrieve a document by path or docid (with fuzzy matching suggestions)
  • •multi_get — Batch retrieve by glob pattern, comma-separated list, or docids
  • •status — Index health and collection info
  • •POST /mcp — MCP Streamable HTTP (JSON responses, stateless)
  • •POST /query (alias /search) — structured search without the MCP protocol
  • •GET /health — liveness check with uptime
  • •RRF rank 1-3: 75% retrieval, 25% reranker (preserves exact matches)
  • •RRF rank 4-10: 60% retrieval, 40% reranker
  • •RRF rank 11+: 40% retrieval, 60% reranker (trust reranker more)

> 标签

TypeScript

暂无评论,来聊聊你的看法吧

> 工具信息

发布日期2026年8月1日
最后更新2026年9月9日
分类编程语言
定价开源

> 相关工具

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言
Baike.dev

baike.dev helps you discover great languages, frameworks, databases, DevOps and cloud-native tools.

Quick links

  • Home
  • All tools
  • Trending
  • Open source

About

  • About us
  • Community
  • News

Contribute

Found a great developer tool? Share it with the community.

Submit a tool
© 2026 baike.dev Developer EncyclopediaUpdated daily · Discover great developer tools