Skip to main content
Glama
README.md
# PDF2ZH — FeatherFlow MCP Server for PDF Translation

A slim [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) tool server that translates scientific PDF documents while **preserving formulas, charts, table of contents, and layout**. Designed to be launched and managed by [FeatherFlow](https://github.com/lichman0405/featherflow).

Based on [PDFMathTranslate](https://github.com/Byaidu/PDFMathTranslate), stripped down to a single OpenAI-compatible translation backend — reusing the same LLM that FeatherFlow is already connected to.

## Features

- **MCP stdio transport** — plug-and-play with FeatherFlow (or any MCP-compatible host)
- **Preserves formulas & layout** — powered by ONNX-based document layout analysis + pdfminer/pymupdf
- **Dual output** — generates both *mono* (translated-only) and *dual* (bilingual side-by-side) PDFs
- **Shares FeatherFlow's LLM** — OpenAI-compatible endpoint via environment variables, no extra API key needed
- **Cross-platform** — works on Linux and Windows; all paths use `pathlib` for portability

## MCP Tools

| Tool | Description |
|------|-------------|
| `translate_pdf` | Translate a PDF file. Accepts `file`, `lang_in`, `lang_out`, optional `output_dir`. Returns **absolute paths** to mono & dual PDFs. |
| `list_supported_languages` | List all supported language codes (`en`, `zh`, `ja`, `ko`, `fr`, `de`, etc.) |

### File Path Resolution

The `file` parameter of `translate_pdf` supports both absolute and relative paths:

- **Absolute path** — used as-is (e.g. `/home/user/.featherflow/workspace/paper.pdf`)
- **Relative path** — resolved against the **workspace directory** (defaults to `~/.featherflow/workspace`, overridable via the `WORKSPACE_DIR` environment variable)

Output PDFs are written to the workspace directory by default. The returned paths are always absolute, making them directly usable by other MCP tools (e.g. feishu-mcp `upload_file` / `upload_file_and_share`).

## Requirements

> **⚠️ Python Environment Isolation — Important**
>
> This project depends on `babeldoc` / `onnxruntime`, which require **Python ≥3.10, <3.13**.
> FeatherFlow itself may run on a different Python version (e.g. 3.13+).
> You **must** create a separate Python environment for this project and point FeatherFlow's MCP config to this project's Python executable — not FeatherFlow's own Python.

- Python 3.10 – 3.12 (recommended: **3.12**)
- [uv](https://github.com/astral-sh/uv) (recommended), Conda, or virtualenv for environment isolation

## Installation

### 0. Install uv (one-time setup, recommended)

uv can automatically download and manage any Python version — no need to
install Python 3.12 manually.

```bash
# Linux / macOS
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows PowerShell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```

Restart your terminal after installation, then verify:

```bash
uv --version
```

### 1. Create a dedicated Python 3.12 environment

**Using uv** (recommended — auto-downloads Python 3.12 even if you only have 3.13+):

```bash
cd /path/to/pdftranslate-mcp
uv venv .venv --python 3.12
```

Activate the environment:

```bash
# Linux / macOS
source .venv/bin/activate

# Windows PowerShell
.venv\Scripts\Activate.ps1

# Windows CMD
.venv\Scripts\activate.bat
```

**Alternative: Conda**

```bash
conda create -p /path/to/pdftranslate-mcp/.venv python=3.12 -y
conda activate /path/to/pdftranslate-mcp/.venv
```

**Alternative: venv** (only if system Python is already 3.10–3.12)

```bash
python3.12 -m venv .venv
source .venv/bin/activate        # Linux/macOS
# .venv\Scripts\Activate.ps1     # Windows PowerShell
```

### 2. Install the package

```bash
pip install -e .
```

Or with uv (10-100x faster):

```bash
uv pip install -e .
```

This installs all dependencies: `pymupdf`, `pdfminer-six`, `babeldoc`, `onnxruntime`, `openai`, `mcp`, etc.

### 3. Verify

```bash
python -m pdf2zh.mcp_server --help
```

## FeatherFlow Configuration

Edit `~/.featherflow/config.json` (or the config file for your setup). Add `pdf2zh` under `tools.mcpServers`.

> **Key point:** The `command` must point to this project's own Python executable, **not** FeatherFlow's Python. This project requires Python <3.13, while FeatherFlow may run on a newer version.

### Example (Linux — production server)

```json
{
  "tools": {
    "mcpServers": {
      "pdf2zh": {
        "command": "/opt/PDFMathTranslate/.venv/bin/python",
        "args": ["-m", "pdf2zh.mcp_server"],
        "toolTimeout": 600,
        "env": {
          "OPENAI_BASE_URL": "https://openrouter.ai/api/v1",
          "OPENAI_API_KEY": "sk-or-v1-xxxxxxxx",
          "OPENAI_MODEL": "anthropic/claude-opus-4-5"
        }
      }
    }
  }
}
```

### Example (Windows — development)

```json
{
  "tools": {
    "mcpServers": {
      "pdf2zh": {
        "command": "C:/Users/<you>/code/PDFMathTranslate/.venv/python.exe",
        "args": ["-m", "pdf2zh.mcp_server"],
        "toolTimeout": 600,
        "env": {
          "OPENAI_BASE_URL": "https://openrouter.ai/api/v1",
          "OPENAI_API_KEY": "sk-or-v1-xxxxxxxx",
          "OPENAI_MODEL": "anthropic/claude-opus-4-5"
        }
      }
    }
  }
}
```

> **Tip:** On Windows, use forward slashes `/` in JSON paths — they work fine with Python's `pathlib`.

### Tool Timeout — Critical for PDF Translation

> **⚠️ You MUST set `toolTimeout` for the pdf2zh MCP server.**
>
> FeatherFlow's default MCP tool timeout is **30 seconds**. PDF translation is a heavy operation — a 6-page paper typically takes **1–5 minutes** depending on the LLM speed. Without increasing `toolTimeout`, the tool call will be cancelled mid-translation, and the agent will report a failure.
>
> **Recommended:** `"toolTimeout": 600` (10 minutes). For very long documents (50+ pages), consider `1200` (20 minutes).

```json
"pdf2zh": {
  "command": "...",
  "args": ["-m", "pdf2zh.mcp_server"],
  "toolTimeout": 600,
  ...
}
```

### Environment Variables

| Variable | Required | Description |
|----------|----------|-------------|
| `OPENAI_BASE_URL` | Yes | API base URL (e.g. `https://openrouter.ai/api/v1`, `https://api.openai.com/v1`) |
| `OPENAI_API_KEY` | Yes | API key for the provider |
| `OPENAI_MODEL` | Yes | Model identifier (e.g. `anthropic/claude-opus-4-5`, `gpt-4o`) |
| `OPENAI_TEMPERATURE` | No | Override LLM temperature. **Omit for reasoning models** (e.g. `kimi-k2.5`) which enforce their own temperature. Set to `0` for deterministic output with standard models. |
| `WORKSPACE_DIR` | No | Shared workspace directory. Defaults to `~/.featherflow/workspace` (same as FeatherFlow's built-in file tools and `paper_download`). Override this if your workspace is at a non-standard location. |

The `OPENAI_*` variables are the same credentials FeatherFlow uses — just pass them through via `env`. `WORKSPACE_DIR` usually does not need to be set; it automatically uses FeatherFlow's default workspace.

## Standalone Usage (without FeatherFlow)

### stdio mode (default)

```bash
python -m pdf2zh.mcp_server
```

### SSE mode (for web-based MCP clients)

```bash
python -m pdf2zh.mcp_server --sse --host 0.0.0.0 --port 3001
```

## Cross-MCP Workflow: pdf2zh + feishu-mcp

This project is designed to work alongside [feishu-mcp](https://github.com/lichman0405/feishu-mcp). A typical end-to-end flow:

```
User: "Translate this paper and share it in the Feishu group"
  ↓
FeatherFlow (LLM orchestration):
  1. paper_download → ~/.featherflow/workspace/paper.pdf
  2. pdf2zh.translate_pdf(file="paper.pdf", lang_in="en", lang_out="zh")
     → ~/.featherflow/workspace/paper-mono.pdf
     → ~/.featherflow/workspace/paper-dual.pdf
  3. feishu-mcp.upload_file_and_share(file_path="/home/user/.featherflow/workspace/paper-dual.pdf")
     → share_url
  4. feishu-mcp.send_message(chat_id, share_url)
```

**Why this works seamlessly:**

- pdf2zh writes output to `~/.featherflow/workspace` by default
- feishu-mcp `upload_file` / `upload_file_and_share` accepts absolute file paths
- pdf2zh returns absolute paths in its result — the LLM can extract and pass them directly to feishu-mcp
- Both MCP servers run as local processes on the same machine, sharing the same filesystem

## Project Structure

```
pdf2zh/
  __init__.py        # Package entry, exports translate_stream
  mcp_server.py      # MCP server (entry point, tools definition)
  translator.py      # BaseTranslator + OpenAITranslator
  converter.py       # PDF content conversion & layout processing
  high_level.py      # Core translation pipeline (translate_stream)
  config.py          # Configuration & constants
  cache.py           # Translation cache (SQLite via peewee)
  doclayout.py       # ONNX document layout model loading
  pdfinterp.py       # Extended PDF interpreter
pyproject.toml       # Dependencies & build config
```

## License

[AGPL-3.0](LICENSE)

## Credits

- Core PDF translation engine from [PDFMathTranslate](https://github.com/Byaidu/PDFMathTranslate) by Byaidu
- MCP host integration for [FeatherFlow](https://github.com/lichman0405/featherflow)

TDQS

A4.2/5.0

Scored across 2 tools

Disambiguation5/5

Only two tools with clearly distinct purposes: one lists supported languages, the other translates PDFs. No overlap or ambiguity.

Naming Consistency5/5

Both tools follow a consistent verb_noun pattern: list_supported_languages and translate_pdf. No mixing of conventions.

Tool Count3/5

With only 2 tools, the server feels thin for a broader translation service, but it is scoped to a single core task. The count is borderline acceptable.

Completeness4/5

The core workflow (translating a PDF with language selection) is fully covered. Missing optional features like output format control, but no critical gaps for the stated purpose.

Maintenance

ActivityInactive
ResponsivenessNo issues