Skip to main content
Glama
jorgell23-sys

mdcx

mdcx

PyPI License DOI

将文档集合转换为经过验证的 Markdown,打包成单个加密文件,并通过模型上下文协议(Model Context Protocol)供代理查询。

问题

代理在回答关于文档集合的问题时有两种选择。它可以将文档放入上下文窗口,但这既昂贵又受窗口大小限制。或者,它可以查询一个已经知道每个条目位置的系统。

测量一个真实查询——最小管道直径在 3D 中如何表示——针对一个包含 99 份文档、180 MB 的真实集合,使用 cl100k_base 分词器:

模型 tokens

本地 tokens

读取原文

2,265,488

2,265,327

查询该包

435

2,688,861

这 435 个 tokens 包括 20 个用于问题、274 个用于检索到的段落、141 个用于答案。

第一行消耗了整个集合,原因很具体:PDF 无法被搜索,它是二进制文件,如果不事先转换,就无法知道 99 份文档中哪一份包含答案。它们全部必须被提取和阅读。

这是一个测量值,不是平均值:节省量取决于答案需要多少文本。不变的是工作的形态。工作并没有消失,它从上下文窗口——按量计费且有限——转移到了 CPU——不按量计费。这就是本地列上升而非下降的原因。

Related MCP server: md-mcp

三个步骤

转换。 每份文档被转换为 Markdown,并与原始文本进行核对。结构化引擎遗漏的内容会以原文附加,而不是报告为丢失。

在开发过程中使用的集合——99 份文档、1,144,553 个参考词——中,594 个词未被保留,全局覆盖率为 99.948%。在 184 份包含文本的文档中,116 份达到 100%,没有一份低于 99.5%。其余四份是仅包含扫描图像的文档,文件中没有文本:它们通过光学字符识别(OCR)读取,并标记为不可验证,因为没有可对照的原文。

打包。 语料库、其搜索索引以及每个段落的来源信息被打包成一个单独的 .mdcx 文件,使用 AES-256-GCM 加密,其头部无需密钥即可读取。8.8 MB 的 Markdown 压缩为一个 3.9 MB 的文件。

检索。 查询返回回答问题的段落及其精确来源。在开发过程中使用的 20 个真实查询中,正确答案在 19 次中出现在前五个结果中,在所有 20 次中出现在前十名中。

安装

该包将查询与转换分开,因为它们的依赖项非常不同。

命令

安装内容

大小

pip install mdcx

查询和读取 .mdcx 包

~10 MB

pip install "mdcx[mcp]"

上述内容加上 MCP 服务器

~50 MB

pip install "mdcx[convert]"

文档转换(Docling、PyTorch)

~1.4 GB

pip install "mdcx[all]"

全部内容,包括 OCR

~1.5 GB

转换功能引入了重型依赖。收到 .mdcx 文件且只需查询的人,不需要安装 Docling 或 PyTorch。

转换集合

pip install "mdcx[convert]"
mdcx-convert --input ./Documents --output ./Documents_md

输出镜像输入目录结构,添加全局索引,并记录每份文件相对于其原文的覆盖率。

打包与查询

mdcx pack --output ./Documents_md --target corpus.mdcx --key "..."
mdcx info corpus.mdcx
mdcx search corpus.mdcx "where is the minimum diameter stated" --key "..."
mdcx export corpus.mdcx --target ./restored --key "..."

info 无需密钥即可读取头部,因此可以在打开文件之前检查其签发者和完整性。export 从包中重建原始文件夹:一种无法离开的格式,即使意图良好,也可能成为陷阱。

用作 MCP 服务器

该服务器需要 Python 和此包。它不需要转换技术栈,因此占用空间约为 50 MB。

{
  "mcpServers": {
    "mdcx": {
      "command": "python",
      "args": ["-m", "mdcx.mcp_server"],
      "env": {
        "MDCX_FILE": "/path/to/corpus.mdcx",
        "MDCX_KEY": "package-key"
      }
    }
  }
}

或者,使用 uv 时,服务器无需事先安装即可运行,这是 Python MCP 服务器的常见安排:

{
  "mcpServers": {
    "mdcx": {
      "command": "uvx",
      "args": ["--from", "mdcx[mcp]", "python", "-m", "mdcx.mcp_server"],
      "env": {
        "MDCX_FILE": "/path/to/corpus.mdcx",
        "MDCX_KEY": "package-key"
      }
    }
  }
}

暴露了三个工具。search 返回回答问题的段落及其来源文档和可移植路径。info 描述语料库及其转换的保真度。document 在段落不够时返回完整文档。

服务器在开始监听之前验证包,因此错误的路径或密钥会立即报告,而不是在第一次查询时。

测试

pip install pytest
python -m pytest tests/ -v

测试套件涵盖恶意输入:空文件和损坏文件、其他字母系统的名称、格式错误的查询(包括 SQL 注入尝试)、截断和篡改的包,以及针对内容丢失的压缩测试。

路径

输出中不包含绝对路径。每份文档通过以 @/ 开头的伪路径标识,相对于包含它的文件夹或包进行解析,因此语料库无论存储在哪里——本地磁盘、网络共享或云端——都保持有效。

签名

包可以被签名,以便其签发者得到证明,而不仅仅是声明。签名覆盖加密主体的摘要,因此它同时证明来源和内容,并且无需加密密钥即可验证。

mdcx keygen
mdcx pack --output ./Documents_md --target corpus.mdcx --key "..." \
          --issuer "Acme Ltd" --signing-key <private-key>
mdcx verify corpus.mdcx --public-key <public-key>

验证还要求主体完整:仅覆盖记录摘要的签名将无法检测到内容被替换而头部未被触碰的情况。

签发者字段本身是自由文本,证明不了什么。只有签名可以。

加密

包在静态时加密,打开时在内存中解密;不会以明文写入磁盘。这保护了传输中的文件。这与在不解密的情况下搜索加密数据不同,后者是一个单独的研究领域,有已记录的泄漏攻击,且每次查询的成本以秒计。

密钥通过 scrypt 派生,这使得猜测变得缓慢:每秒约 8 次尝试,每次需要 32 MB 内存,从而防止了 GPU 上的并行化。即便如此,真正的强度在于口令:字典密码会在一天内被破解。

作者

由 Jorge Ellena G. 构思和指导,在 Claude(Anthropic)的协助下编程。

这个包中的每个决策都是基于测量而非惯例做出的:使用哪个转换引擎、哪个许可证允许什么、如何对搜索进行排名、接受哪些优化、拒绝哪些优化。有几个优化正是因为测量结果而被拒绝——减少搜索候选池看似快了十倍,但实际上将准确率从 19/20 降到了 17/20——这些测量与它们所决定的决策一起被记录在案。

引用

在 Zenodo 上以永久标识符存档。概念 DOI 始终解析到最新版本:

https://doi.org/10.5281/zenodo.22015991

许可证

Apache 2.0。本软件可以被使用、修改和销售,前提是保留版权声明。

PyMuPDF 被有意避免:其 AGPL 许可证将要求任何使用本软件的人以 AGPL 发布他们自己的软件,包括那些仅将其作为网络服务提供的人。

Related MCP Connectors

Related MCP Servers