lightpdf-aipdf-mcp 0.1.87__py3-none-any.whl → 0.1.89__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lightpdf_aipdf_mcp/server.py +132 -11
- lightpdf_aipdf_mcp/translator.py +101 -0
- {lightpdf_aipdf_mcp-0.1.87.dist-info → lightpdf_aipdf_mcp-0.1.89.dist-info}/METADATA +1 -1
- lightpdf_aipdf_mcp-0.1.89.dist-info/RECORD +10 -0
- lightpdf_aipdf_mcp-0.1.87.dist-info/RECORD +0 -9
- {lightpdf_aipdf_mcp-0.1.87.dist-info → lightpdf_aipdf_mcp-0.1.89.dist-info}/WHEEL +0 -0
- {lightpdf_aipdf_mcp-0.1.87.dist-info → lightpdf_aipdf_mcp-0.1.89.dist-info}/entry_points.txt +0 -0
lightpdf_aipdf_mcp/server.py
CHANGED
@@ -19,6 +19,7 @@ import mcp.types as types
|
|
19
19
|
from .common import BaseResult, Logger, FileHandler
|
20
20
|
from .converter import Converter, ConversionResult
|
21
21
|
from .editor import Editor, EditResult
|
22
|
+
from .translator import Translator, TranslateResult
|
22
23
|
|
23
24
|
# 加载环境变量
|
24
25
|
load_dotenv()
|
@@ -277,9 +278,29 @@ async def process_tool_call(
|
|
277
278
|
"""
|
278
279
|
file_handler = FileHandler(logger)
|
279
280
|
editor = Editor(logger, file_handler)
|
280
|
-
|
281
|
+
# 新增:翻译操作分支
|
282
|
+
if operation_config.get("is_translate_operation"):
|
283
|
+
translator = Translator(logger, file_handler)
|
284
|
+
extra_params = operation_config.get("extra_params", {})
|
285
|
+
|
286
|
+
results = await process_batch_files(
|
287
|
+
file_objects,
|
288
|
+
logger,
|
289
|
+
lambda file_path, password, original_name: translator.translate_pdf(
|
290
|
+
file_path=file_path,
|
291
|
+
source=extra_params.get("source", "auto"),
|
292
|
+
target=extra_params.get("target"),
|
293
|
+
output_type=extra_params.get("output_type", "mono"),
|
294
|
+
password=password,
|
295
|
+
original_name=original_name
|
296
|
+
),
|
297
|
+
"PDF翻译"
|
298
|
+
)
|
299
|
+
|
300
|
+
report_msg = generate_result_report(results)
|
301
|
+
|
281
302
|
# 根据操作类型选择不同的处理逻辑
|
282
|
-
|
303
|
+
elif operation_config.get("is_edit_operation"):
|
283
304
|
# 编辑操作
|
284
305
|
edit_type = operation_config.get("edit_type", "")
|
285
306
|
extra_params = operation_config.get("extra_params")
|
@@ -397,7 +418,7 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
397
418
|
return [
|
398
419
|
types.Tool(
|
399
420
|
name="convert_document",
|
400
|
-
description="Document format conversion tool.\n\nPDF can be converted to: DOCX/XLSX/PPTX/Images (including long images)/HTML/TXT (for text extraction)/CSV;\nOther formats can be converted to PDF: DOCX/XLSX/PPTX/Images/CAD/CAJ/OFD.\n\nDoes not support creating files from content",
|
421
|
+
description="Document format conversion tool.\n\nPDF can be converted to: DOCX/XLSX/PPTX/Images (including long images)/HTML/TXT (for text extraction)/CSV;\nOther formats can be converted to PDF: DOCX/XLSX/PPTX/Images/CAD/CAJ/OFD.\n\nDoes not support creating files from content.",
|
401
422
|
inputSchema={
|
402
423
|
"type": "object",
|
403
424
|
"properties": {
|
@@ -408,20 +429,20 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
408
429
|
"properties": {
|
409
430
|
"path": {
|
410
431
|
"type": "string",
|
411
|
-
"description": "File URL, must include protocol, supports http/https/oss"
|
432
|
+
"description": "File URL, must include protocol, supports http/https/oss."
|
412
433
|
},
|
413
434
|
"password": {
|
414
435
|
"type": "string",
|
415
|
-
"description": "Document password, required if the document is password-protected"
|
436
|
+
"description": "Document password, required if the document is password-protected."
|
416
437
|
},
|
417
438
|
"name": {
|
418
439
|
"type": "string",
|
419
|
-
"description": "Original filename of the document"
|
440
|
+
"description": "Original filename of the document."
|
420
441
|
}
|
421
442
|
},
|
422
443
|
"required": ["path"]
|
423
444
|
},
|
424
|
-
"description": "List of files to convert, each containing path and optional password"
|
445
|
+
"description": "List of files to convert, each containing path and optional password."
|
425
446
|
},
|
426
447
|
"format": {
|
427
448
|
"type": "string",
|
@@ -432,7 +453,7 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
432
453
|
"type": "integer",
|
433
454
|
"enum": [0, 1],
|
434
455
|
"default": 0,
|
435
|
-
"description": "Whether to merge results: 1 = merge all, 0 = separate. Only valid for: PDF to Excel (1: all pages to one sheet, 0: each page to a sheet), PDF to Image (1: merge to long image, 0: each page to an image), Image to PDF (1: all images to one PDF, 0: each image to a PDF)"
|
456
|
+
"description": "Whether to merge results: 1 = merge all, 0 = separate. Only valid for: PDF to Excel (1: all pages to one sheet, 0: each page to a sheet), PDF to Image (1: merge to long image, 0: each page to an image), Image to PDF (1: all images to one PDF, 0: each image to a PDF)."
|
436
457
|
}
|
437
458
|
},
|
438
459
|
"required": ["files", "format"]
|
@@ -964,7 +985,7 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
964
985
|
),
|
965
986
|
types.Tool(
|
966
987
|
name="flatten_pdf",
|
967
|
-
description="Flatten PDF files (convert editable elements such as text, form fields, annotations, and layers into non-editable static content or fixed content)",
|
988
|
+
description="Flatten PDF files (convert editable elements such as text, form fields, annotations, and layers into non-editable static content or fixed content).",
|
968
989
|
inputSchema={
|
969
990
|
"type": "object",
|
970
991
|
"properties": {
|
@@ -1025,7 +1046,96 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
1025
1046
|
},
|
1026
1047
|
"required": ["files"]
|
1027
1048
|
}
|
1028
|
-
)
|
1049
|
+
),
|
1050
|
+
types.Tool(
|
1051
|
+
name="translate_pdf",
|
1052
|
+
description="Translate PDF documents from a source language to a target language. Supports mono (target only) or dual (source/target bilingual) output.",
|
1053
|
+
inputSchema={
|
1054
|
+
"type": "object",
|
1055
|
+
"properties": {
|
1056
|
+
"files": {
|
1057
|
+
"type": "array",
|
1058
|
+
"items": {
|
1059
|
+
"type": "object",
|
1060
|
+
"properties": {
|
1061
|
+
"path": {
|
1062
|
+
"type": "string",
|
1063
|
+
"description": "PDF file URL, must include protocol, supports http/https/oss."
|
1064
|
+
},
|
1065
|
+
"password": {
|
1066
|
+
"type": "string",
|
1067
|
+
"description": "PDF document password, required if the document is password-protected."
|
1068
|
+
},
|
1069
|
+
"name": {
|
1070
|
+
"type": "string",
|
1071
|
+
"description": "Original filename of the document."
|
1072
|
+
}
|
1073
|
+
},
|
1074
|
+
"required": ["path"]
|
1075
|
+
},
|
1076
|
+
"description": "List of PDF files to translate, each containing path and optional password."
|
1077
|
+
},
|
1078
|
+
"source": {
|
1079
|
+
"type": "string",
|
1080
|
+
"description": "Source language. Supports 'auto' for automatic detection.",
|
1081
|
+
"enum": ["auto", "ar", "bg", "cz", "da", "de", "el", "en", "es", "fi", "fr", "hbs", "hi", "hu", "id", "it", "ja", "ko", "ms", "nl", "no", "pl", "pt", "ru", "sl", "sv", "th", "tr", "vi", "zh", "zh-tw"],
|
1082
|
+
"default": "auto"
|
1083
|
+
},
|
1084
|
+
"target": {
|
1085
|
+
"type": "string",
|
1086
|
+
"description": "Target language. Must be specified.",
|
1087
|
+
"enum": ["ar", "bg", "cz", "da", "de", "el", "en", "es", "fi", "fr", "hbs", "hi", "hu", "id", "it", "ja", "ko", "ms", "nl", "no", "pl", "pt", "ru", "sl", "sv", "th", "tr", "vi", "zh", "zh-tw"]
|
1088
|
+
},
|
1089
|
+
"output_type": {
|
1090
|
+
"type": "string",
|
1091
|
+
"description": "Output type: 'mono' for target language only, 'dual' for source/target bilingual output.",
|
1092
|
+
"enum": ["mono", "dual"],
|
1093
|
+
"default": "mono"
|
1094
|
+
}
|
1095
|
+
},
|
1096
|
+
"required": ["files", "target"]
|
1097
|
+
}
|
1098
|
+
),
|
1099
|
+
types.Tool(
|
1100
|
+
name="resize-pdf",
|
1101
|
+
description="Resize PDF pages. You can specify the target page size (a0/a1/a2/a3/a4/a5/a6/letter) and/or the image resolution (dpi, e.g., 72). If not set, the corresponding property will not be changed.",
|
1102
|
+
inputSchema={
|
1103
|
+
"type": "object",
|
1104
|
+
"properties": {
|
1105
|
+
"files": {
|
1106
|
+
"type": "array",
|
1107
|
+
"items": {
|
1108
|
+
"type": "object",
|
1109
|
+
"properties": {
|
1110
|
+
"path": {
|
1111
|
+
"type": "string",
|
1112
|
+
"description": "PDF file URL to resize, must include protocol, supports http/https/oss"
|
1113
|
+
},
|
1114
|
+
"password": {
|
1115
|
+
"type": "string",
|
1116
|
+
"description": "PDF document password, required if the document is password-protected"
|
1117
|
+
},
|
1118
|
+
"name": {
|
1119
|
+
"type": "string",
|
1120
|
+
"description": "Original filename of the document"
|
1121
|
+
}
|
1122
|
+
},
|
1123
|
+
"required": ["path"]
|
1124
|
+
},
|
1125
|
+
"description": "List of PDF files to resize, each containing path and optional password"
|
1126
|
+
},
|
1127
|
+
"page_size": {
|
1128
|
+
"type": "string",
|
1129
|
+
"description": "Target page size. Any valid page size name is supported (e.g., a4, letter, legal, etc.), or use width,height in points (pt, e.g., 595,842). If not set, page size will not be changed."
|
1130
|
+
},
|
1131
|
+
"resolution": {
|
1132
|
+
"type": "integer",
|
1133
|
+
"description": "Image resolution (dpi), e.g., 72. If not set, resolution will not be changed."
|
1134
|
+
}
|
1135
|
+
},
|
1136
|
+
"required": ["files"]
|
1137
|
+
}
|
1138
|
+
),
|
1029
1139
|
]
|
1030
1140
|
|
1031
1141
|
@app.call_tool()
|
@@ -1052,6 +1162,11 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
|
|
1052
1162
|
"format": "flatten-pdf", # 固定format
|
1053
1163
|
"is_edit_operation": False
|
1054
1164
|
},
|
1165
|
+
"resize-pdf": {
|
1166
|
+
"format": "resize-pdf",
|
1167
|
+
"is_edit_operation": False,
|
1168
|
+
"param_keys": ["page_size", "resolution"]
|
1169
|
+
},
|
1055
1170
|
"unlock_pdf": {
|
1056
1171
|
"edit_type": "decrypt", # 编辑类型
|
1057
1172
|
"is_edit_operation": True, # 标记为编辑操作
|
@@ -1105,6 +1220,10 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
|
|
1105
1220
|
"is_edit_operation": True,
|
1106
1221
|
"param_keys": [] # 不暴露provider
|
1107
1222
|
},
|
1223
|
+
"translate_pdf": {
|
1224
|
+
"is_translate_operation": True,
|
1225
|
+
"param_keys": ["source", "target", "output_type"]
|
1226
|
+
},
|
1108
1227
|
}
|
1109
1228
|
|
1110
1229
|
DEFAULTS = {
|
@@ -1120,7 +1239,9 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
|
|
1120
1239
|
"merge_all": 1,
|
1121
1240
|
"angle": 90,
|
1122
1241
|
"pages": "all", # 更新默认值为"all"
|
1123
|
-
"format": "png" # 提取图片的默认格式
|
1242
|
+
"format": "png", # 提取图片的默认格式
|
1243
|
+
"page_size": "",
|
1244
|
+
"resolution": 0,
|
1124
1245
|
}
|
1125
1246
|
|
1126
1247
|
if name in TOOL_CONFIG:
|
@@ -0,0 +1,101 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
import os
|
3
|
+
import httpx
|
4
|
+
from typing import Optional, Dict, Any
|
5
|
+
from .common import Logger, BaseResult, FileHandler, BaseApiClient
|
6
|
+
|
7
|
+
@dataclass
|
8
|
+
class TranslateResult(BaseResult):
|
9
|
+
"""翻译结果数据类"""
|
10
|
+
pass
|
11
|
+
|
12
|
+
class Translator(BaseApiClient):
|
13
|
+
"""PDF文档翻译器"""
|
14
|
+
def __init__(self, logger: Logger, file_handler: FileHandler):
|
15
|
+
super().__init__(logger, file_handler)
|
16
|
+
api_endpoint = os.getenv("API_ENDPOINT", "techsz.aoscdn.com/api")
|
17
|
+
self.api_base_url = f"https://{api_endpoint}/tasks/document/transdocument-local"
|
18
|
+
|
19
|
+
async def translate_pdf(self, file_path: str, source: str, target: str, output_type: str = "mono", password: Optional[str] = None, original_name: Optional[str] = None) -> TranslateResult:
|
20
|
+
if not self.api_key:
|
21
|
+
await self.logger.error("未找到API_KEY。请在客户端配置API_KEY环境变量。")
|
22
|
+
return TranslateResult(success=False, file_path=file_path, error_message="未找到API_KEY", original_name=original_name)
|
23
|
+
|
24
|
+
# 构建API参数
|
25
|
+
extra_params = {
|
26
|
+
"source": source or "auto",
|
27
|
+
"target": target,
|
28
|
+
"output_type": output_type or "mono"
|
29
|
+
}
|
30
|
+
if password:
|
31
|
+
extra_params["password"] = password
|
32
|
+
if original_name:
|
33
|
+
extra_params["filename"] = os.path.splitext(original_name)[0]
|
34
|
+
|
35
|
+
async with httpx.AsyncClient(timeout=3600.0) as client:
|
36
|
+
task_id = None
|
37
|
+
try:
|
38
|
+
# 创建翻译任务
|
39
|
+
task_id = await self._create_task(client, file_path, extra_params)
|
40
|
+
# 等待任务完成
|
41
|
+
download_url = await self._wait_for_task(client, task_id, "翻译")
|
42
|
+
|
43
|
+
await self.logger.log("info", "翻译完成。可通过下载链接获取结果文件。")
|
44
|
+
return TranslateResult(
|
45
|
+
success=True,
|
46
|
+
file_path=file_path,
|
47
|
+
error_message=None,
|
48
|
+
download_url=download_url,
|
49
|
+
original_name=original_name,
|
50
|
+
task_id=task_id
|
51
|
+
)
|
52
|
+
except Exception as e:
|
53
|
+
return TranslateResult(
|
54
|
+
success=False,
|
55
|
+
file_path=file_path,
|
56
|
+
error_message=str(e),
|
57
|
+
download_url=None,
|
58
|
+
original_name=original_name,
|
59
|
+
task_id=task_id
|
60
|
+
)
|
61
|
+
|
62
|
+
async def _create_task(self, client: httpx.AsyncClient, file_path: str, extra_params: dict = None) -> str:
|
63
|
+
await self.logger.log("info", "正在提交翻译任务...")
|
64
|
+
headers = {"X-API-KEY": self.api_key}
|
65
|
+
data = {}
|
66
|
+
if extra_params:
|
67
|
+
data.update(extra_params)
|
68
|
+
# 检查是否为OSS路径
|
69
|
+
if self.file_handler.is_oss_id(file_path):
|
70
|
+
data["resource_id"] = file_path.split("oss_id://")[1]
|
71
|
+
headers["Content-Type"] = "application/json"
|
72
|
+
response = await client.post(
|
73
|
+
self.api_base_url,
|
74
|
+
json=data,
|
75
|
+
headers=headers
|
76
|
+
)
|
77
|
+
elif self.file_handler.is_url(file_path):
|
78
|
+
# arxiv.org/pdf/特殊处理
|
79
|
+
if isinstance(file_path, str) and "arxiv.org/pdf/" in file_path:
|
80
|
+
from urllib.parse import urlparse, urlunparse
|
81
|
+
url_obj = urlparse(file_path)
|
82
|
+
if not url_obj.path.endswith(".pdf"):
|
83
|
+
new_path = url_obj.path + ".pdf"
|
84
|
+
file_path = urlunparse(url_obj._replace(path=new_path))
|
85
|
+
data["url"] = file_path
|
86
|
+
headers["Content-Type"] = "application/json"
|
87
|
+
response = await client.post(
|
88
|
+
self.api_base_url,
|
89
|
+
json=data,
|
90
|
+
headers=headers
|
91
|
+
)
|
92
|
+
else:
|
93
|
+
with open(file_path, "rb") as f:
|
94
|
+
files = {"file": f}
|
95
|
+
response = await client.post(
|
96
|
+
self.api_base_url,
|
97
|
+
files=files,
|
98
|
+
data=data,
|
99
|
+
headers=headers
|
100
|
+
)
|
101
|
+
return await self._handle_api_response(response, "创建翻译任务")
|
@@ -0,0 +1,10 @@
|
|
1
|
+
lightpdf_aipdf_mcp/__init__.py,sha256=PPnAgpvJLYLVOTxnHDmJAulFnHJD6wuTwS6tRGjqq6s,141
|
2
|
+
lightpdf_aipdf_mcp/common.py,sha256=_UO1f6S9Qr_3k6u5iBpdVDpvTK5U-tHEpu9KsDGqV8Y,6635
|
3
|
+
lightpdf_aipdf_mcp/converter.py,sha256=f0gS8tAQlJ8uwJUVUmd9nAA4O9m558e9lAT2B_MxmIo,15135
|
4
|
+
lightpdf_aipdf_mcp/editor.py,sha256=9teOqi2y2JbjcCI-kUhYpSXL-F75i7Mfr9E20KKyZP0,29909
|
5
|
+
lightpdf_aipdf_mcp/server.py,sha256=uLb_gOB1uP6eN53BDMp2qLXQk_9GjKJjYJDSjb75iyo,62324
|
6
|
+
lightpdf_aipdf_mcp/translator.py,sha256=NbFDz-mZSD4qCNQVyV0W_0x6xXwbqs_7FiBU13JAxZs,4243
|
7
|
+
lightpdf_aipdf_mcp-0.1.89.dist-info/METADATA,sha256=O87D9f3cGiHt0FbLwdDLb_s5jD-v6c4MBK7X7HbAkkI,8119
|
8
|
+
lightpdf_aipdf_mcp-0.1.89.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
+
lightpdf_aipdf_mcp-0.1.89.dist-info/entry_points.txt,sha256=X7TGUe52N4sYH-tYt0YUGApeJgw-efQlZA6uAZmlmr4,63
|
10
|
+
lightpdf_aipdf_mcp-0.1.89.dist-info/RECORD,,
|
@@ -1,9 +0,0 @@
|
|
1
|
-
lightpdf_aipdf_mcp/__init__.py,sha256=PPnAgpvJLYLVOTxnHDmJAulFnHJD6wuTwS6tRGjqq6s,141
|
2
|
-
lightpdf_aipdf_mcp/common.py,sha256=_UO1f6S9Qr_3k6u5iBpdVDpvTK5U-tHEpu9KsDGqV8Y,6635
|
3
|
-
lightpdf_aipdf_mcp/converter.py,sha256=f0gS8tAQlJ8uwJUVUmd9nAA4O9m558e9lAT2B_MxmIo,15135
|
4
|
-
lightpdf_aipdf_mcp/editor.py,sha256=9teOqi2y2JbjcCI-kUhYpSXL-F75i7Mfr9E20KKyZP0,29909
|
5
|
-
lightpdf_aipdf_mcp/server.py,sha256=rn9QvHr5E-IaCx2V1pXuKtiXKf1-b3zns-VoxH6x2UE,56220
|
6
|
-
lightpdf_aipdf_mcp-0.1.87.dist-info/METADATA,sha256=4WUxVWAPL13KBXsEKfI3iHn4qBxof-GMBNVcXZtLmkQ,8119
|
7
|
-
lightpdf_aipdf_mcp-0.1.87.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
8
|
-
lightpdf_aipdf_mcp-0.1.87.dist-info/entry_points.txt,sha256=X7TGUe52N4sYH-tYt0YUGApeJgw-efQlZA6uAZmlmr4,63
|
9
|
-
lightpdf_aipdf_mcp-0.1.87.dist-info/RECORD,,
|
File without changes
|
{lightpdf_aipdf_mcp-0.1.87.dist-info → lightpdf_aipdf_mcp-0.1.89.dist-info}/entry_points.txt
RENAMED
File without changes
|