lightpdf-aipdf-mcp 0.1.86__py3-none-any.whl → 0.1.88__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/converter.py +7 -0
- lightpdf_aipdf_mcp/editor.py +62 -1
- lightpdf_aipdf_mcp/server.py +174 -23
- lightpdf_aipdf_mcp/translator.py +94 -0
- {lightpdf_aipdf_mcp-0.1.86.dist-info → lightpdf_aipdf_mcp-0.1.88.dist-info}/METADATA +1 -1
- lightpdf_aipdf_mcp-0.1.88.dist-info/RECORD +10 -0
- lightpdf_aipdf_mcp-0.1.86.dist-info/RECORD +0 -9
- {lightpdf_aipdf_mcp-0.1.86.dist-info → lightpdf_aipdf_mcp-0.1.88.dist-info}/WHEEL +0 -0
- {lightpdf_aipdf_mcp-0.1.86.dist-info → lightpdf_aipdf_mcp-0.1.88.dist-info}/entry_points.txt +0 -0
lightpdf_aipdf_mcp/converter.py
CHANGED
@@ -320,6 +320,13 @@ class Converter(BaseApiClient):
|
|
320
320
|
)
|
321
321
|
# 检查是否为URL路径
|
322
322
|
elif self.file_handler.is_url(file_path):
|
323
|
+
# arxiv.org/pdf/特殊处理
|
324
|
+
if isinstance(file_path, str) and "arxiv.org/pdf/" in file_path:
|
325
|
+
from urllib.parse import urlparse, urlunparse
|
326
|
+
url_obj = urlparse(file_path)
|
327
|
+
if not url_obj.path.endswith(".pdf"):
|
328
|
+
new_path = url_obj.path + ".pdf"
|
329
|
+
file_path = urlunparse(url_obj._replace(path=new_path))
|
323
330
|
data["url"] = file_path
|
324
331
|
# 使用JSON方式时添加Content-Type
|
325
332
|
headers["Content-Type"] = "application/json"
|
lightpdf_aipdf_mcp/editor.py
CHANGED
@@ -293,7 +293,7 @@ class Editor(BaseApiClient):
|
|
293
293
|
# 调用edit_pdf方法处理API请求
|
294
294
|
return await self.edit_pdf(file_path, EditType.DECRYPT, {}, password, original_name)
|
295
295
|
|
296
|
-
async def
|
296
|
+
async def add_text_watermark(
|
297
297
|
self,
|
298
298
|
file_path: str,
|
299
299
|
text: str,
|
@@ -359,6 +359,53 @@ class Editor(BaseApiClient):
|
|
359
359
|
# 调用edit_pdf方法处理API请求
|
360
360
|
return await self.edit_pdf(file_path, EditType.ADD_WATERMARK, extra_params, password, original_name)
|
361
361
|
|
362
|
+
async def add_image_watermark(
|
363
|
+
self,
|
364
|
+
file_path: str,
|
365
|
+
image_url: str,
|
366
|
+
position: str = "center",
|
367
|
+
opacity: float = 0.7,
|
368
|
+
range: str = "",
|
369
|
+
layout: Optional[str] = None,
|
370
|
+
password: Optional[str] = None,
|
371
|
+
original_name: Optional[str] = None
|
372
|
+
) -> EditResult:
|
373
|
+
"""为PDF文件添加图片水印
|
374
|
+
|
375
|
+
Args:
|
376
|
+
file_path: 要添加水印的PDF文件路径
|
377
|
+
image_url: 水印图片的URL,必须包含协议(http/https/oss)
|
378
|
+
position: 水印位置,如"top", "center", "diagonal"等,默认"center"
|
379
|
+
opacity: 透明度,0.0-1.0,默认0.7
|
380
|
+
range: 页面范围,例如 "1,3,5-7" 或空字符串表示所有页面
|
381
|
+
layout: 布局方式:"on"=在内容上,"under"=在内容下,默认"on"
|
382
|
+
password: 文档密码,如果文档受密码保护,则需要提供(可选)
|
383
|
+
original_name: 原始文件名(可选)
|
384
|
+
|
385
|
+
Returns:
|
386
|
+
EditResult: 添加图片水印结果
|
387
|
+
"""
|
388
|
+
# 验证输入文件是否为PDF
|
389
|
+
if not await self._validate_pdf_file(file_path):
|
390
|
+
return EditResult(success=False, file_path=file_path, error_message="非PDF文件", original_name=original_name)
|
391
|
+
if not image_url:
|
392
|
+
await self.logger.error("水印图片URL不能为空")
|
393
|
+
return EditResult(success=False, file_path=file_path, error_message="水印图片URL不能为空", original_name=original_name)
|
394
|
+
# 构建API参数
|
395
|
+
extra_params = {
|
396
|
+
"edit_type": "image",
|
397
|
+
"image_url": image_url,
|
398
|
+
"position": position,
|
399
|
+
"opacity": opacity,
|
400
|
+
"range": range
|
401
|
+
}
|
402
|
+
if layout:
|
403
|
+
extra_params["layout"] = layout
|
404
|
+
# 记录操作描述
|
405
|
+
await self._log_operation("为PDF添加图片水印", f"图片: {image_url}, 位置: {position}, 透明度: {opacity}")
|
406
|
+
# 调用edit_pdf方法处理API请求
|
407
|
+
return await self.edit_pdf(file_path, EditType.ADD_WATERMARK, extra_params, password, original_name)
|
408
|
+
|
362
409
|
async def remove_margin(self, file_path: str, password: Optional[str] = None, original_name: Optional[str] = None) -> EditResult:
|
363
410
|
"""去除PDF文件的白边
|
364
411
|
|
@@ -523,6 +570,13 @@ class Editor(BaseApiClient):
|
|
523
570
|
)
|
524
571
|
# 检查是否为URL路径
|
525
572
|
elif self.file_handler.is_url(file_path):
|
573
|
+
# arxiv.org/pdf/特殊处理
|
574
|
+
if isinstance(file_path, str) and "arxiv.org/pdf/" in file_path:
|
575
|
+
from urllib.parse import urlparse, urlunparse
|
576
|
+
url_obj = urlparse(file_path)
|
577
|
+
if not url_obj.path.endswith(".pdf"):
|
578
|
+
new_path = url_obj.path + ".pdf"
|
579
|
+
file_path = urlunparse(url_obj._replace(path=new_path))
|
526
580
|
# 使用JSON方式时添加Content-Type
|
527
581
|
headers["Content-Type"] = "application/json"
|
528
582
|
data["url"] = file_path
|
@@ -580,6 +634,13 @@ class Editor(BaseApiClient):
|
|
580
634
|
input_item["password"] = password
|
581
635
|
url_inputs.append(input_item)
|
582
636
|
elif self.file_handler.is_url(file_path):
|
637
|
+
# arxiv.org/pdf/特殊处理
|
638
|
+
if isinstance(file_path, str) and "arxiv.org/pdf/" in file_path:
|
639
|
+
from urllib.parse import urlparse, urlunparse
|
640
|
+
url_obj = urlparse(file_path)
|
641
|
+
if not url_obj.path.endswith(".pdf"):
|
642
|
+
new_path = url_obj.path + ".pdf"
|
643
|
+
file_path = urlunparse(url_obj._replace(path=new_path))
|
583
644
|
# 对于URL或OSS路径,添加到inputs数组
|
584
645
|
input_item = {"url": file_path}
|
585
646
|
if password:
|
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()
|
@@ -154,10 +155,10 @@ async def process_edit_file(
|
|
154
155
|
"""处理单个文件编辑"""
|
155
156
|
if edit_type == "decrypt":
|
156
157
|
return await editor.decrypt_pdf(file_path, password, original_name)
|
157
|
-
elif edit_type == "
|
158
|
-
return await editor.
|
158
|
+
elif edit_type == "add_text_watermark":
|
159
|
+
return await editor.add_text_watermark(
|
159
160
|
file_path=file_path,
|
160
|
-
text=extra_params.get("text", "
|
161
|
+
text=extra_params.get("text", "文本水印"),
|
161
162
|
position=extra_params.get("position", "center"),
|
162
163
|
opacity=extra_params.get("opacity", 1.0),
|
163
164
|
range=extra_params.get("range", ""),
|
@@ -168,6 +169,17 @@ async def process_edit_file(
|
|
168
169
|
password=password,
|
169
170
|
original_name=original_name
|
170
171
|
)
|
172
|
+
elif edit_type == "add_image_watermark":
|
173
|
+
return await editor.add_image_watermark(
|
174
|
+
file_path=file_path,
|
175
|
+
image_url=extra_params.get("image_url"),
|
176
|
+
position=extra_params.get("position", "center"),
|
177
|
+
opacity=extra_params.get("opacity", 0.7),
|
178
|
+
range=extra_params.get("range", ""),
|
179
|
+
layout=extra_params.get("layout", "on"),
|
180
|
+
password=password,
|
181
|
+
original_name=original_name
|
182
|
+
)
|
171
183
|
elif edit_type == "encrypt":
|
172
184
|
return await editor.encrypt_pdf(
|
173
185
|
file_path=file_path,
|
@@ -266,9 +278,29 @@ async def process_tool_call(
|
|
266
278
|
"""
|
267
279
|
file_handler = FileHandler(logger)
|
268
280
|
editor = Editor(logger, file_handler)
|
269
|
-
|
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
|
+
|
270
302
|
# 根据操作类型选择不同的处理逻辑
|
271
|
-
|
303
|
+
elif operation_config.get("is_edit_operation"):
|
272
304
|
# 编辑操作
|
273
305
|
edit_type = operation_config.get("edit_type", "")
|
274
306
|
extra_params = operation_config.get("extra_params")
|
@@ -276,7 +308,8 @@ async def process_tool_call(
|
|
276
308
|
# 获取操作描述
|
277
309
|
edit_map = {
|
278
310
|
"decrypt": "解密",
|
279
|
-
"
|
311
|
+
"add_text_watermark": "添加文本水印",
|
312
|
+
"add_image_watermark": "添加图片水印",
|
280
313
|
"encrypt": "加密",
|
281
314
|
"compress": "压缩",
|
282
315
|
"split": "拆分",
|
@@ -385,7 +418,7 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
385
418
|
return [
|
386
419
|
types.Tool(
|
387
420
|
name="convert_document",
|
388
|
-
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.",
|
389
422
|
inputSchema={
|
390
423
|
"type": "object",
|
391
424
|
"properties": {
|
@@ -396,20 +429,20 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
396
429
|
"properties": {
|
397
430
|
"path": {
|
398
431
|
"type": "string",
|
399
|
-
"description": "File URL, must include protocol, supports http/https/oss"
|
432
|
+
"description": "File URL, must include protocol, supports http/https/oss."
|
400
433
|
},
|
401
434
|
"password": {
|
402
435
|
"type": "string",
|
403
|
-
"description": "Document password, required if the document is password-protected"
|
436
|
+
"description": "Document password, required if the document is password-protected."
|
404
437
|
},
|
405
438
|
"name": {
|
406
439
|
"type": "string",
|
407
|
-
"description": "Original filename of the document"
|
440
|
+
"description": "Original filename of the document."
|
408
441
|
}
|
409
442
|
},
|
410
443
|
"required": ["path"]
|
411
444
|
},
|
412
|
-
"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."
|
413
446
|
},
|
414
447
|
"format": {
|
415
448
|
"type": "string",
|
@@ -420,7 +453,7 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
420
453
|
"type": "integer",
|
421
454
|
"enum": [0, 1],
|
422
455
|
"default": 0,
|
423
|
-
"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)."
|
424
457
|
}
|
425
458
|
},
|
426
459
|
"required": ["files", "format"]
|
@@ -509,7 +542,7 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
509
542
|
}
|
510
543
|
),
|
511
544
|
types.Tool(
|
512
|
-
name="
|
545
|
+
name="add_text_watermark",
|
513
546
|
description="Add text watermarks to PDF files.",
|
514
547
|
inputSchema={
|
515
548
|
"type": "object",
|
@@ -521,7 +554,7 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
521
554
|
"properties": {
|
522
555
|
"path": {
|
523
556
|
"type": "string",
|
524
|
-
"description": "PDF file URL to add watermark to, must include protocol, supports http/https/oss"
|
557
|
+
"description": "PDF file URL to add text watermark to, must include protocol, supports http/https/oss"
|
525
558
|
},
|
526
559
|
"password": {
|
527
560
|
"type": "string",
|
@@ -534,7 +567,7 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
534
567
|
},
|
535
568
|
"required": ["path"]
|
536
569
|
},
|
537
|
-
"description": "List of PDF files to add watermarks to, each containing path and optional password"
|
570
|
+
"description": "List of PDF files to add text watermarks to, each containing path and optional password"
|
538
571
|
},
|
539
572
|
"text": {
|
540
573
|
"type": "string",
|
@@ -542,7 +575,7 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
542
575
|
},
|
543
576
|
"position": {
|
544
577
|
"type": "string",
|
545
|
-
"description": "
|
578
|
+
"description": "Text watermark position: top-left(topleft), top-center(top), top-right(topright), left(left), center(center), right(right), bottom-left(bottomleft), bottom(bottom), bottom-right(bottomright), diagonal(diagonal, -45 degrees), reverse-diagonal(reverse-diagonal, 45 degrees)",
|
546
579
|
"enum": ["topleft", "top", "topright", "left", "center", "right",
|
547
580
|
"bottomleft", "bottom", "bottomright", "diagonal", "reverse-diagonal"],
|
548
581
|
"default": "center"
|
@@ -580,6 +613,66 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
580
613
|
"required": ["files", "text", "position"]
|
581
614
|
}
|
582
615
|
),
|
616
|
+
types.Tool(
|
617
|
+
name="add_image_watermark",
|
618
|
+
description="Add image watermarks to PDF files.",
|
619
|
+
inputSchema={
|
620
|
+
"type": "object",
|
621
|
+
"properties": {
|
622
|
+
"files": {
|
623
|
+
"type": "array",
|
624
|
+
"items": {
|
625
|
+
"type": "object",
|
626
|
+
"properties": {
|
627
|
+
"path": {
|
628
|
+
"type": "string",
|
629
|
+
"description": "PDF file URL to add image watermark to, must include protocol, supports http/https/oss"
|
630
|
+
},
|
631
|
+
"password": {
|
632
|
+
"type": "string",
|
633
|
+
"description": "PDF document password, required if the document is password-protected"
|
634
|
+
},
|
635
|
+
"name": {
|
636
|
+
"type": "string",
|
637
|
+
"description": "Original filename of the document"
|
638
|
+
}
|
639
|
+
},
|
640
|
+
"required": ["path"]
|
641
|
+
},
|
642
|
+
"description": "List of PDF files to add image watermarks to, each containing path and optional password"
|
643
|
+
},
|
644
|
+
"image_url": {
|
645
|
+
"type": "string",
|
646
|
+
"description": "Image URL for the watermark, must include protocol, supports http/https/oss"
|
647
|
+
},
|
648
|
+
"position": {
|
649
|
+
"type": "string",
|
650
|
+
"description": "Image watermark position: top-left(topleft), top-center(top), top-right(topright), left(left), center(center), right(right), bottom-left(bottomleft), bottom(bottom), bottom-right(bottomright), diagonal(diagonal, -45 degrees), reverse-diagonal(reverse-diagonal, 45 degrees)",
|
651
|
+
"enum": ["topleft", "top", "topright", "left", "center", "right",
|
652
|
+
"bottomleft", "bottom", "bottomright", "diagonal", "reverse-diagonal"],
|
653
|
+
"default": "center"
|
654
|
+
},
|
655
|
+
"opacity": {
|
656
|
+
"type": "number",
|
657
|
+
"description": "Opacity, 0.0-1.0",
|
658
|
+
"default": 0.7,
|
659
|
+
"minimum": 0.0,
|
660
|
+
"maximum": 1.0
|
661
|
+
},
|
662
|
+
"range": {
|
663
|
+
"type": "string",
|
664
|
+
"description": "Page range, e.g. '1,3,5-7' or '' (empty string or not set) for all pages"
|
665
|
+
},
|
666
|
+
"layout": {
|
667
|
+
"type": "string",
|
668
|
+
"description": "Layout position: on top of content(on) or under content(under)",
|
669
|
+
"enum": ["on", "under"],
|
670
|
+
"default": "on"
|
671
|
+
}
|
672
|
+
},
|
673
|
+
"required": ["files", "image_url", "position"]
|
674
|
+
}
|
675
|
+
),
|
583
676
|
types.Tool(
|
584
677
|
name="unlock_pdf",
|
585
678
|
description="Remove password protection from PDF files.",
|
@@ -892,7 +985,7 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
892
985
|
),
|
893
986
|
types.Tool(
|
894
987
|
name="flatten_pdf",
|
895
|
-
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).",
|
896
989
|
inputSchema={
|
897
990
|
"type": "object",
|
898
991
|
"properties": {
|
@@ -953,7 +1046,56 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
953
1046
|
},
|
954
1047
|
"required": ["files"]
|
955
1048
|
}
|
956
|
-
)
|
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
|
+
),
|
957
1099
|
]
|
958
1100
|
|
959
1101
|
@app.call_tool()
|
@@ -984,11 +1126,16 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
|
|
984
1126
|
"edit_type": "decrypt", # 编辑类型
|
985
1127
|
"is_edit_operation": True, # 标记为编辑操作
|
986
1128
|
},
|
987
|
-
"
|
988
|
-
"edit_type": "
|
1129
|
+
"add_text_watermark": {
|
1130
|
+
"edit_type": "add_text_watermark", # 编辑类型,文本水印
|
989
1131
|
"is_edit_operation": True, # 标记为编辑操作
|
990
1132
|
"param_keys": ["text", "position", "opacity", "range", "layout",
|
991
|
-
"font_family", "font_size", "font_color"] # 需要从arguments
|
1133
|
+
"font_family", "font_size", "font_color"] # 需要从arguments获取的参数(文本水印)
|
1134
|
+
},
|
1135
|
+
"add_image_watermark": {
|
1136
|
+
"edit_type": "add_image_watermark",
|
1137
|
+
"is_edit_operation": True,
|
1138
|
+
"param_keys": ["image_url", "position", "opacity", "range", "layout"]
|
992
1139
|
},
|
993
1140
|
"protect_pdf": {
|
994
1141
|
"edit_type": "encrypt", # 编辑类型
|
@@ -1028,6 +1175,10 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
|
|
1028
1175
|
"is_edit_operation": True,
|
1029
1176
|
"param_keys": [] # 不暴露provider
|
1030
1177
|
},
|
1178
|
+
"translate_pdf": {
|
1179
|
+
"is_translate_operation": True,
|
1180
|
+
"param_keys": ["source", "target", "output_type"]
|
1181
|
+
},
|
1031
1182
|
}
|
1032
1183
|
|
1033
1184
|
DEFAULTS = {
|
@@ -1075,8 +1226,8 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
|
|
1075
1226
|
if name == "add_page_numbers":
|
1076
1227
|
# 添加页码工具使用"5"作为position默认值
|
1077
1228
|
operation_config["extra_params"][key] = arguments.get(key, DEFAULTS.get("position_page_numbers"))
|
1078
|
-
elif name == "
|
1079
|
-
#
|
1229
|
+
elif name == "add_text_watermark":
|
1230
|
+
# 添加文本水印工具使用"center"作为position默认值
|
1080
1231
|
operation_config["extra_params"][key] = arguments.get(key, DEFAULTS.get("position_watermark"))
|
1081
1232
|
else:
|
1082
1233
|
# 其他工具使用通用默认值
|
@@ -0,0 +1,94 @@
|
|
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
|
+
data["url"] = file_path
|
79
|
+
headers["Content-Type"] = "application/json"
|
80
|
+
response = await client.post(
|
81
|
+
self.api_base_url,
|
82
|
+
json=data,
|
83
|
+
headers=headers
|
84
|
+
)
|
85
|
+
else:
|
86
|
+
with open(file_path, "rb") as f:
|
87
|
+
files = {"file": f}
|
88
|
+
response = await client.post(
|
89
|
+
self.api_base_url,
|
90
|
+
files=files,
|
91
|
+
data=data,
|
92
|
+
headers=headers
|
93
|
+
)
|
94
|
+
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=khv6gJNWx8nhH6gpOu4jdeeL8p7xMPONEv7mAf8oWXQ,59952
|
6
|
+
lightpdf_aipdf_mcp/translator.py,sha256=FACnFcnz1zNDdndR3tAgTfDDkfk1rJRRgWorFbiiEUk,3834
|
7
|
+
lightpdf_aipdf_mcp-0.1.88.dist-info/METADATA,sha256=AOlJAa2HvSrrcN2I264tvQ5FR3-Gl4Nz7jqo1Ai4Wjs,8119
|
8
|
+
lightpdf_aipdf_mcp-0.1.88.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
+
lightpdf_aipdf_mcp-0.1.88.dist-info/entry_points.txt,sha256=X7TGUe52N4sYH-tYt0YUGApeJgw-efQlZA6uAZmlmr4,63
|
10
|
+
lightpdf_aipdf_mcp-0.1.88.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=f9YuDtOmXBGlMmS3O4Xn3rdWljY9XcNxu0CjftH4s0o,14726
|
4
|
-
lightpdf_aipdf_mcp/editor.py,sha256=O7wF_HWs5l-IiXLbZYLNYjj1ygo2v4yGJEYMJtn7jpo,26916
|
5
|
-
lightpdf_aipdf_mcp/server.py,sha256=sHFc2c7gLM6qh5sqbZREynoT53QZDvoXKCNEzWfnC6o,52200
|
6
|
-
lightpdf_aipdf_mcp-0.1.86.dist-info/METADATA,sha256=jQKNGcg_UD18y2zd1SC4Mms3iGuE0bPW1lPp31tVw5Y,8119
|
7
|
-
lightpdf_aipdf_mcp-0.1.86.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
8
|
-
lightpdf_aipdf_mcp-0.1.86.dist-info/entry_points.txt,sha256=X7TGUe52N4sYH-tYt0YUGApeJgw-efQlZA6uAZmlmr4,63
|
9
|
-
lightpdf_aipdf_mcp-0.1.86.dist-info/RECORD,,
|
File without changes
|
{lightpdf_aipdf_mcp-0.1.86.dist-info → lightpdf_aipdf_mcp-0.1.88.dist-info}/entry_points.txt
RENAMED
File without changes
|