Metadata-Version: 2.4
Name: deepseek-translate-server
Version: 0.2.0
Summary: Translate Markdown to Chinese through DeepSeek with bounded concurrency
Keywords: deepseek,markdown,mcp,translation
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.13
Requires-Dist: fastmcp>=3.4.2
Requires-Dist: openai>=1.65.0
Description-Content-Type: text/markdown

# DeepSeek Translate MCP Server

通过 DeepSeek API 将 Markdown / HTML 混排内容翻译为中文。服务只暴露同步翻译
能力，不在 MCP 进程内创建后台任务或保存任务状态。

## 设计概览

```text
translate_md(text 或 file_path)
    → 直接返回 Markdown 译文

translate_md_dir(dir_path)
    → 为每个文件调用 translate_md(file_path=...)
    → 保存各文件译文
    → 生成 full_trans.md
    → 返回完成摘要
```

DeepSeek Chat Completions 是同步请求，没有可由远端继续查询的 `job_id`。因此本
Server 不提供 `start/status` 工具。内存中的后台任务在 Server 重启后会丢失，不能
构成可靠的任务协议；若将来需要可恢复的后台翻译，应另行接入持久化任务队列。

两个 Tool 都会在当前 MCP 调用内等待 API 完成。`translate_md_dir` 会在 Host
提供 `progressToken` 时按完成文件数发送进度；进度不是持久化状态，也不能在调用
结束后查询。

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

## 环境要求

