Metadata-Version: 2.4
Name: scnet-document-convert
Version: 1.0.5
Summary: MCP stdio service for Scnet document conversion API
Author: Sugon
License-Expression: MIT
Keywords: mcp,document-convert,scnet,文档格式转化
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: mcp>=2.0.0
Requires-Dist: httpx>=0.27.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: mypy>=1.0; extra == "dev"
Requires-Dist: black>=24.0; extra == "dev"
Requires-Dist: mcp>=0.1.0; extra == "dev"

# Scnet 文档转换 MCP 服务

基于 [Scnet 文档格式转化 API](https://api.scnet.cn) 实现的 MCP（Model Context Protocol）服务，提供 stdio 通信模式。

## 功能

- `convert_document`：提交文档转换任务（PDF→Word，图片→Word/PPT）
- `query_task`：批量查询任务状态、结果下载链接或错误信息

## 环境要求

- Python 3.10+
- 有效的 Scnet API Key

## 安装与运行

### 作为 Python 包安装

```bash
pip install -e .        # 开发模式安装
# 或使用 uvx 直接运行（无需安装）
uvx scnet-document-convert
```

## MCP 客户端配置

将以下内容添加到 MCP 客户端配置中（如 `mcp.json` / `claude_desktop_config.json`）：

```json
{
  "mcpServers": {
    "scnet-document-convert": {
      "command": "uvx",
      "args": ["scnet-document-convert"],
      "env": {
        "SCNET_API_KEY": "sk-your-api-key-here",
        "SCNET_API_URL": "https://api.scnet.cn/api/llm/v1"
      },
      "description": "Scnet 文档转换 MCP 服务：支持 PDF/图片转 Word/PPT，并查询任务结果。"
    }
  }
}
```

### 环境变量说明

| 变量名 | 必填 | 默认值 | 说明 |
|--------|------|--------|------|
| `SCNET_API_KEY` | 是 | - | Scnet API Key（Bearer Token） |
| `SCNET_API_URL` | 否 | `https://api.scnet.cn/api/llm/v1` | Scnet API 基础地址 |

## 支持的文件类型

### 文档格式

| 类型 | 扩展名 |
|------|--------|
| PDF | `.pdf` |

### 图片格式

| 类型 | 扩展名 |
|------|--------|
| JPEG | `.jpg` / `.jpeg` |
| PNG | `.png` |
| BMP | `.bmp` |
| TIFF | `.tiff` / `.tif` |
| WebP | `.webp` |

> 说明：请根据转换类型（`ocrType`）选择对应的输入文件：
> - `PDF_TO_WORD`：输入 PDF
> - `IMAGE_TO_WORD` / `IMAGE_TO_PPT`：输入图片

## 接口说明

### 1. `convert_document` —— 提交文档转换任务

提交文档转换任务到 Scnet 服务，返回 `task_id` 用于后续结果查询。

#### 请求参数

**必填参数**

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `ocrType` | `string` | 是 | 转换类型：`PDF_TO_WORD`、`IMAGE_TO_WORD`、`IMAGE_TO_PPT` |
| `filePath` | `string` | 否 | 本地待转换文件绝对路径，与 `fileUrl` 二选一 |
| `fileUrl` | `string` | 否 | 公网可访问的文件下载地址，与 `filePath` 二选一 |

> `filePath` 与 `fileUrl` 必须至少提供一个，否则返回参数错误。

**可选参数**

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `pageIndex` | `string` | 否 | 页码范围，如 `"1"` 或 `"1-5"`，默认全部解析 |
| `isTableCls` | `boolean` | 否 | 是否开启表格细分处理，默认 `false` |
| `isDocOri` | `boolean` | 否 | 是否开启文档方向矫正，默认 `false` |
| `enforceSeal` | `boolean` | 否 | 是否开启强制二次印章检测，默认 `false` |
| `isInlineFormula` | `boolean` | 否 | 是否开启行内公式检测，默认 `false` |

#### 返回结构

| 字段名 | 类型 | 说明 |
|--------|------|------|
| `output` | `object` | 任务提交结果 |
| `output.task_status` | `string` | 任务状态：`pending` / `running` / `succeeded` / `failed` / `unknown` |
| `output.task_id` | `string` | 任务唯一标识，用于后续结果查询 |
| `request_id` | `string` | 请求唯一标识 |

#### 响应示例

**成功响应**

```json
{
  "output": {
    "task_status": "pending",
    "task_id": "0385dc79-5ff8-4d82-bcb6-xxxxxx"
  },
  "request_id": "4909100c-7b5a-9f92-bfe5-xxxxxx"
}
```

**失败响应**

```json
{
  "error": "Burst rate limit exceeded for model xxx",
  "raw_response": {
    "code": "10011",
    "msg": "Burst rate limit exceeded for model xxx"
  }
}
```

---

### 2. `query_task` —— 查询任务状态和结果

批量查询一个或多个文档转换任务的状态、结果下载链接或错误信息。

#### 请求参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `task_ids` | `list[string]` | 是 | 任务 ID 列表（从 `convert_document` 返回的 `task_id`） |

#### 返回结构

| 字段名 | 类型 | 说明 |
|--------|------|------|
| `data` | `array` | 任务结果列表，每个 `task_id` 对应一个元素 |
| `data[].request_id` | `string` | 请求唯一标识 |
| `data[].output` | `object` | 任务结果 |
| `data[].output.task_id` | `string` | 任务唯一标识 |
| `data[].output.task_status` | `string` | 任务状态 |
| `data[].output.submit_time` | `string` | 任务提交时间 |
| `data[].output.end_time` | `string` | 任务结束时间（成功/失败时返回） |
| `data[].output.results` | `array[string]` | 结果文件下载地址列表（成功时返回） |
| `data[].output.error_code` | `string` | 错误码（失败时返回） |
| `data[].output.error_message` | `string` | 错误信息（失败时返回） |

#### 响应示例

**任务成功**

```json
{
  "data": [
    {
      "output": {
        "results": [
          "https://minio.example.com/.../document.docx?X-Amz-Signature=xxxxxx"
        ],
        "task_id": "2056703208598626305",
        "task_status": "succeeded",
        "submit_time": "2026-05-19 19:47:11",
        "end_time": "2026-05-19 19:47:40"
      },
      "request_id": "5e726f4f7d518259"
    }
  ]
}
```

**任务进行中**

```json
{
  "data": [
    {
      "request_id": "8ae698ba-df2d-966c-abcf-xxxxxx",
      "output": {
        "task_id": "e56d806f-76f9-4037-aefa-xxxxxx",
        "task_status": "running",
        "submit_time": "2026-04-20 19:33:50.425"
      }
    }
  ]
}
```

**任务失败**

```json
{
  "data": [
    {
      "request_id": "c61fe158-c0de-40f0-b4d9-964625119ba4",
      "output": {
        "task_id": "86ecf553-d340-4e21-xxxxxxxxx",
        "task_status": "failed",
        "submit_time": "2025-11-11 11:46:28.116",
        "end_time": "2025-11-11 11:46:28.255",
        "error_code": "10011",
        "error_message": "Burst rate limit exceeded for model xxx"
      }
    }
  ]
}
```

## 任务状态说明

| 状态 | 描述 |
|------|------|
| `pending` | 任务已提交，等待处理 |
| `running` | 任务处理中 |
| `succeeded` | 任务处理成功（`results` 包含下载地址） |
| `failed` | 任务处理失败（`error_code` 和 `error_message` 提供详情） |
| `unknown` | 任务不存在或未知状态 |

## 错误码说明

| 错误码 | 描述 |
|--------|------|
| `10001` | Unknown error |
| `10002` | Unsupported modal type xxx |
| `10003` | Unsupported provider xxx |
| `10004` | Unsupported model xxx |
| `10005` | Model xxx not found |
| `10006` | Task not found |
| `10007` | Concurrency conflict for request, please try again later |
| `10008` | Provider xxx process error |
| `10009` | Model xxx route failed |
| `10010` | Illegal content detected by content approval |
| `10011` | Burst rate limit exceeded for model xxx |
| `10012` | An system error has occurred, please try again later |
| `10013` | Parameter illegal |
| `10014` | Incorrect API key provided |
| `10015` | Task timeout, please try again later |

## 使用示例

### 本地调用示例

```bash
# 安装依赖
pip install -e .

# 设置环境变量并启动服务
export SCNET_API_KEY="sk-your-api-key-here"
python server.py
```

### Python 客户端调用

参考项目中的 `client_test.py`，示例如下：

```python
import asyncio
import json
import os
from mcp.client.stdio import stdio_client, StdioServerParameters
from mcp.client.session import ClientSession

async def main():
    server_params = StdioServerParameters(
        command="python",
        args=["server.py"],
        env={**os.environ, "SCNET_API_KEY": "sk-your-api-key-here"},
    )

    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # 提交任务
            result = await session.call_tool(
                "convert_document",
                arguments={
                    "ocrType": "PDF_TO_WORD",
                    "fileUrl": "https://example.com/document.pdf",
                    "pageIndex": "1-3",
                },
            )
            resp = json.loads(result.content[0].text)
            task_id = resp["output"]["task_id"]
            print(f"Task ID: {task_id}")

            # 查询任务
            query_result = await session.call_tool(
                "query_task",
                arguments={"task_ids": [task_id]},
            )
            print(query_result.content[0].text)

asyncio.run(main())
```

## 注意事项

1. `filePath` 与 `fileUrl` 二选一，必须至少提供一个。
2. 转换后的文件下载地址为临时授权链接，请及时下载使用。
3. 任务提交后需要通过 `query_task` 轮询查询结果。
4. 请根据 `ocrType` 选择正确的输入文件类型。
