Metadata-Version: 2.4
Name: paddle-ocr-server
Version: 0.3.1
Summary: MCP server exposing PaddleOCR remote jobs for PDF/image-to-Markdown conversion
Keywords: markdown,mcp,ocr,paddleocr,pdf
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Text Processing :: Markup
Requires-Python: >=3.13
Requires-Dist: fastmcp>=3.4.2
Requires-Dist: httpx>=0.28.0
Description-Content-Type: text/markdown

# PaddleOCR MCP Server

通过 PaddleOCR 官方 API 将 PDF 或图片转换为 Markdown。服务直接暴露 PaddleOCR
远端任务的提交、查询和保存三个阶段，不创建第二套本地后台任务。

## 设计概览

```text
ocr_submit(file_path 或 file_url)
    ↓ 返回 PaddleOCR 远端 job_id
ocr_status(job_id)
    ↓ 调用方按需重复查询，直到 state=done
ocr_save(job_id, output_dir)
    ↓ 下载 JSONL、逐页 Markdown 和图片
full.md
```

PaddleOCR 的 `job_id` 是唯一任务状态源。MCP Server 不保存本地 job 表，因此进程
重启后仍可继续查询或保存已有远端任务。每个 MCP 调用都是一次短请求，不依赖 MCP
Host 是否支持 `progressToken`。

本 Server 只暴露 Tools，不提供 Resources 或 Prompts。

## 环境要求

