Skip to main content
Glama

双路抽取 MCP Server(dual-extract-mcp)

把「MinerU 文本/版面路 + Qwen3-VL 视觉路 + 双路融合裁决」封装成 MCP 工具集(8 个 Tool), 让 Claude Desktop / Cursor 等 MCP 客户端通过 stdio 调用,对本地 PDF 做结构化文档理解 (字段抽取 / 表格 / 公式 / 探查 / 校验)。完全本地、离线、零外部 API。 V2.0 起升级为形态 2「三源两段」:MinerU 结构主路 + OvisOCR2 按需校验 → 文本路内部融合 → 现有 Fusion Engine 二元契约零改动。

  • 状态:v2.0.1(2026-08-10):形态 2「三源两段」——MinerU 结构主路 + OvisOCR2 按需校验 → 文本路内部融合 → 现有 Fusion Engine 二元契约零改动;OvisOCR2 服务端 POST /parse_page 422 修复;D-2 真连实测 PASS(p213 95.71% 201/210、p8 95.45% 84/88,实质错值 0);674 回归全绿(+9 deselected)

  • 传输:stdio(本期不监听端口)

  • 语言:Python 3.11+(受管 3.13.14 实测可用)


1. 功能一览(8 个 Tool)

Tool

说明

里程碑

dual_extract

双路+融合主入口(字段抽取)

M3(T03)

parse_text

文本路单跑(V2.0 起可选 OvisOCR2 校验,触发规则引擎四信号按需调用)

M2(T02)

vision_extract

