mdctx
A zero-ML-dependency keyword index MCP server for markdown context files, enabling fast search and retrieval of relevant files using RAKE and BM25, with a flat JSON index.
README
mdctx
A zero-ML-dependency keyword index for markdown context files. Built for
large workspaces where dumping every doc into an LLM's context window
wastes tokens. mdctx indexes your markdown once, then answers "which
files are relevant to X" in milliseconds, from a flat JSON file you can
commit and diff like code.
Why this exists
There are already several MCP servers that index markdown notes with
hybrid keyword+semantic search (qmd, dotmd, foam-notes-mcp, and others).
mdctx is deliberately narrower than those:
- No SQLite, no vector DB, no embedding model to download. The index is a single flat JSON file you can read, diff, and review in a PR.
- Keyword-only by design, using RAKE-style extraction and BM25 ranking. Fully deterministic, fully offline, no cost per index update.
- Cross-model on purpose. Ships as both a CLI (usable from any script or agent that can shell out) and an MCP server (usable by Claude, ChatGPT, Cursor, and anything else that speaks MCP).
If you need semantic/fuzzy matching across large free-text notes, one of the heavier tools above is likely a better fit. If you have a workspace of structured docs and want a fast, transparent, git-friendly index, that's what this is for.
Install
npm install -g mdctx
Or via Homebrew (not yet in homebrew-core, so tap this repo first):
brew tap zachkepe/mdctx
brew install mdctx
CLI usage
# Index every .md/.mdx file under a directory (incremental, only
# changed files are re-processed)
mdctx build ./docs
# Search the index
mdctx search "auth flow"
mdctx search "auth flow" --json --limit 3
MCP usage
Add to your MCP client config (e.g. Claude Desktop's
claude_desktop_config.json):
{
"mcpServers": {
"mdctx": {
"command": "mdctx-mcp",
"env": {
"MDCTX_ROOT": "/absolute/path/to/your/docs"
}
}
}
}
This exposes three tools: search_context, refresh_index, and
list_context. search_context auto-heals: if the index is missing,
corrupt, or was written by an older mdctx version, it gets rebuilt
transparently on the next call rather than erroring out.
Keeping the index up to date automatically
Run this once per repo:
mdctx init ./docs
This wires up everything needed so you never have to remember to run
mdctx build by hand:
.mdctx.json: records which directory holds your docs and where the index lives, so the hooks and CI job below don't need arguments.- A pre-commit hook: rebuilds the index and stages it before every
commit, so
context-index.jsonalways reflects what you're about to commit. If nothing changed, the rebuild is a no-op and nothing gets added to the commit. - post-merge / post-checkout hooks: rebuild the index after a pull or a branch switch, so local search results match whatever's on disk. These are advisory, if the rebuild fails for any reason they print a warning to stderr rather than blocking the merge or checkout.
.github/workflows/mdctx-index.yml: a CI check that rebuilds the index on every push/PR and fails the build if it doesn't match what's committed. This is the backstop for commits made with--no-verify, from a machine that never ranmdctx init, or by a bot.
Re-running mdctx init is safe. It won't overwrite a hook it didn't
install unless you pass --force, so it won't clobber pre-existing
hooks from another tool.
If you'd rather not touch git hooks, the CI workflow alone is enough to
catch staleness; just don't run mdctx init and use mdctx build
manually or in whatever pipeline already touches your docs.
How it works
-
mdctx buildwalks the target directory for.md/.mdxfiles, hashes each one, and skips any file whose hash hasn't changed since the last run. -
For new/changed files, it extracts a title (from frontmatter or the first heading) and a set of keywords using a RAKE-style scoring algorithm. No LLM call, no network request. The number of keywords per file is auto-sized to that file's word count (roughly one keyword per 30 words, clamped to 5-25) rather than a flat count for every file, so a two-paragraph doc doesn't get padded with low-signal phrases and a long doc doesn't lose relevant terms to an arbitrarily small cutoff. Pass
-k <n>tomdctx buildto pin a fixed count for every file instead. Because of the incremental hash cache, this only affects files that are new or whose content changed on that run. An already-indexed file keeps its existing keyword count until it's re-processed. Deletecontext-index.jsonand rebuild if you want to re-score everything under the new sizing immediately.Words that appear in a markdown heading or a bold/emphasis span get a score multiplier, on the reasoning that an author calling something out explicitly is a stronger relevance signal than RAKE's raw co-occurrence statistics. This matters most for short, specific terms (a product name, a technology) that would otherwise lose to longer, more interconnected prose phrases even in a long document. It's not a complete fix for every case:
*/_are also phrase delimiters (they have to be, so raw asterisks don't leak into extracted phrase text), so a single word wrapped in**word**gets isolated as its own one-word phrase before the boost applies, and a short isolated phrase can still lose to a longer unboosted one. Bolding a multi-word span doesn't have this problem, only single words do. -
Everything is written to
context-index.json, one JSON object per file, human-readable and git-diffable. A rebuild that finds no content changes writes back the exact same bytes (therootpath is stored relative to the index file, and thegeneratedAttimestamp only moves when something actually changed), so runningmdctx buildrepeatedly never produces spurious diffs. -
mdctx searchloads that JSON, builds a BM25 index in memory over each file's title+keywords, and returns ranked results.
Benchmark
To check whether keyword-based retrieval actually saves tokens in practice,
we ran a controlled comparison: two identical research agents given the same
task against the same real, mid-size production codebase (a security
software platform with a job queue, worker pool, and container-based task
execution, roughly 30 markdown docs plus a large multi-service backend).
Same prompt, same target repo, same task ("research how queuing and
concurrency work, identify the parallelization control points"). The only
variable was retrieval method: one agent ran mdctx search first and read
only what it surfaced; the other explored from scratch with grep/find/direct
reads, no index.
| Metric | mdctx | Baseline | Improvement |
|---|---|---|---|
| Tokens used | 68,748 | 86,314 | 20.3% fewer |
| Tool calls | 32 | 45 | 28.9% fewer |
| Wall-clock time | ~167s | ~206s | ~19% faster |
Both agents independently converged on the same core architectural
findings: the primary concurrency limiter, the split between the two
container-execution models, and the coupling between worker replica count
and the concurrency cap, using a fifth fewer tokens and nearly a third
fewer tool calls with mdctx. That's the expected result of pointing an
agent at the handful of files that actually matter instead of letting it
rediscover the codebase's shape from scratch: less redundant exploration,
the same answer, faster and cheaper. For deeper audits where exhaustive
coverage matters more than speed, pair mdctx search with a broader
follow-up pass over the next few ranked results rather than stopping at
the top hit. The index is there to narrow where you start, not to replace
judgment about how far to go.
Development
npm install
npm run build
npm test
License
MIT
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。