Metadata-Version: 2.4
Name: belindoc-mcp
Version: 0.1.6
Summary: Belindoc 文档 / 视频翻译的 MCP 服务
Author: zhangjun
License: MIT
Project-URL: Homepage, https://belindoc.com
Keywords: mcp,translation,document,video,subtitle
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Text Processing :: Linguistic
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: mcp>=2.0.0
Requires-Dist: httpx>=0.25.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: starlette>=0.37.0
Requires-Dist: uvicorn>=0.27.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"

# Trans MCP Server

Belindoc 翻译开放 API 的 MCP 服务：文档（PDF / Word / Excel / Markdown / 图片）和视频翻译、
字幕改写。

## 两种运行方式

| | stdio | HTTP 远程 |
|---|---|---|
| 入口 | `belindoc-mcp` | `trans-mcp-http` |
| 跑在哪 | 用户自己的机器上 | 一台服务器上，多人共用 |
| API Key | 服务端从 `BELINDOC_API_KEY` 读 | 每个客户端自己带 `Authorization: Bearer <key>`，服务器不存任何密钥 |
| 传输 | stdio | Streamable HTTP（SSE + `Mcp-Session-Id`） |

两种方式的工具、行为完全一致，包括服务端直接向用户弹窗确认（elicitation）和等待期间的
进度通知。部署 HTTP 模式看 [DEPLOY.md](DEPLOY.md)。

## 安装

从 PyPI 装即可，不用 clone 源码：

```bash
uvx belindoc-mcp       # 试跑一下；客户端配置里也直接这么写，不用预装
# 或者
pipx install belindoc-mcp
```

走 `uvx` 这条路得先有 uv——`uvx` 是它带的命令。没装过就先装：

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

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

机器上已经有 Homebrew 或 pip 的话，`brew install uv`、`pip install uv` 也一样。

**装完重开一个终端再往下走**，`uv` 要新开的 shell 才进得了 `PATH`。这步漏掉，后面客户端一
律报找不到 `uvx`——是整个流程里最常见的失败原因，没有之一。

用 `pipx` 那条路不需要 uv。