视觉路单跑(PDF 或独立图片,v1.3 起支持 image_path

M2(T02)

extract_table

表格(跨页 + 大表分层 + CSV/XLSX)

M4(T04)

extract_formulas

公式(LaTeX + PNG 渲染)

M5(T04)

inspect_document

文档探查(页数/表格/图片/公式统计)

M6(T05)

validate_output

输出结构校验(schema/manifest)

M6(T05)

health

MinerU + Ollama + OvisOCR2 三服务探活(OvisOCR2 可选)

M1

完整输入/输出契约见 docs/api.md


Related MCP server: mdify-mcp

2. 环境要求

  • Python 3.11+(Windows 优先,Linux 兼容)

  • 本地已部署:

    • MinerU 服务 http://127.0.0.1:8000/health 返回 {"status":"ok"}

    • Ollama http://127.0.0.1:11434(含 qwen3-vl:30b 视觉模型)

    • OvisOCR2(可选,V2.0 文本路校验增强):独立进程 http://127.0.0.1:8010scripts/ovisocr2_server.py + 独立 venv;默认 text_backend=mineru 不加载/ 不调用,DUAL_EXTRACT_TEXT_BACKEND=mineru+ovisocr2 显式开启)

    OvisOCR2 部署(独立 venv,不进主项目 pyproject 依赖;命令以 scripts/ovisocr2_server.py 模块 docstring 为权威,V2.0 真连实测验证)

    # 1. 建独立 venv(复用系统 CUDA 可选 --system-site-packages;用户本机标准命令)
    python -m venv .venv-ovisocr2
    .\.venv-ovisocr2\Scripts\Activate.ps1
    
    # 2. 安装依赖(版本对齐调研实测,与脚本 docstring 一致)
    pip install "transformers==5.14.1" "torch>=2.6.0,<3.0" `
        "fastapi>=0.110.0" "uvicorn>=0.29.0" "pillow>=10.0.0" "modelscope>=1.15.0"
    
    # 3. 启动(默认 127.0.0.1:8010;与 MinerU :8000 / Ollama :11434 错开)
    #    不传 --model 时默认从 ModelScope 下载 ATH-MaaS/OvisOCR2
    python scripts/ovisocr2_server.py
    
    #    本地模型快照加载(已下载过时用,免重复下载;快照含 model.safetensors)
    python scripts/ovisocr2_server.py --host 127.0.0.1 --port 8010 `
        --model "C:/Users/Administrator/.cache/modelscope/models/ATH-MaaS--OvisOCR2/snapshots/master"
    
    # 4. 健康检查(常驻显存 ~1.6GB;模型加载一次常驻,与 Qwen3-VL 串行不并发)
    curl http://127.0.0.1:8010/health
    # → {"status":"ok","model":...,"gpu_mem_mb":1626}

    ⚠️ transformers 5.x 坑位(脚本 docstring 权威,服务端已封装,用户无需传参): 显式 eos_token_id=[248046, 248044] 防死循环;temperature 参数在 Qwen3_5 架构 无效必须省略;do_sample=Falseimages_kwargs={"min_pixels":448*448,"max_pixels":2880*2880}enable_thinking=False

⚠️ 沙箱提示:本沙箱内 python -m venv 会静默失败,须用受管 Python 建 venv (C:\Users\Administrator\.workbuddy\binaries\python\versions\3.13.12\python.exe -m venv .venv)。 用户本机无此坑,按下方标准命令即可。


3. 安装

# 1. 建 venv(受管 Python 3.13.14;用户本机可直接 python -m venv .venv)
C:\Users\Administrator\.workbuddy\binaries\python\versions\3.13.12\python.exe -m venv .venv

# 2. 激活 + 安装(含测试依赖)
.\.venv\Scripts\Activate.ps1
pip install -e ".[test]"

# 3. 复制配置模板并按需修改(可选;不配则用默认值)
Copy-Item .env.example .env

4. 运行

# 开发期直跑(stdio 交互;起几秒确认无报错即可 Ctrl+C)
python -m dual_extract.server
# 或等价的 console script
mcp-server

接入 Claude Desktop(claude_desktop_config.json)

{
  "mcpServers": {
    "dual-extract": {
      "command": "D:/Dual-Extract-MCP/.venv/Scripts/python.exe",
      "args": ["D:/Dual-Extract-MCP/src/dual_extract/server.py"]
    }
  }
}

接入 Cursor

~/.cursor/mcp.json

{
  "mcpServers": {
    "dual-extract": {
      "command": "D:/Dual-Extract-MCP/.venv/Scripts/python.exe",
      "args": ["D:/Dual-Extract-MCP/src/dual_extract/server.py"]
    }
  }
}

B1:MCP 客户端 CWD 不可控,建议在 .env 中把 DUAL_EXTRACT_OUTPUT_DIR 配成绝对路径


5. 测试

# 单元测试(默认跳过 smoke)
pytest -q

# 冒烟:真连本机 MinerU/Ollama(需服务在线)
pytest -m smoke -q

当前回归:674 passed + 9 deselected(2026-08-10 v2.0.1 全量回归核实,含新增 OvisOCR2 相关测试)。 历史演进:v0.2.1 353 passed → V1.0 406 passed → V1.3.0 615 passed → V2.0.1 674 passed


5.1 V1.0 增量说明(2026-08-03)

V1.0 在 v0.2.1 基础上新增 5 项可选/默认关能力(全部 additive,当时 schema_version"1.0";当前统一 "1.1",validate_output 兼容 1.0/1.1 白名单双值,旧客户端零改动):

增量

落点

说明

meta.generation

extraction.jsonmeta.generation

生成环境信息:{generator, version, mineru_version, vlm, vlm_backend}(PRD §5.1)

artifact_id

extraction.json 顶层 + document.artifact_id

产物唯一身份:{pdf_stem}_{YYYYMMDD}_{seq:03d}(同 run 幂等复用)

fields[].field_id

extraction.jsonfields[] / aggregated_pages[]

字段审计 ID:{artifact_id}.field.{key_snake}.p{page}.{seq:03d}

quality_report.json

抽取类 Tool job 结束自动落盘({产物目录}/quality_report.json

质量报告:17 顶层键,overall 自动三态判定(达标/有条件达标/不达标)

tables[].cell_provenance

extract_table 输出(默认关DUAL_EXTRACT_CELL_PROVENANCE=true 开启)

单元格溯源五字段 {row, col, value, page, bbox};chunked 大表 >500 行只记录前 500 行 + warning

挂接约定(artifact_manifest,V1.0 P2):产物唯一身份由 artifact_id + manifest.json 的 SHA256 + 相对路径 三元组确定——artifact_id 标识「哪次抽取」,SHA256 保证「内容未被篡改」,相对路径定位「文件在哪」。详见 docs/最终输出说明.md §4 / docs/api.md §2.4。


6. 配置项(全部前缀 DUAL_EXTRACT_,仅 config.py 读取)

默认值

说明

DUAL_EXTRACT_MINERU_BASE_URL

http://127.0.0.1:8000

MinerU HTTP 端点

DUAL_EXTRACT_MINERU_TIMEOUT

120

MinerU 请求超时(秒)

DUAL_EXTRACT_OLLAMA_BASE_URL

http://127.0.0.1:11434

Ollama 端点

DUAL_EXTRACT_OLLAMA_MODEL

qwen3-vl:30b

视觉模型

DUAL_EXTRACT_OLLAMA_TIMEOUT

600

Ollama 超时(秒;冷启动模型加载可达)

DUAL_EXTRACT_OLLAMA_THINK

false

视觉路 thinking 开关(G2,默认关)

DUAL_EXTRACT_TEXT_BACKEND

mineru

文本路后端:mineru | mineru+ovisocr2(V2.0;默认 mineru 零加载/零调用)

DUAL_EXTRACT_OVISOCR2_BASE_URL

http://127.0.0.1:8010

OvisOCR2 独立进程 HTTP 端点(V2.0)

DUAL_EXTRACT_OVISOCR2_MODEL

ATH-MaaS/OvisOCR2

OvisOCR2 模型(ModelScope 仓库 id,V2.0)

DUAL_EXTRACT_OVISOCR2_PAGE_TIMEOUT

180

OvisOCR2 整页超时红线(秒;实测 96s × ~1.9 余量)

DUAL_EXTRACT_OVISOCR2_TABLE_TIMEOUT

90

OvisOCR2 单表超时(秒;拆表兜底 P2 预留)

DUAL_EXTRACT_OVISOCR2_CONF_THRESHOLD

0.80

表格低置信触发阈值(confidence < 此值 → 触发 OvisOCR2 校验)

DUAL_EXTRACT_OVISOCR2_TRUNC_RATIO

0.90

截断兜底比例(token_count ≥ 上限 × 此值 → 判截断降级用 MinerU)

DUAL_EXTRACT_OVISOCR2_MAX_NEW_TOKENS

12288

服务端生成上限(< 16384 官方上限,防死循环)

DUAL_EXTRACT_OUTPUT_DIR

./outputs

输出根目录(建议绝对路径)

DUAL_EXTRACT_OUTPUT_VERSIONED

false

true 时输出加时间戳子目录

DUAL_EXTRACT_CONFIDENCE_THRESHOLD

0.7

低置信阈值

DUAL_EXTRACT_PARALLEL

false

页并行(默认串行防 3090 OOM)

DUAL_EXTRACT_MAX_FILE_MB

500

输入 PDF 大小上限

DUAL_EXTRACT_MAX_PAGES

1000

页数上限

DUAL_EXTRACT_IMAGE_DPI

200

页图/图片 DPI

DUAL_EXTRACT_CROP_FIGURES

false

图表/印章裁剪开关

DUAL_EXTRACT_SINGLE_PATH_DEGRADE

true

单路降级开关

DUAL_EXTRACT_CELL_PROVENANCE

false

表格单元格溯源(V1.0;开启后 tables[].cell_provenance 记录 row/col/value/page/bbox,chunked 大表自动降级)

DUAL_EXTRACT_MODEL_JUDGE

false

模型裁判(默认关,v0.4 前不实现)

DUAL_EXTRACT_LOG_LEVEL

INFO

日志级别

DUAL_EXTRACT_TEMP_DIR

./tmp

临时目录(退出清理)

DUAL_EXTRACT_CSV_SPLIT_ROWS

500

CSV 分片行数


7. 真实样例用法

# 1. 探查:先「ls」一份 PDF 再决定抽取策略
#    inspect_document 返回 {pages, tables, images, formulas, text_chars}
#    样例:232 页财报 → 421 图 / 213,232 字符(MinerU 不可达时自动降级 PyMuPDF)

# 2. 双路字段抽取
#    dual_extract(pdf_path="D:/samples/中芯国际_年报.pdf",
#                 fields=["公司代码","公司简称","法定代表人","报告期","资产总计"],
#                 field_types={"资产总计":"money","报告期":"date"})

# 3. 表格(跨页 + 大表分层 + CSV/XLSX)
#    extract_table(pdf_path="D:/samples/中芯国际_年报.pdf", pages=[10,11],
#                  format=["json","csv"], cross_page=true)

# 4. 公式
#    extract_formulas(pdf_path="D:/samples/中芯国际_年报.pdf", pages=[20],
#                     render=true, tex=false)

# 5. 校验结果
#    validate_output(output_dir="D:/Dual-Extract-MCP/outputs/中芯_年报")
#    → {"schema_version": "1.1", "ok": true, "errors": [], "manifest_ok": true}
#    (产物目录含 extraction.json + manifest.json;manifest 记录各产物 SHA256,
#      validate_output 会与磁盘实文件比对,防产物漂移/被篡改)

# 6. 独立图片字段抽取(v1.3+,截图/扫描件/名单/表格照片,无需先包 PDF)
#    vision_extract(image_path="D:/samples/名单.png",
#                   fields=["公司代码","公司简称","注册资本"])
#    → document.source_image 填充 / source_pdf 空串 / 字段 page:0

# 7. OvisOCR2 文本路校验增强(v2.0+,形态 2「三源两段」)
#    DUAL_EXTRACT_TEXT_BACKEND=mineru+ovisocr2 python -m dual_extract.server
#    dual_extract(pdf_path="D:/samples/中芯国际_年报.pdf",
#                 fields=["公司代码","短期借款"],
#                 verify_pages=[1,2])   # 调用级指定校验页(信号②)
#    → meta.generation.text_extractors=["mineru","ovisocr2"];
#      warnings 含 ovisocr2_crosscheck:p1:rate=1.0 等校验信号;
#      默认 text_backend=mineru 不加载/不调用(零回归)

8. 项目结构(四层架构,依赖单向向下)

MCP Layer       server.py → mcp/{tools,handlers,converters}.py
    ↓
Service Layer   services/*.py(extract/parse/vision/table/formula/inspect/validate/health)
    ↓
Pipeline Layer  pipelines/*.py + fusion/*.py
    ↓
Adapter Layer   adapters/base.py(接口缝)+ mineru/ollama/storage/ovisocr2 实现
  • 与外部服务唯一耦合点 = 四个 Adapter 的 Protocol 接口(MinerUAdapter / OllamaAdapter / StorageAdapter / OvisOCR2Adapter)。

  • 铁律:不建注册表、不预写第二后端(Docling/云模型/对象存储 v0.4+ 再说)。

  • 详见 docs/architecture.md


9. 常见问题(FAQ)

9.1 VPN 代理导致 localhost 请求 502 / 超时(trust_env 陷阱)

Windows 上挂极光 VPN(端口 29290)时,Python httpx 默认 trust_env=True 会读取系统 注册表代理,把 http://127.0.0.1:8000 的 localhost 请求也走代理 → 502。 本项目所有 Adapter 的 httpx.AsyncClient 一律 trust_env=False无需手动处理。 若在自定义脚本里直接调 httpx,请同样加 trust_env=False

9.2 Ollama 冷启动 600s 超时

qwen3-vl:30b(30B 模型)首次加载进显存可能耗时 1-5 分钟,第一请求容易超时。 本项目 DUAL_EXTRACT_OLLAMA_TIMEOUT 默认 600 秒,足够冷启动; 若你的显存/磁盘更慢,可调大该值。探活(health)用固定 8s 短超时,不受此影响。

9.3 3090 24G 显存:默认串行防 OOM

qwen3-vl:30b 在 3090 24G 下并行多页易 OOM。默认 DUAL_EXTRACT_PARALLEL=false(串行), 页图渲染也是串行(F1)。若显存充裕可开并行,但建议先压测单页显存占用。

9.4 MinerU 不可达时怎么办

  • dual_extract / vision_extract / parse_text:自动降级(视觉路单跑或纯文本), 输出 warningsdegraded,不整体失败;

  • inspect_document:自动降级为纯 PyMuPDF 本地统计(页数/图片/文本字符), degraded=true + warnings 标注。

9.5 输出目录找不到产物

MCP 客户端 CWD 不可控,务必在 .envDUAL_EXTRACT_OUTPUT_DIR 配成绝对路径 (如 D:/Dual-Extract-MCP/outputs)。产物按 pdf_stem 分目录: {输出根}/{pdf文件名}/extraction.json + manifest.json


10. Roadmap

  • v0.2-alpha gate(T01-T03 = M1-M3):骨架 + 单路 + 双路融合闭环 ✅

  • T04(M4-M5):表格 + 公式 ✅

  • T05(M6-M7):inspect/validate + 安全加固 + 降级完善 + 文档 ✅

  • v0.2.0(2026-08-03 转正):G4 gate 真连验收 PASS(8 Tool 真连全通 + 3 P1 修复闭环 + 349 回归全绿)✅

  • v0.2.1(2026-08-03 发版):BUG-4 MinerUAdapter 子区间页号偏移修复(parse(pages=[7]) 时产物 page_idx 从 0 重编号,blocks 全标 page=1;应按 pages[0] 偏移)——commit bec69aa,353 回归全绿(4 个 BUG-4 专项回归)✅

  • V1.0(2026-08-03 增量)meta.generation / artifact_id / fields[].field_id / quality_report.json 自动产出 / tables[].cell_provenance(默认关)——5 项 additive 增量,406 回归全绿,QA 独立验证 PASS ✅

  • V1.2.0(2026-08-04):对齐《程序输出规范 v1.1》六项增量(source/pipeline/models 镜像、0.95 封顶、字段级 source、schema 1.1、cleaning_report、manifest 收敛)——582 回归全绿 ✅

  • V1.2.1(2026-08-04):MinerU 适配器降级/异常双 bug 修复(HTTP 相对路径 500 + CLI -p 误传)——586 回归全绿 ✅

  • V1.2.2(2026-08-05):health asyncio 嵌套修复 + BUG-5/6/7 表格取值修复——608 回归全绿 ✅

  • V1.3.0(2026-08-09):vision_extract 新增 image_path 独立图片输入——615 回归全绿 ✅

  • V2.0.0(2026-08-09):OvisOCR2 文本路校验增强接入(形态 2「三源两段」)——MinerU 结构主路 + OvisOCR2 按需校验 → 文本路内部融合 → 现有 Fusion Engine 二元契约零改动;纯 additive,schema_version 保持 "1.1",默认 text_backend=mineru 零加载/零调用——674 回归全绿 ✅

  • V2.0.1(2026-08-10):OvisOCR2 服务端 POST /parse_page 422 修复(根因:ParsePageRequest/ParsePageResponse 原是 create_app() 内部局部类,FastAPI 局部 Pydantic model 请求体识别失败 → 提升为模块级)+ D-2 真连实测 PASS(p213 95.71% 201/210、p8 95.45% 84/88,均 ≥95% 达标,实质错值 0)+ D-8 文档同步——bugfix 无 API 变更,674 回归全绿 ✅

  • 调研完成:复杂表格页降载策略(docs/复杂表格页降载策略.md)+ OvisOCR2 vs MinerU 对比(docs/OvisOCR2_vs_MinerU_对比报告.md)+ 模型分类与升级路线(docs/模型分类与升级路线.md)

  • 后续登记(保持 roadmap):OvisOCR2 文本路 Adapter 立项(需先补 16k 截断兜底设计) → ✅ 已完成(V2.0);VLM 线评估 Qwen3.6-35B-A3B(排在 OvisOCR2 之后);模型裁判(C3 默认关)→ v0.4 前不实现(保留原条目)

  • 多后端注册表(Docling/云模型/对象存储)→ 保持 roadmap

详细设计见 docs/技术方案设计_双路抽取MCP.md / docs/双路抽取MCP_架构设计.md / docs/双路抽取MCP_PRD.md / docs/architecture.md / docs/api.md。 输出质量评估(三层框架 + 可执行脚本)见 docs/输出质量评估指南.md / tools/assess_output_quality.py

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A local MCP server that extracts text-layer content from PDF files, enabling AI agents to inspect, extract text, outlines, and page content.
    -
  • A
    license
    A
    quality
    D
    maintenance
    A local-first MCP server that ingests PDFs, extracts structure, and provides semantic search and sequential navigation tools for AI clients to query and learn from documents.
    10
    MIT