- Python >= 3.13
- PaddleOCR AI Studio access token
- 推荐使用 [uv](https://docs.astral.sh/uv/) / `uvx` 运行

获取 token：https://aistudio.baidu.com/account/accessToken

Server 从环境变量读取 token：

```text
PADDLEOCR_ACCESS_TOKEN
```

不要把 token 写入提示词、提交到 Git，或输出到 MCP 日志。

## 安装与 MCP Host 配置

推荐固定版本，避免未来破坏性升级影响现有 Host：

```json
{
  "mcpServers": {
    "paddle-ocr": {
      "command": "uvx",
      "args": ["paddle-ocr-server==0.3.1"],
      "env": {
        "PADDLEOCR_ACCESS_TOKEN": "your-token"
      }
    }
  }
}
```

运行最新版本时可使用：

```bash
PADDLEOCR_ACCESS_TOKEN=your-token uvx paddle-ocr-server
```

本地开发配置：

```json
{
  "mcpServers": {
    "paddle-ocr": {
      "command": "uv",
      "args": [
        "--directory",
        "/absolute/path/to/servers/paddle-ocr",
        "run",
        "paddle-ocr-server"
      ],
      "env": {
        "PADDLEOCR_ACCESS_TOKEN": "your-token"
      }
    }
  }
}
```

## Tool：`ocr_submit`

提交本地文件或远程 URL，立即返回 PaddleOCR 的远端任务 ID。本 Tool 不等待 OCR
完成。

### 参数

| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---:|---|---|
| `file_path` | `string \| null` | 二选一 | `null` | MCP Server 所在机器可读取的 PDF/图片路径 |
| `file_url` | `string \| null` | 二选一 | `null` | PaddleOCR 服务可访问的文件 URL |
| `model` | `string` | 否 | `PaddleOCR-VL-1.6` | PaddleOCR 模型名称 |

`file_path` 和 `file_url` 必须且只能提供一个。本地路径是 **Server 机器上的路径**，
不是远程 MCP Host 或用户电脑上的任意路径。

当前固定关闭以下 PaddleOCR 可选处理：

```text
useDocOrientationClassify = false
useDocUnwarping = false
useChartRecognition = false
```

图表识别固定关闭，避免把图片转换为可读性较差的表格；此设置不作为 Tool 参数开放。

### 返回值

```json
{
  "job_id": "remote-paddle-job-id",
  "state": "submitted"
}
```

这里的 `submitted` 表示 MCP 已取得远端 `job_id`，不是 PaddleOCR 的实时任务状态。
后续状态以 `ocr_status` 为准。

## Tool：`ocr_status`

查询一次 PaddleOCR 远端状态，不在 Server 内循环等待。推荐由 Agent 或 Host 每
5 秒左右调用一次；没有必要每秒高频查询。

### 参数

| 参数 | 类型 | 必需 | 说明 |
|---|---|---:|---|
| `job_id` | `string` | 是 | `ocr_submit` 返回的远端任务 ID |

### 返回值

```json
{
  "job_id": "remote-paddle-job-id",
  "state": "running",
  "extracted_pages": 92,
  "total_pages": 343,
  "start_time": "2026-07-10 10:26:57",
  "end_time": null,
  "result_url": null,
  "error": null
}
```

| 字段 | 类型 | 说明 |
|---|---|---|
| `job_id` | `string` | 远端任务 ID |
| `state` | `string` | PaddleOCR 返回的原始状态；实测处理中为 `running`，完成为 `done` |
| `extracted_pages` | `number` | 已处理页数；远端尚未返回进度时为 `0` |
| `total_pages` | `number \| null` | 总页数；任务刚提交时可能为 `null` |
| `start_time` | `string \| null` | 远端 `startTime` 原值；不猜测或转换时区 |
| `end_time` | `string \| null` | 远端 `endTime` 原值；通常完成后才存在 |
| `result_url` | `string \| null` | 完成后的 JSONL 下载地址；处理中通常为 `null` |
| `error` | `string \| null` | 远端错误信息 |

任务刚提交时，PaddleOCR 可能只返回 `jobId` 和 `state`，暂时没有页数和时间字段；
这是正常状态，不应当视为解析失败。页数按远端批次更新，可能连续几次查询保持不变。

`result_url` 可能是临时签名地址，不要记录或分享完整 URL。通常无需直接使用它，
将 `job_id` 传给 `ocr_save` 即可。

## Tool：`ocr_save`

重新查询远端状态，确认 `state=done` 后下载并保存结果。如果任务仍在处理中，本
Tool 会明确报错，不会在内部等待。

### 参数

| 参数 | 类型 | 必需 | 说明 |
|---|---|---:|---|
| `job_id` | `string` | 是 | 已完成的远端任务 ID |
| `output_dir` | `string` | 是 | MCP Server 所在机器上的输出目录 |

### 返回值

返回生成的 `full.md` 路径，例如：

```text
/data/ocr/book/full.md
```

### 输出结构

```text
output_dir/
├── full.md
├── pages/
│   ├── page_0.md
│   ├── page_1.md
│   └── ...
└── imgs/
    └── ...
```

- `pages/page_N.md`：按所有 JSONL 批次中的 `layoutParsingResults` 连续编号
- `full.md`：使用 `\n\n---\n\n` 连接全部页面
- `imgs/`：Markdown 引用的远端图片

PaddleOCR 的 JSONL 一行可能包含多个页面，不能假设“一行等于一页”。Server 会
遍历所有 `layoutParsingResults` 后连续编号。

复用同一个 `output_dir` 时，Server 会覆盖本次页面并删除不再存在的
`pages/page_数字.md`，但不会删除其他人工文件。为避免保留不再引用的旧图片，生产
使用中仍推荐每个文档使用独立输出目录。

## Markdown 后处理

保存前会进行以下轻量处理：

- 去掉行内公式 `$ ... $` 边界处的空格
- 将块公式整理为独立的 `$$` 行
- 将三个及以上连续换行压缩为两个

因此输出并非远端 Markdown 的逐字节原样副本。

## 推荐的 Agent 调用策略

```text
1. 调用 ocr_submit，保存 job_id。
2. 等待约 5 秒。
3. 调用 ocr_status：
   - state=done：进入第 4 步；
   - state=failed/error：向用户报告 error；
   - 其他状态：报告 extracted_pages/total_pages，继续等待。
4. 调用 ocr_save。
5. 返回 full.md 路径，并按需要交给后续翻译或摘要工具。
```

不要在 Agent 内另造一套本地 job ID；直接持有 PaddleOCR 的 `job_id` 即可。

## 错误行为

- 缺少 token：抛出环境变量未设置错误
- `file_path` 与 `file_url` 未提供或同时提供：拒绝提交
- 本地路径不存在或不是普通文件：拒绝提交
- `job_id` 为空：拒绝查询或保存
- 远端任务未完成：`ocr_save` 返回当前状态和页数
- 远端任务失败：返回 PaddleOCR 错误信息
- 完成响应缺少 `result_url`：拒绝生成不完整结果
- 图片路径试图逃逸 `output_dir`：拒绝写入

网络错误和 PaddleOCR HTTP 错误会直接传递给 MCP 调用方。目前不在 Server 内自动
重试提交请求，以避免创建重复远端任务。

## 从 0.2.x 升级

`0.3.0` 是破坏性升级：

- 删除阻塞式 `ocr_pdf`
- 新增 `ocr_submit`、`ocr_status`、`ocr_save`
- 不再使用 MCP progress notification
- 轮询责任从 Server 移到 Agent/Host

旧调用方必须按新的三阶段流程调整。

## 本地开发

```bash
cd servers/paddle-ocr
uv sync
uv run pytest -v
uv run paddle-ocr-server
```
