关于 QMD MCP
QMD MCP 将 QMD 封装为长期运行的 Streamable HTTP 服务,为 MCP 客户端提供本地 Markdown 搜索、检索和有界索引维护,不开放任意 Shell 执行。
主要功能
- 01搜索并检索已索引文档
- 02启动有界异步重建任务
- 03执行嵌入任务并查看状态
- 04按计划刷新索引
- 05可选重排搜索结果
适用场景
- 01搜索本地 Markdown 文档集
- 02检索回答所需的源文档
- 03按计划刷新索引
MCP 相关
它通过 Streamable HTTP 向 MCP 客户端提供 QMD 搜索、检索、健康状态和有界管理工具。文档内容默认仅内部返回;向用户公开资源需要显式批准参数。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
docker run -i --rm ghcr.io/x1pher/qmd-mcp:v0.2.2本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
ghcr.io/x1pher/qmd-mcp:v0.2.2Streamable HTTP工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
QMD MCP
QMD MCP 将 QMD 打包为长期运行的 Streamable HTTP MCP 服务。它提供 QMD 搜索和文档检索,以及范围受限的索引维护操作,不会暴露任意 shell 执行功能。
这是由社区维护的集成项目,与上游 QMD 项目无关联,也未获得其认可或官方维护。
反馈与贡献
请使用 GitHub Issues 报告 Bug 和提出功能请求,并通过拉取请求提交拟议的更改。有关开发工作流、测试要求和编码规范,请参阅 CONTRIBUTING.md。安全问题必须按照 SECURITY.md 中的私密流程处理。
发布变更记录在 CHANGELOG.md 中。
快速开始
发布镜像托管在 GitHub Container Registry(GHCR)上:
ghcr.io/x1pher/qmd-mcp:v0.2.2
已发布的软件包为公开包,因此 Docker 拉取受认可的发布版本时无需登录 GitHub。
对于生产部署,请从受认可的 GitHub Release 中选择稳定版本标签。保留该标签解析后的摘要,作为不可变的来源证明和回滚依据。
该镜像支持 linux/amd64 和 linux/arm64。每个平台的镜像仅保留与其匹配的 QMD 原生 llama 运行时,以控制镜像大小。
1. 创建目录
mkdir -p qmd/config qmd/content
cd qmd
将要由 QMD 索引的 Markdown 文件放入 content/。
2. 创建 config/index.yml
global_context: >-
This is a local Markdown knowledge base. Search results are discovery evidence;
read the source document before relying on a material claim.
collections:
docs:
path: /vault
pattern: "**/*.md"
ignore:
- "notes/**"
notes:
path: /vault/notes
pattern: "**/*.md"
includeByDefault: false
history:
path: /vault/logs
pattern: "history.md"
includeByDefault: false
embedding: false
path 值指向容器内的路径。下方的 Compose 示例会将 ./content 挂载到 /vault。
embedding: false 是 QMD MCP 为应仅保留词法索引的集合提供的扩展。这些文件仍会被索引,并可通过显式词法(lex)搜索访问,但不会纳入嵌入状态检查,也不会参与手动 start_embed 任务。对于大型追加式日志或其他精确查找内容,可使用此设置;反复重建向量会增加成本,却无法带来有用的语义召回。
3. 创建 compose.yml
services:
qmd-mcp:
image: ghcr.io/x1pher/qmd-mcp:v0.2.2
container_name: qmd-mcp
environment:
QMD_FORCE_CPU: "1"
QMD_REFRESH_INTERVAL_MINUTES: "15"
QMD_REFRESH_INITIAL_DELAY_SECONDS: "120"
ports:
- "127.0.0.1:8181:8181"
volumes:
- ./content:/vault:ro
- ./config:/config:ro
- qmd-data:/data
healthcheck:
test:
- CMD
- node
- -e
- >-
fetch('http://127.0.0.1:8181/health')
.then(r=>process.exit(r.ok?0:1))
.catch(()=>process.exit(1))
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
restart: unless-stopped
volumes:
qmd-data:
此示例仅将 HTTP 端口绑定到环回地址。如果其他容器必须直接调用 QMD MCP,请将两个容器连接到同一个 Docker 网络,并使用 QMD 服务名称,而不要在主机上广泛开放端口。
QMD_FORCE_CPU=1 可确保仅使用 CPU 的部署具有可预测性。如果你明确希望 QMD 探测受支持的加速方式,请移除此变量或将其设为 0。
4. 启动容器
docker compose up -d
检查服务:
curl --fail http://127.0.0.1:8181/health
Streamable HTTP MCP 端点为:
http://127.0.0.1:8181/mcp
Docker CLI 替代方案
无需 Compose,也可以运行相同的发布版本:
docker volume create qmd-data
docker run -d \
--name qmd-mcp \
--restart unless-stopped \
-p 127.0.0.1:8181:8181 \
-e QMD_FORCE_CPU=1 \
-e QMD_REFRESH_INTERVAL_MINUTES=15 \
-e QMD_REFRESH_INITIAL_DELAY_SECONDS=120 \
-v "$PWD/content:/vault:ro" \
-v "$PWD/config:/config:ro" \
-v qmd-data:/data \
ghcr.io/x1pher/qmd-mcp:v0.2.2
QMD MCP 提供的功能
QMD MCP 保留了 QMD 的只读 MCP 工具,并增加了范围受限的管理操作:
health报告索引和运行时状态;start_update启动范围受限的异步文件系统重新索引任务;start_embed启动范围受限的异步嵌入任务;job_status报告近期管理任务;- 定时刷新只更新词法/索引状态;嵌入通过
start_embed显式运行,而embedding: false的集合仅使用词法索引; - 常规
query运行时会禁用重排序; query_reranked提供独立的、CPU 开销较大的重排序路径;- 配置
QMD_SOURCE_RELATIVE_ROOT后,如果源路径能够明确解析,查询结果可以包含精确的source_relative_path,用于权威的文件系统交接; - 默认情况下,文档检索会返回内部文本;只有同时设置
exposeToUser=true和confirmUserApprovedExposure=true,才能向用户公开 MCP 资源;预览/显示/打开/渲染/检查意图不代表已获批准。
一次只能运行一个管理任务。已完成的任务会保留在内存中,并采用有界历史记录。完整的九种工具参考(含访问级别和副作用)请参阅 docs/tools.md。
运行时路径
容器使用以下稳定路径:
| 路径 | 用途 |
|---|---|
/config/index.yml | QMD 集合配置 |
/data/index.sqlite | QMD 索引数据库 |
/data/home | 运行时主目录 |
/data/cache | 模型和运行时缓存 |
源集合通常应以只读方式挂载。/data 必须保持可写,因为其中包含可重建的索引以及模型/运行时缓存。
配置
Dockerfile 为常规运行时路径和 HTTP 监听器提供了可用的默认值。只覆盖部署所需的设置。
| 变量 | 默认值 | 用途 |
|---|---|---|
QMD_HTTP_HOST | 0.0.0.0 | 容器内的 HTTP 监听地址 |
QMD_HTTP_PORT | 8181 | HTTP 监听端口 |
QMD_CONFIG_PATH | /config/index.yml | QMD 集合配置文件 |
INDEX_PATH | /data/index.sqlite | QMD 索引数据库 |
QMD_SOURCE_RELATIVE_ROOT | 未设置 | 可选的公共源根目录。设置后,查询结果会包含相对于该根目录的精确、可防冲突的 source_relative_path 值。 |
QMD_DEFAULT_COLLECTION | 未设置 | start_embed 的默认集合;否则使用第一个已配置的集合 |
QMD_FORCE_CPU | 0 | 设为 1 以禁用加速探测并强制使用 CPU |
QMD_EMBED_PARALLELISM | 未设置 | 可选的 QMD 嵌入并行度覆盖值 |
QMD_EMBED_MAX_DOCS_PER_BATCH | 8 | 手动或定时嵌入批次中的默认最大文档数;可接受范围为 1-32 |
QMD_EMBED_MAX_BATCH_MB | 16 | 手动或定时嵌入批次的默认最大大小,单位为 MiB;可接受范围为 1-128 |
QMD_EMBED_MAX_DURATION_MS | 3600000 | 嵌入会话的最长时长;可接受范围为 60000-7200000 ms |
QMD_EMBED_INTERVAL_MINUTES | 0 | 自动嵌入检查间隔;0 会禁用所有嵌入定时器。整数范围为 0-1440 分钟 |
QMD_EMBED_INITIAL_DELAY_SECONDS | 120 | 首次自动嵌入检查的延迟;整数范围为 0-3600 秒 |
QMD_REFRESH_INTERVAL_MINUTES | 15 | 定时索引刷新的间隔;刷新绝不会启动嵌入。0 会禁用刷新,最大值为 1440 |
QMD_REFRESH_INITIAL_DELAY_SECONDS | 120 | 首次定时刷新前的延迟;可接受范围为 0-3600 |
无效的有界数值会在启动时导致失败,而不会被静默接受。QMD_SOURCE_RELATIVE_ROOT 绝不会公开绝对路径;只会返回相对源路径,对于存在歧义的规范化路径冲突则会返回 null,而不会猜测。
自动嵌入
默认情况下,自动嵌入处于禁用状态。将 QMD_EMBED_INTERVAL_MINUTES 设置为正数,即可在 QMD_EMBED_INITIAL_DELAY_SECONDS 指定的初始延迟后启用定期检查。
每次检查都会根据生效的集合 embedding 策略选择待处理工作。未设置 embedding 表示已启用;默认搜索选择和 QMD_DEFAULT_COLLECTION 不会限制调度器。一个 scheduled_embed 作业会在与手动作业和刷新相同的维护占用期间,依次处理符合条件的集合。在获准执行的检查中,第一个符合条件的集合会在进程内轮换,避免某个集合无限期占用每个共享时限。繁忙或有活动查询时,检查会跳过,不会排入工作队列。处理每个集合前都会重新检查策略和待处理工作。
计划嵌入可在协作式时限内恢复。未完成文档中已成功嵌入的分块会作为内部检查点保留,且仅适用于完全匹配的模型/指纹;在整个文档完成前,这些分块仍视为待处理,并会在下次计划尝试时跳过。未完成的检查点组不会出现在向量搜索结果中。手动嵌入会保留原有行为:中断时原子性清理未完成的文档。结果会保留各集合的处理结果和当前汇总待处理工作。协作式时限取消会报告为时限欠账,而非模型失败;真正的嵌入失败仍按原有方式处理。成功、无操作和跳过的检查不会输出日志;未完成的运行会记录简要摘要,不包含文档内容。
MCP health 和 HTTP /health 会公开 scheduledEmbedding 的启用状态、间隔、初始延迟、上次结果、下次计划检查时间以及复用的嵌入边界。状态仅限进程内有效,重启后会重置。下次检查时间是计划安排,并不保证作业一定在该时刻开始。scheduledRefresh.embeddingAutomatic: false 仅描述刷新:刷新仍然只执行更新。
调度器使用一个共享的协作式时间预算。可选的 AbortSignal 会传递至固定版本的 SDK、嵌入会话和原生循环。观察到中止信号后,不会再开始新的文档准备、重试或原生评估。已经开始的原生调用会等待其完成,包括并行工作线程。已完成的计划分块会保存为检查点,以便后续恢复,而非重新计算;只有在具备所有预期分块后,未完成的检查点组才会对搜索可见。关闭时会清除调度器定时器,并等待实际维护工作完成后再关闭存储。协作式时限无法硬性限制正在执行的原生调用所耗费的实际时间。手动嵌入仍保留其每集合会话时长,以及对未完成文档的原子性清理;未指定时长时,默认值为 3600000 ms。
每个索引应只由一个服务写入;其他副本或直接 CLI 写入不受进程内占用机制约束。批次字节数是打包目标,而非内存上限。部署时必须提供适当的 CPU/RAM/交换空间限制和嵌入并行度。查询延后机制会在准入时及集合之间生效;在处理集合期间到达的查询仍按现有行为处理。
安全模型
- 容器以 Node 上游镜像中权限受限的
node用户运行。 - 通常应以只读方式挂载源内容集合。
- 索引和缓存状态与源内容分开存放。
- 管理操作仅限于已公开的作业操作。封装程序直接调用 QMD 存储 API;不会调用 QMD CLI 更新钩子,也不会开放任意 Shell 执行。
- MCP 请求正文在解析 JSON 前会限制为 1 MiB。
- 错误消息会对已配置的索引路径和配置路径进行脱敏。
- MCP 传输层不提供身份验证。请将其限制在可信网络边界内,或置于经过身份验证的 MCP 网关之后。
- 生产部署应选择稳定版本标签,而非分支、
latest或其他会变动的标签;并保留解析后的摘要作为制品凭证。
漏洞报告和部署指南请参阅 SECURITY.md;项目采用的安全设计原则、常见弱点类别和审查要求请参阅 docs/SECURE-DEVELOPMENT.md。
上游关系
本仓库不是完整 QMD 源代码树的分支。它使用确切版本的 @tobilu/qmd 软件包,并在构建镜像时应用一小组失败时关闭的兼容性补丁。如果预期的上游补丁目标不再完全匹配,构建就会失败。
当前上游版本、补丁清单和更新流程请参阅 UPSTREAM.md。自动嵌入验收列出了行为和镜像检查。
验证
容器构建是主要的验证边界。它会安装锁定的依赖项、应用所有上游补丁、运行完整的单元测试和属性测试套件、执行 JavaScript 语法检查,并在运行时阶段之前移除仅供开发使用的依赖项。CI 还会启动镜像、初始化 MCP 协议、验证恰好包含九个工具的接口,针对临时 Markdown 内容集合执行一次真实索引更新,并验证最终文档数量。
依赖项和基础镜像更新由 Dependabot 提议。只有针对提议版本通过镜像构建和功能发布验收后,才会接受 QMD 更新。
发布
版本使用 SemVer 标签,例如 v0.2.0。发布必须指向 CI 全部通过的确切提交。由标签触发的 Release 工作流会:
- 验证标签与
package.json相符; - 构建
linux/amd64和linux/arm64镜像,并发布一个多架构标签; - 将其发布到 GHCR;
- 记录不可变的镜像摘要;
- 发布 SBOM/来源证明和 GitHub 证明;
- 创建对应的 GitHub Release。
常规 CI 不会发布镜像或版本。发布标签不可变,不会重复用于其他提交。
许可证
QMD MCP 的原创封装代码采用 MIT 许可证。QMD 和捆绑的依赖项保留各自的许可证。请参阅 LICENSE 和 UPSTREAM.md。
存储感知型维护准入
所有维护任务共用现有的单任务所有权机制,并会在启动前检查本地完整 I/O 和内存 PSI avg10。默认情况下,压力达到 5% 时延后任务,并在压力消退后等待五分钟;这是工作负载准入机制,不会更改告警阈值。读取/查询服务仍可用。计划中的嵌入任务会在各集合之间再次检查,并保留已提交的进度。刷新任务会让位于活跃查询。任务之间设有一分钟的静默期;未完成或失败的工作会按类型退避 30、60 分钟,之后最多退避 120 分钟,因此嵌入积压不会永远挤占常规刷新任务。重启时沿用现有的初始启动延迟。
health.storageAdmission 会公开最近一次决策和冷却时间。QMD_IO_PSI_FULL_AVG10_MAX 和 QMD_MEMORY_PSI_FULL_AVG10_MAX 用于配置有界整数阈值。PSI 缺失或格式错误时会延后任务,而不是默默接受工作。可选的 QMD_STORAGE_THROTTLE_FILE 是由部署方管理的只读原子 JSON 缓存:包含 schemaVersion:1、ISO8601 格式的 observedAt,以及非负数值 throttledIOs。任何正数节流值都会延后任务;若已配置该缓存,但缓存缺失、过期或格式错误,也会延后任务。默认最大数据时效为 10 分钟(QMD_STORAGE_THROTTLE_MAX_AGE_MS)。QMD 不会添加云凭据、API 客户端、网络请求或数据生成器;采样和传递由运维人员负责,并且在数据生成器通过验收前不得配置该缓存。未配置的云端覆盖情况会在运行状况中明确显示。常规的 15 分钟刷新和 30 分钟嵌入周期仍由部署方管理。
QMD_PRESSURE_ROOT 可指定一个由运维人员管理的绝对只读目录,其中包含 io 和 memory PSI 文件;默认值为 /proc/pressure。集成测试会在 /proc 之外挂载确定性的健康/退化状态测试夹具,同时保留 Docker 的 proc 安全隔离。生产环境使用真实的内核压力接口。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。