- Python >= 3.13
- DeepSeek/OpenAI 兼容 provider 的 API key
- 推荐使用 [uv](https://docs.astral.sh/uv/) / `uvx` 运行

Server 从环境变量读取 provider 配置：

| 环境变量 | 必需 | 默认值 | 说明 |
|---|---:|---|---|
| `DEEPSEEK_API_KEY` | 是 | — | 当前 provider 的 API key |
| `DEEPSEEK_BASE_URL` | 否 | `https://api.deepseek.com` | 当前 provider 的 OpenAI 兼容 API 地址 |

`base_url` 和 `api_key` 只存在于 Server 配置，不会出现在 Tool schema、模型上下文
或 Tool 调用日志中。切换 provider 时修改这两个环境变量，并在 Tool 调用中传入对应
的 `model` 即可。目标 provider 需要兼容 Chat Completions 以及
`thinking.type=disabled` 请求字段。

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

## 安装与 MCP Host 配置

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

```json
{
  "mcpServers": {
    "deepseek-translate": {
      "command": "uvx",
      "args": ["deepseek-translate-server==0.2.0"],
      "env": {
        "DEEPSEEK_API_KEY": "your-api-key",
        "DEEPSEEK_BASE_URL": "https://api.deepseek.com"
      }
    }
  }
}
```

直接运行最新版本：

```bash
DEEPSEEK_API_KEY=your-api-key \
DEEPSEEK_BASE_URL=https://api.deepseek.com \
uvx deepseek-translate-server
```

本地开发配置：

```json
{
  "mcpServers": {
    "deepseek-translate": {
      "command": "uv",
      "args": [
        "--directory",
        "/absolute/path/to/servers/deepseek-translate",
        "run",
        "deepseek-translate-server"
      ],
      "env": {
        "DEEPSEEK_API_KEY": "your-api-key",
        "DEEPSEEK_BASE_URL": "https://api.deepseek.com"
      }
    }
  }
}
```

## Tool：`translate_md`

翻译一段 Markdown 文本或读取一个本地 Markdown 文件，直接返回译文。本 Tool
不会写入文件。

### 参数

| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---:|---|---|
| `text` | `string \| null` | 二选一 | `null` | 待翻译的 Markdown 文本 |
| `file_path` | `string \| null` | 二选一 | `null` | Server 所在机器可读取的 Markdown 文件 |
| `model` | `string` | 否 | `deepseek-v4-flash` | DeepSeek 模型名称 |
| `temperature` | `number` | 否 | `0.3` | 采样温度 |

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

Server 不设置最大字符数，也不拆分输入。一份 `text` 或一个 `file_path` 始终对应
一次 provider 请求；实际可接受长度只受所选模型的上下文限制约束。已经分页的 OCR
资料应直接使用 `translate_md_dir`。

System message 包含全部翻译规则；User message 的内容就是原始 Markdown，不再
额外拼接重复的翻译指令。

### 返回值

返回翻译后的 Markdown 字符串。返回值可能很大；对于 OCR 目录，通常应使用
`translate_md_dir`，让结果直接写入文件。

## Tool：`translate_md_dir`

翻译目录内所有匹配的 Markdown 文件，保存逐文件译文并生成合并译文。
本 Tool 不实现第二套翻译逻辑，而是为每个源文件直接调用 `translate_md`。

### 参数

| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---:|---|---|
| `dir_path` | `string` | 是 | — | Markdown 目录或 PaddleOCR 输出目录 |
| `pattern` | `string` | 否 | `*.md` | 文件名匹配模式 |
| `model` | `string` | 否 | `deepseek-v4-flash` | DeepSeek 模型名称 |
| `temperature` | `number` | 否 | `0.3` | 采样温度 |

### 输入与输出约定

- 如果 `dir_path/pages/` 存在，只读取该子目录；否则读取 `dir_path`。
- 自动排除文件名以 `_trans.md` 结尾的既有译文，避免重复翻译。
- 每个源文件作为一个完整翻译单位，只发起一次 provider 请求，不再拆分。
- 每个译文保存为源文件同目录下的 `{stem}_trans.md`。
- 译文按文件名中的数字排序，并合并为 `dir_path/full_trans.md`。
- 所有文件以及同时发生的其他 Tool 调用共享 Server 进程级信号量；整个 Server
  同时最多发出 `100` 个 provider API 请求。

### 返回值

全部文件翻译并写入成功后返回：

```json
{
  "source_dir": "/data/book/pages",
  "total_files": 58,
  "output_files": [
    "/data/book/pages/page_0_trans.md",
    "/data/book/pages/page_1_trans.md"
  ],
  "merged_path": "/data/book/full_trans.md"
}
```

| 字段 | 类型 | 说明 |
|---|---|---|
| `source_dir` | `string` | 实际读取源文件的目录 |
| `total_files` | `integer` | 成功翻译的文件总数 |
| `output_files` | `string[]` | 按源文件顺序排列的逐文件译文路径 |
| `merged_path` | `string` | 合并译文路径 |

## 进度与失败语义

- `translate_md_dir` 按已完成文件数报告进度。
- 只有 Host 为调用提供 MCP `progressToken` 时，进度才会显示；是否显示由 Host
  决定，Server 不会为此创建额外状态。
- 连接错误、超时、HTTP `429` 和 `5xx` 会对当前 Markdown 请求最多重试 `2` 次，
  即每个文件最多请求 `3` 次；两次退避分别为 `1` 秒和 `2` 秒。
- 退避等待不会占用全局信号量槽位；每次重试都重新进入全局并发队列。
- OpenAI SDK 自带重试已关闭，避免与 Server 重试相乘。
- HTTP `400/401/403` 等永久错误不重试，立即失败。
- 目录中某个文件重试耗尽后，Tool 调用失败，并取消、等待尚未完成的其他文件，
  防止失败返回后仍在后台产生请求和费用。
- 失败前已经完成的 `{stem}_trans.md` 可能保留，便于人工检查；
  `full_trans.md` 只会在本次全部文件成功后写入。
- 重试是当前调用内的短暂恢复机制，不创建持久化任务或可查询的重试队列。

目录翻译可能超过 Host 的默认 Tool 超时。此时应提高 Host 的调用超时，或缩小
`pattern` 的匹配范围；不要依赖已经删除的内存后台任务绕过超时。

## 翻译规则

内置提示词面向英文学术 Markdown / HTML：

- 翻译标题、正文、列表、脚注、图片说明和 HTML 标签内的自然语言。
- 保留公式、LaTeX、代码块、URL、DOI、邮箱、文件路径和标签属性。
- 保持 Markdown / HTML 结构，不总结或省略正文。
- 压缩很长的作者名单，避免逐人翻译。
- 固定发送 `thinking.type=disabled`，不开放 reasoning 参数。

这些规则是固定实现，不是 Tool 参数。修改规则需要修改 Server 代码并重新发布。

## 从 0.1.x 升级

0.2.0 是不兼容重构：

- 删除 `translate_md_dir_start`。
- 删除 `translate_job_status`。
- 删除进程内 `_JOBS` 和不可恢复的 `asyncio.create_task()` 后台任务。
- `translate_md_dir` 返回值由合并文件路径字符串改为结构化完成摘要。
- 删除 Tool 的 `thinking` 和 `concurrency` 参数；thinking 固定关闭，所有调用共享
  Server 进程级 `100` 并发上限。
- 增加可选 `DEEPSEEK_BASE_URL`，用于切换兼容 provider。
- 每份 Markdown 或目录中的每个文件均作为一次完整请求，不设置字符阈值或拆分。
- 增加临时 provider 错误的文件级有限重试，并关闭 SDK 内建重试。
- `translate_md_dir` 直接复用 `translate_md`，删除重复的内部翻译函数。
- 删除重复 User 指令，User message 仅承载原始 Markdown。

升级后，MCP Host 或 Agent 只应发现 `translate_md` 与 `translate_md_dir` 两个 Tool。

## README 与 MCP Tool 描述

README 面向安装者、PyPI 页面和人工审查，不会由 MCP 协议自动发送给模型。MCP
使用方通常只看到 `tools/list` 返回的 Tool 名称、docstring 描述和输入 schema。
因此关键调用约定同时写在代码 docstring 中；README 保留完整的安装、失败语义和
升级说明。

## 开发验证

```bash
cd servers/deepseek-translate
uv sync
uv run pytest -q
uv build
uvx twine check dist/*
```

单元测试使用模拟 API，不消耗 DeepSeek token。实际 API 验证会产生费用，应只用
短文本执行，并确保日志不输出 `DEEPSEEK_API_KEY`。