从源码装（开发、或要改代码）见 [开发](#开发)。

## 环境变量

| 变量 | 用在哪 | 说明 |
|------|--------|------|
| `BELINDOC_API_KEY` | stdio | 必需。格式 `ft_` + 40 位随机串，共 43 字符 |
| `BELINDOC_API_BASE_URL` | 都 | 上游地址。不设即生产 `https://belindoc.com/api`；要打到别的环境才需要设 |
| `MCP_HOST` / `MCP_PORT` | HTTP | 监听地址与端口，默认 `0.0.0.0:8080` |
| `MCP_PATH` | HTTP | MCP 服务端点路径，默认 `/mcp`。同域名下落地页占了 `/mcp` 时挪开 |
| `MCP_LOCALE` | 都 | 用户可见文案的语言，默认 `zh`。见下方「输出语言」 |

HTTP 模式**不读** `BELINDOC_API_KEY`——别把真实 key 写进服务器的 `.env`。
完整注释见 [.env.example](.env.example)。

### 获取 API Key

登录 https://belindoc.com → 「开放平台」→「API Key 管理」→ 创建。

## 客户端接入

### stdio

```json
{
  "mcpServers": {
    "belindoc-mcp": {
      "type": "stdio",
      "command": "uvx",
      "args": ["belindoc-mcp"],
      "env": {
        "BELINDOC_API_KEY": "ft_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "BELINDOC_API_BASE_URL": "https://belindoc.com/api"
      }
    }
  }
}
```

`BELINDOC_API_BASE_URL` 填的就是默认值，不写也一样；要打到别的环境才改它。

配置文件位置：Claude Desktop 是 `~/Library/Application Support/Claude/claude_desktop_config.json`，
Codex 是 `~/.codex/config.json`。

不想手改 JSON 的话，两个客户端都有命令行可以一把加：

```bash
# Claude Code
claude mcp add belindoc-mcp \
  -e BELINDOC_API_KEY=ft_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
  -e BELINDOC_API_BASE_URL=https://belindoc.com/api \
  -- uvx belindoc-mcp

# Codex
codex mcp add belindoc-mcp \
  --env BELINDOC_API_KEY=ft_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
  --env BELINDOC_API_BASE_URL=https://belindoc.com/api \
  -- uvx belindoc-mcp
```

两条只差传环境变量的写法：`claude` 用 `-e`，`codex` 用 `--env`。`--` 后面是真正要跑的命令，
别漏。

`uvx` 会自己拉包、自己建隔离环境，用户不用预装本项目，也不用管路径——代价是机器上得先有
uv 本身，见上面的[安装](#安装)。

**从源码装的话**，`command` 必须填绝对路径——`pip install -e .` 之后 venv 里会生成
`belindoc-mcp` 这个可执行文件，填它的完整路径（形如
`/path/to/trans-mcp/.venv/bin/belindoc-mcp`）。客户端不走登录 shell，`PATH` 里通常没有
这个 venv，写裸命令名会起不来。

不想把 key 写进客户端配置的话，也可以放进项目根目录的 `.env`，启动时自己加载：

```bash
cp .env.example .env   # 填入 API Key
source .env && belindoc-mcp
```

### 其他客户端

stdio 这套配置在各家客户端里是同一个东西，换客户端只有三处要对：配置文件在哪、顶层的键叫
什么、以及那三行本项目自己的内容（`command: uvx`、`args: ["belindoc-mcp"]`、`env` 里的两个
变量）。第三项到哪都一样，抄上面的 JSON 即可。

前两项：

| 客户端 | 配置文件 | 顶层键 |
|--------|----------|--------|
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` | `mcpServers` |
| Claude Code | 项目根 `.mcp.json`（或直接 `claude mcp add`） | `mcpServers` |
| Codex | `~/.codex/config.json`（或直接 `codex mcp add`） | `mcpServers` |
| Cursor | 项目 `.cursor/mcp.json`，或全局 `~/.cursor/mcp.json` | `mcpServers` |
| Windsurf | `~/.codeium/windsurf/mcp_config.json` | `mcpServers` |
| VS Code | 项目 `.vscode/mcp.json` | `servers` |

这张表会过期——各家的路径和键名都改过不止一次，装之前对一眼自己客户端的当前文档。跟本项目
有关的部分不会变。

装完起不来，先查两条：`uvx` 在不在客户端能看到的 `PATH` 里（客户端不走登录 shell，装完 uv
没重开终端最常见），以及 key 有没有填对。

### HTTP 远程

```json
{
  "mcpServers": {
    "belindoc-mcp": {
      "type": "http",
      "url": "https://mcp.belindoc.com/api/mcp",
      "headers": {
        "Authorization": "Bearer ft_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }
    }
  }
}
```

`type` 的取值因客户端而异，Claude Code 填 `http`，其他客户端以其当前文档为准。Streamable HTTP 是协议名，不是要填进配置的值。

Codex CLI 的 HTTP MCP 发不了自定义请求头，只能用 Bearer；走 `~/.codex/config.toml` 的话：

```toml
[mcp_servers.belindoc-mcp]
url = "https://mcp.belindoc.com/api/mcp"
bearer_token_env_var = "BELINDOC_API_KEY"
```

### 接上之后

调一次 `get_account_status` 验证密钥通不通，顺便看余额。想知道这个客户端支不支持服务端
弹窗确认（关系到视频提交走一步还是两步），用 `MCP_DEBUG_TOOLS=1` 起服务，调一次
`probe_elicitation`——它不翻译、不提交任务、不扣额度。

## 典型流程

**文档**：`upload_document` 取预签名链接 → 按返回的 `uploadCommand` 上传 →
（PDF 才要）`check_pdf_ocr` 看是不是扫描件 → `translate_document` 提交 →
`wait_for_translation` 跟进 → `get_document_translation_result` 取下载链接。

**视频**：`upload_video` → 上传 → `calculate_video_translation_quota` 试算 →
`translate_video` 提交（两步确认，见下）→ `wait_for_video_translation` 跟进。
想改字幕重出一版：`get_video_subtitles` → `calculate_rewrite_quota` →
`rewrite_video_subtitles` → `get_video_rewrite_status`。

上传由调用方自己执行返回的 `uploadCommand`，服务端不碰用户机器上的文件；下载给的是
签名链接，问号后面的签名参数一个字符都不能改，截掉就是 403。

## 工具列表

### 账户与元信息
| 工具 | 说明 |
|------|------|
| `get_supported_languages` | 支持的语言列表（79 种，语言码 → 显示名） |
| `get_model_list` | 当前账户可用的翻译模型 |
| `get_account_status` | 可用额度、会员档位、各项限额（单视频时长 / 并发数 / 单文件大小） |

### 文档翻译
| 工具 | 说明 |
|------|------|
| `upload_document` | 取文档的预签名上传链接 |
| `check_pdf_ocr` | 判断已上传的 PDF 是不是扫描件 / 双层 PDF |
| `translate_document` | 提交文档翻译任务 |
| `wait_for_translation` | 等待任务完成，进度一有变化就返回 |
| `get_document_translation_status` | 查单个任务状态 |
| `get_document_translation_result` | 取译文下载链接 |
| `list_document_translations` | 分页查任务列表 |
| `get_document_translation_by_batch` | 按批次号查任务 |

### 视频翻译
| 工具 | 说明 |
|------|------|
| `upload_video` | 取视频的预签名上传地址 |
| `calculate_video_translation_quota` | 试算要花多少额度，不扣费 |
| `translate_video` | 提交视频翻译任务（会真扣额度，两步确认） |
| `wait_for_video_translation` | 等待任务完成，进度一有变化就返回 |
| `get_video_translation_status` | 查单个任务状态 |
| `list_video_translations` | 分页查任务列表（只有最近 15 天） |
| `cancel_video_translation` | 取消任务 |

### 字幕改写
| 工具 | 说明 |
|------|------|
| `get_video_subtitles` | 取原文与译文字幕下载地址 |
| `calculate_rewrite_quota` | 试算改写要花多少额度，不扣费 |
| `rewrite_video_subtitles` | 用编辑后的字幕重新生成视频（会真扣额度，两步确认） |
| `get_video_rewrite_status` | 查改写进度 |

### 排查

默认不挂出来，设 `MCP_DEBUG_TOOLS=1` 才有。

| 工具 | 说明 |
|------|------|
| `probe_elicitation` | 自检：这个客户端到底吃不吃 elicitation。不翻译、不提交、不扣额度 |

## 扣费确认

`translate_video` 和 `rewrite_video_subtitles` 会真扣额度，所以提交是**两步**，第一次
一定不会提交：

- 客户端支持 **elicitation** 时，服务端直接弹窗问用户，一次调用即可；
- 不支持时退回**确认码**：第一次调用返回 409 + 一段给用户看的话 + 一张菜单（配音 ×
  字幕的各种组合，每格自带额度和 `confirmToken`），把菜单原样给用户看、他挑了哪一项，
  就用那一项的 `confirmToken` 重调一次，这一次才真的提交。

之所以不能只信一个 `user_confirmed=true`：那种布尔量永远是模型自己填的，服务端无法验证
背后到底有没有问过人。想知道某个客户端走哪条路，开 `MCP_DEBUG_TOOLS=1` 调一次 `probe_elicitation`。

## 输出语言

会被念给用户听的那部分文案（任务状态、产出说明、进度行、失败原因、下载说明）支持九种
语言：`zh` / `zh-Hant` / `en` / `ja` / `ko` / `de` / `fr` / `ru` / `ar`。工具描述和给模型
的操作指令始终是中文——那是写给模型的。

优先级：工具参数 `locale` > 服务端 `MCP_LOCALE` > `zh`。

## 故障排除

### 认证失败 (10004)

API Key 不对、没注册、或格式错（必须 `ft_` 开头共 43 字符）。先 `echo $BELINDOC_API_KEY`
确认，再去平台看 key 的状态。

### 密钥类错误码 (30306 / 30307 / 30308 / 30309 / 30312)

这几个上游一律用 HTTP 200 送回来，业务码在响应体里。工具会把它们翻成一句可执行的话
（key 没复制全 / 被禁用要重新启用 / 已过期 / IP 不在白名单 / 需联系客服），并明确标注
**重试、换参数、重新上传都没有用**。只有 30311 是该退避重试的。

### 接口不存在 (404)

返回里会写明「接口 X 在当前服务地址（Y）上不存在」。这不是网络故障，是该功能在这个环境
没部署，或者 `BELINDOC_API_BASE_URL` 指错了环境。重试无用。

### 连接超时

检查后端是否在跑、网络是否通、防火墙是否放行。

## 开发

```bash
cd /path/to/trans-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest tests/
```

### 发布到 PyPI

```bash
pip install build twine
python -m build            # 出 dist/*.whl 和 dist/*.tar.gz
twine upload dist/*
```

发之前先把 `pyproject.toml` 的 `version` 加上去——PyPI 的同一版本号只能传一次。
`python -m build` 之前先 `rm -rf dist/`，否则旧版本会跟着一起传上去。

包是公开的，所以别往仓库里放任何只该留在内部的东西：`README.md` 会原样变成 PyPI
首页，`tests/` 会进 sdist。加内容前对着 `tar tzf dist/*.tar.gz` 看一眼。

根目录的 `test_api.py` / `test_upload.py` 是手动连真实 API 的冒烟脚本，不是用例，
pytest 只收集 `tests/`。

### 项目结构

PyPI 包名是 `belindoc-mcp`，仓库目录和 Python 模块仍叫 `trans-mcp` / `trans_mcp`——
后两个用户看不见，跟着改要动 Dockerfile、systemd 单元和已在跑的服务器的升级路径。
`trans-mcp` / `trans-mcp-http` 这两个命令也照旧留着，部署脚本在调它们。

```
trans-mcp/
├── README.md              # 本文件
├── DEPLOY.md              # HTTP 远程模式的部署
├── INTEGRATION.md         # 客户端配置速查
├── CONFIG.md              # 环境变量速查
├── pyproject.toml
├── .env.example
├── src/trans_mcp/
│   ├── server.py          # stdio 入口
│   ├── http_server.py     # HTTP 入口（Streamable HTTP）
│   ├── tools.py           # 工具定义与处理器（两种模式共用）
│   ├── client.py          # 上游 API 客户端
│   └── i18n.py            # 用户可见文案的九种语言
├── tests/
├── deploy.sh              # Docker 部署
├── deploy-linux.sh        # systemd 部署
└── server.sh              # 本机起停
```

## 许可证

MIT License
