lightpdf-aipdf-mcp 0.1.43__py3-none-any.whl → 0.1.44__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/common.py +2 -1
- lightpdf_aipdf_mcp/converter.py +39 -24
- lightpdf_aipdf_mcp/editor.py +76 -70
- lightpdf_aipdf_mcp/server.py +124 -87
- {lightpdf_aipdf_mcp-0.1.43.dist-info → lightpdf_aipdf_mcp-0.1.44.dist-info}/METADATA +1 -2
- lightpdf_aipdf_mcp-0.1.44.dist-info/RECORD +9 -0
- lightpdf_aipdf_mcp-0.1.43.dist-info/RECORD +0 -9
- {lightpdf_aipdf_mcp-0.1.43.dist-info → lightpdf_aipdf_mcp-0.1.44.dist-info}/WHEEL +0 -0
- {lightpdf_aipdf_mcp-0.1.43.dist-info → lightpdf_aipdf_mcp-0.1.44.dist-info}/entry_points.txt +0 -0
lightpdf_aipdf_mcp/common.py
CHANGED
@@ -5,7 +5,7 @@ import os
|
|
5
5
|
import time
|
6
6
|
from dataclasses import dataclass
|
7
7
|
from typing import List, Optional, Dict, Any, Tuple
|
8
|
-
|
8
|
+
import urllib.parse
|
9
9
|
import httpx
|
10
10
|
|
11
11
|
@dataclass
|
@@ -15,6 +15,7 @@ class BaseResult:
|
|
15
15
|
file_path: str
|
16
16
|
error_message: Optional[str] = None
|
17
17
|
download_url: Optional[str] = None
|
18
|
+
original_name: Optional[str] = None
|
18
19
|
|
19
20
|
class Logger:
|
20
21
|
"""日志记录器类"""
|
lightpdf_aipdf_mcp/converter.py
CHANGED
@@ -103,9 +103,10 @@ class Converter(BaseApiClient):
|
|
103
103
|
"""PDF文档转换器"""
|
104
104
|
def __init__(self, logger: Logger, file_handler: FileHandler):
|
105
105
|
super().__init__(logger, file_handler)
|
106
|
-
|
106
|
+
api_endpoint = os.environ.get("API_ENDPOINT", "devaw.aoscdn.com/tech")
|
107
|
+
self.api_base_url = f"https://{api_endpoint}/tasks/document/conversion"
|
107
108
|
|
108
|
-
async def add_page_numbers(self, file_path: str, start_num: int = 1, position: str = "5", margin: int = 30, password: str = None) -> ConversionResult:
|
109
|
+
async def add_page_numbers(self, file_path: str, start_num: int = 1, position: str = "5", margin: int = 30, password: str = None, original_name: Optional[str] = None) -> ConversionResult:
|
109
110
|
"""为PDF文档添加页码
|
110
111
|
|
111
112
|
Args:
|
@@ -114,6 +115,7 @@ class Converter(BaseApiClient):
|
|
114
115
|
position: 页码显示位置,字符串类型,可选值1-6(左上/上中/右上/左下/下中/右下),默认为5(下中)
|
115
116
|
margin: 页码显示的边距,整数类型,可选值10/30/60,默认为30
|
116
117
|
password: 文档密码,如果文档受密码保护,则需要提供(可选)
|
118
|
+
original_name: 原始文件名(可选)
|
117
119
|
|
118
120
|
Returns:
|
119
121
|
ConversionResult: 转换结果
|
@@ -122,6 +124,7 @@ class Converter(BaseApiClient):
|
|
122
124
|
input_format = self.file_handler.get_input_format(file_path)
|
123
125
|
if input_format != InputFormat.PDF:
|
124
126
|
await self.logger.error(f"添加页码功能仅支持PDF文件,当前文件格式为: {self.file_handler.get_file_extension(file_path)}")
|
127
|
+
return ConversionResult(success=False, file_path=file_path, error_message="添加页码功能仅支持PDF文件", original_name=original_name)
|
125
128
|
|
126
129
|
# 验证参数
|
127
130
|
valid_positions = {"1", "2", "3", "4", "5", "6"}
|
@@ -129,15 +132,21 @@ class Converter(BaseApiClient):
|
|
129
132
|
|
130
133
|
# 验证position参数
|
131
134
|
if position not in valid_positions:
|
132
|
-
|
135
|
+
error_msg = f"无效的页码位置值: {position}。有效值为: 1(左上), 2(上中), 3(右上), 4(左下), 5(下中), 6(右下)"
|
136
|
+
await self.logger.error(error_msg)
|
137
|
+
return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
|
133
138
|
|
134
139
|
# 验证margin参数
|
135
140
|
if margin not in valid_margins:
|
136
|
-
|
141
|
+
error_msg = f"无效的页码边距值: {margin}。有效值为: 10, 30, 60"
|
142
|
+
await self.logger.error(error_msg)
|
143
|
+
return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
|
137
144
|
|
138
145
|
# 验证start_num是否为正整数
|
139
146
|
if not isinstance(start_num, int) or start_num < 1:
|
140
|
-
|
147
|
+
error_msg = f"起始页码必须是正整数,当前值为: {start_num}"
|
148
|
+
await self.logger.error(error_msg)
|
149
|
+
return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
|
141
150
|
|
142
151
|
# 构建API参数
|
143
152
|
extra_params = {
|
@@ -150,9 +159,9 @@ class Converter(BaseApiClient):
|
|
150
159
|
await self.logger.log("info", f"正在为PDF添加页码(起始页码: {start_num}, 位置: {position}, 边距: {margin})...")
|
151
160
|
|
152
161
|
# 调用convert_file方法处理API请求
|
153
|
-
return await self.convert_file(file_path, "number-pdf", extra_params, password)
|
162
|
+
return await self.convert_file(file_path, "number-pdf", extra_params, password, original_name)
|
154
163
|
|
155
|
-
async def convert_file(self, file_path: str, format: str, extra_params: dict = None, password: str = None) -> ConversionResult:
|
164
|
+
async def convert_file(self, file_path: str, format: str, extra_params: dict = None, password: str = None, original_name: Optional[str] = None) -> ConversionResult:
|
156
165
|
"""转换单个文件
|
157
166
|
|
158
167
|
Args:
|
@@ -160,12 +169,14 @@ class Converter(BaseApiClient):
|
|
160
169
|
format: 目标格式
|
161
170
|
extra_params: 额外的API参数,例如去除水印
|
162
171
|
password: 文档密码,如果文档受密码保护,则需要提供(可选)
|
172
|
+
original_name: 原始文件名(可选)
|
163
173
|
|
164
174
|
Returns:
|
165
175
|
ConversionResult: 转换结果
|
166
176
|
"""
|
167
177
|
if not self.api_key:
|
168
178
|
await self.logger.error("未找到API_KEY。请在客户端配置API_KEY环境变量。")
|
179
|
+
return ConversionResult(success=False, file_path=file_path, error_message="未找到API_KEY", original_name=original_name)
|
169
180
|
|
170
181
|
# 特殊格式:doc-repair用于去除水印,number-pdf用于添加页码,输出均为PDF
|
171
182
|
is_special_operation = format in ["doc-repair", "number-pdf"]
|
@@ -180,38 +191,40 @@ class Converter(BaseApiClient):
|
|
180
191
|
# 验证输入文件格式
|
181
192
|
input_format = self.file_handler.get_input_format(file_path)
|
182
193
|
if not input_format and not is_special_operation:
|
183
|
-
|
194
|
+
error_msg = f"不支持的输入文件格式: {self.file_handler.get_file_extension(file_path)}"
|
195
|
+
await self.logger.error(error_msg)
|
196
|
+
return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
|
184
197
|
|
185
198
|
# 如果是去除水印操作,检查是否PDF文件
|
186
199
|
if format == "doc-repair" and input_format != InputFormat.PDF:
|
187
|
-
|
200
|
+
error_msg = "去除水印功能仅支持PDF文件"
|
201
|
+
await self.logger.error(error_msg)
|
202
|
+
return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
|
188
203
|
|
189
204
|
# 如果是添加页码操作,检查是否PDF文件
|
190
205
|
if format == "number-pdf" and input_format != InputFormat.PDF:
|
191
|
-
|
206
|
+
error_msg = "添加页码功能仅支持PDF文件"
|
207
|
+
await self.logger.error(error_msg)
|
208
|
+
return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
|
192
209
|
|
193
210
|
# 验证输出格式(除去特殊操作外)
|
194
211
|
if not is_special_operation:
|
195
212
|
try:
|
196
213
|
output_format = OutputFormat(format)
|
197
214
|
except ValueError:
|
198
|
-
|
215
|
+
error_msg = f"不支持的输出格式: {format}"
|
216
|
+
await self.logger.error(error_msg)
|
217
|
+
return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
|
199
218
|
|
200
|
-
# 验证格式转换是否支持
|
201
|
-
if input_format: # 确保input_format有效
|
202
|
-
available_formats = self.file_handler.get_available_output_formats(input_format)
|
203
|
-
if output_format not in available_formats:
|
204
|
-
await self.logger.error(
|
205
|
-
f"不支持从 {input_format.value} 格式转换为 {output_format.value} 格式。"
|
206
|
-
f"支持的输出格式: {', '.join(f.value for f in available_formats)}"
|
207
|
-
)
|
208
219
|
else:
|
209
220
|
# 远程路径的情况,设置必要的变量以便后续使用
|
210
221
|
if not is_special_operation:
|
211
222
|
try:
|
212
223
|
output_format = OutputFormat(format)
|
213
224
|
except ValueError:
|
214
|
-
|
225
|
+
error_msg = f"不支持的输出格式: {format}"
|
226
|
+
await self.logger.error(error_msg)
|
227
|
+
return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
|
215
228
|
else:
|
216
229
|
output_format = OutputFormat.PDF
|
217
230
|
|
@@ -221,7 +234,7 @@ class Converter(BaseApiClient):
|
|
221
234
|
# 验证文件
|
222
235
|
exists = await self.file_handler.validate_file_exists(file_path)
|
223
236
|
if not exists:
|
224
|
-
return ConversionResult(success=False, file_path=file_path, error_message="文件不存在")
|
237
|
+
return ConversionResult(success=False, file_path=file_path, error_message="文件不存在", original_name=original_name)
|
225
238
|
|
226
239
|
# 操作描述
|
227
240
|
if format == "doc-repair":
|
@@ -239,7 +252,7 @@ class Converter(BaseApiClient):
|
|
239
252
|
await self.logger.log("info", f"正在{operation_desc}...")
|
240
253
|
|
241
254
|
import httpx
|
242
|
-
async with httpx.AsyncClient(timeout=
|
255
|
+
async with httpx.AsyncClient(timeout=600.0) as client:
|
243
256
|
try:
|
244
257
|
# 初始化extra_params(如果为None)
|
245
258
|
if extra_params is None:
|
@@ -262,7 +275,8 @@ class Converter(BaseApiClient):
|
|
262
275
|
success=True,
|
263
276
|
file_path=file_path,
|
264
277
|
error_message=None,
|
265
|
-
download_url=download_url
|
278
|
+
download_url=download_url,
|
279
|
+
original_name=original_name
|
266
280
|
)
|
267
281
|
|
268
282
|
except Exception as e:
|
@@ -270,7 +284,8 @@ class Converter(BaseApiClient):
|
|
270
284
|
success=False,
|
271
285
|
file_path=file_path,
|
272
286
|
error_message=str(e),
|
273
|
-
download_url=None
|
287
|
+
download_url=None,
|
288
|
+
original_name=original_name
|
274
289
|
)
|
275
290
|
|
276
291
|
async def _create_task(self, client: httpx.AsyncClient, file_path: str, format: str, extra_params: dict = None) -> str:
|
lightpdf_aipdf_mcp/editor.py
CHANGED
@@ -29,7 +29,8 @@ class Editor(BaseApiClient):
|
|
29
29
|
"""PDF文档编辑器"""
|
30
30
|
def __init__(self, logger: Logger, file_handler: FileHandler):
|
31
31
|
super().__init__(logger, file_handler)
|
32
|
-
|
32
|
+
api_endpoint = os.environ.get("API_ENDPOINT", "devaw.aoscdn.com/tech")
|
33
|
+
self.api_base_url = f"https://{api_endpoint}/tasks/document/pdfedit"
|
33
34
|
|
34
35
|
async def _validate_pdf_file(self, file_path: str) -> bool:
|
35
36
|
"""验证文件是否为PDF格式
|
@@ -63,7 +64,7 @@ class Editor(BaseApiClient):
|
|
63
64
|
log_msg += "..."
|
64
65
|
await self.logger.log("info", log_msg)
|
65
66
|
|
66
|
-
async def split_pdf(self, file_path: str, pages: str = "", password: Optional[str] = None, split_type: str = "page", merge_all: int = 1) -> EditResult:
|
67
|
+
async def split_pdf(self, file_path: str, pages: str = "", password: Optional[str] = None, split_type: str = "page", merge_all: int = 1, original_name: Optional[str] = None) -> EditResult:
|
67
68
|
"""拆分PDF文件
|
68
69
|
|
69
70
|
Args:
|
@@ -72,19 +73,20 @@ class Editor(BaseApiClient):
|
|
72
73
|
password: 文档密码,如果文档受密码保护,则需要提供(可选)
|
73
74
|
split_type: 拆分类型,可选值: "every"=每页拆分为一个文件, "page"=指定页面规则拆分,默认为"page"
|
74
75
|
merge_all: 是否合并拆分后的文件,仅在split_type="page"时有效,0=不合并,1=合并,默认为1
|
76
|
+
original_name: 原始文件名(可选)
|
75
77
|
|
76
78
|
Returns:
|
77
79
|
EditResult: 拆分结果
|
78
80
|
"""
|
79
81
|
# 验证输入文件是否为PDF
|
80
82
|
if not await self._validate_pdf_file(file_path):
|
81
|
-
return EditResult(success=False, file_path=file_path, error_message="非PDF文件")
|
83
|
+
return EditResult(success=False, file_path=file_path, error_message="非PDF文件", original_name=original_name)
|
82
84
|
|
83
85
|
# 验证拆分类型
|
84
86
|
valid_split_types = {"every", "page"}
|
85
87
|
if split_type not in valid_split_types:
|
86
88
|
await self.logger.error(f"无效的拆分类型: {split_type}。有效值为: every, page")
|
87
|
-
return EditResult(success=False, file_path=file_path, error_message=f"无效的拆分类型: {split_type}。有效值为: every, page")
|
89
|
+
return EditResult(success=False, file_path=file_path, error_message=f"无效的拆分类型: {split_type}。有效值为: every, page", original_name=original_name)
|
88
90
|
|
89
91
|
# 构建API参数
|
90
92
|
extra_params = {
|
@@ -103,30 +105,31 @@ class Editor(BaseApiClient):
|
|
103
105
|
await self._log_operation("拆分PDF文件", operation_details)
|
104
106
|
|
105
107
|
# 调用edit_pdf方法处理API请求
|
106
|
-
return await self.edit_pdf(file_path, EditType.SPLIT, extra_params, password)
|
108
|
+
return await self.edit_pdf(file_path, EditType.SPLIT, extra_params, password, original_name)
|
107
109
|
|
108
|
-
async def merge_pdfs(self, file_paths: List[str], password: Optional[str] = None) -> EditResult:
|
110
|
+
async def merge_pdfs(self, file_paths: List[str], password: Optional[str] = None, original_name: Optional[str] = None) -> EditResult:
|
109
111
|
"""合并多个PDF文件
|
110
112
|
|
111
113
|
Args:
|
112
114
|
file_paths: 要合并的PDF文件路径列表
|
113
115
|
password: 文档密码,如果文档受密码保护,则需要提供(可选)
|
116
|
+
original_name: 原始文件名(可选)
|
114
117
|
|
115
118
|
Returns:
|
116
119
|
EditResult: 合并结果
|
117
120
|
"""
|
118
121
|
if len(file_paths) < 2:
|
119
122
|
await self.logger.error("合并PDF至少需要两个文件")
|
120
|
-
return EditResult(success=False, file_path=','.join(file_paths), error_message="合并PDF至少需要两个文件")
|
123
|
+
return EditResult(success=False, file_path=','.join(file_paths), error_message="合并PDF至少需要两个文件", original_name=original_name)
|
121
124
|
|
122
125
|
# 验证所有文件是否都是PDF并且存在
|
123
126
|
for pdf_file in file_paths:
|
124
127
|
if not await self._validate_pdf_file(pdf_file):
|
125
|
-
return EditResult(success=False, file_path=pdf_file, error_message="非PDF文件")
|
128
|
+
return EditResult(success=False, file_path=pdf_file, error_message="非PDF文件", original_name=original_name)
|
126
129
|
|
127
130
|
exists = await self.file_handler.validate_file_exists(pdf_file)
|
128
131
|
if not exists:
|
129
|
-
return EditResult(success=False, file_path=pdf_file, error_message="文件不存在")
|
132
|
+
return EditResult(success=False, file_path=pdf_file, error_message="文件不存在", original_name=original_name)
|
130
133
|
|
131
134
|
# 记录操作描述
|
132
135
|
await self._log_operation("合并PDF文件", f"{len(file_paths)} 个文件")
|
@@ -147,7 +150,8 @@ class Editor(BaseApiClient):
|
|
147
150
|
success=True,
|
148
151
|
file_path=file_paths[0], # 使用第一个文件路径作为参考
|
149
152
|
error_message=None,
|
150
|
-
download_url=download_url
|
153
|
+
download_url=download_url,
|
154
|
+
original_name=original_name
|
151
155
|
)
|
152
156
|
|
153
157
|
except Exception as e:
|
@@ -155,10 +159,11 @@ class Editor(BaseApiClient):
|
|
155
159
|
success=False,
|
156
160
|
file_path=file_paths[0],
|
157
161
|
error_message=str(e),
|
158
|
-
download_url=None
|
162
|
+
download_url=None,
|
163
|
+
original_name=original_name
|
159
164
|
)
|
160
165
|
|
161
|
-
async def rotate_pdf(self, file_path: str, angle: int, pages: str = "", password: Optional[str] = None) -> EditResult:
|
166
|
+
async def rotate_pdf(self, file_path: str, angle: int, pages: str = "", password: Optional[str] = None, original_name: Optional[str] = None) -> EditResult:
|
162
167
|
"""旋转PDF文件的页面
|
163
168
|
|
164
169
|
Args:
|
@@ -166,19 +171,20 @@ class Editor(BaseApiClient):
|
|
166
171
|
angle: 旋转角度,可选值为90、180、270
|
167
172
|
pages: 指定要旋转的页面范围,例如 "1,3,5-7" 或 "" 表示所有页面
|
168
173
|
password: 文档密码,如果文档受密码保护,则需要提供(可选)
|
174
|
+
original_name: 原始文件名(可选)
|
169
175
|
|
170
176
|
Returns:
|
171
177
|
EditResult: 旋转结果
|
172
178
|
"""
|
173
179
|
# 验证输入文件是否为PDF
|
174
180
|
if not await self._validate_pdf_file(file_path):
|
175
|
-
return EditResult(success=False, file_path=file_path, error_message="非PDF文件")
|
181
|
+
return EditResult(success=False, file_path=file_path, error_message="非PDF文件", original_name=original_name)
|
176
182
|
|
177
183
|
# 验证旋转角度
|
178
184
|
valid_angles = {90, 180, 270}
|
179
185
|
if angle not in valid_angles:
|
180
186
|
await self.logger.error("无效的旋转角度。角度必须是: 90, 180, 270")
|
181
|
-
return EditResult(success=False, file_path=file_path, error_message="无效的旋转角度。角度必须是: 90, 180, 270")
|
187
|
+
return EditResult(success=False, file_path=file_path, error_message="无效的旋转角度。角度必须是: 90, 180, 270", original_name=original_name)
|
182
188
|
|
183
189
|
# 构建API参数
|
184
190
|
extra_params = {
|
@@ -190,27 +196,28 @@ class Editor(BaseApiClient):
|
|
190
196
|
await self._log_operation("旋转PDF文件", f"参数: {angle_desc}")
|
191
197
|
|
192
198
|
# 调用edit_pdf方法处理API请求
|
193
|
-
return await self.edit_pdf(file_path, EditType.ROTATE, extra_params, password)
|
199
|
+
return await self.edit_pdf(file_path, EditType.ROTATE, extra_params, password, original_name)
|
194
200
|
|
195
|
-
async def compress_pdf(self, file_path: str, image_quantity: int = 60, password: Optional[str] = None) -> EditResult:
|
201
|
+
async def compress_pdf(self, file_path: str, image_quantity: int = 60, password: Optional[str] = None, original_name: Optional[str] = None) -> EditResult:
|
196
202
|
"""压缩PDF文件
|
197
203
|
|
198
204
|
Args:
|
199
205
|
file_path: 要压缩的PDF文件路径
|
200
206
|
image_quantity: 图片质量,范围1-100,默认为60
|
201
207
|
password: 文档密码,如果文档受密码保护,则需要提供(可选)
|
208
|
+
original_name: 原始文件名(可选)
|
202
209
|
|
203
210
|
Returns:
|
204
211
|
EditResult: 压缩结果
|
205
212
|
"""
|
206
213
|
# 验证输入文件是否为PDF
|
207
214
|
if not await self._validate_pdf_file(file_path):
|
208
|
-
return EditResult(success=False, file_path=file_path, error_message="非PDF文件")
|
215
|
+
return EditResult(success=False, file_path=file_path, error_message="非PDF文件", original_name=original_name)
|
209
216
|
|
210
|
-
#
|
211
|
-
if not 1 <= image_quantity <= 100:
|
212
|
-
await self.logger.error(
|
213
|
-
return EditResult(success=False, file_path=file_path, error_message=
|
217
|
+
# 验证图片质量范围
|
218
|
+
if not (1 <= image_quantity <= 100):
|
219
|
+
await self.logger.error("图片质量必须在1到100之间")
|
220
|
+
return EditResult(success=False, file_path=file_path, error_message="图片质量必须在1到100之间", original_name=original_name)
|
214
221
|
|
215
222
|
# 构建API参数
|
216
223
|
extra_params = {
|
@@ -221,60 +228,65 @@ class Editor(BaseApiClient):
|
|
221
228
|
await self._log_operation("压缩PDF文件", f"图片质量: {image_quantity}")
|
222
229
|
|
223
230
|
# 调用edit_pdf方法处理API请求
|
224
|
-
return await self.edit_pdf(file_path, EditType.COMPRESS, extra_params, password)
|
231
|
+
return await self.edit_pdf(file_path, EditType.COMPRESS, extra_params, password, original_name)
|
225
232
|
|
226
|
-
async def encrypt_pdf(self, file_path: str, password: str, original_password: Optional[str] = None) -> EditResult:
|
233
|
+
async def encrypt_pdf(self, file_path: str, password: str, original_password: Optional[str] = None, original_name: Optional[str] = None) -> EditResult:
|
227
234
|
"""加密PDF文件
|
228
235
|
|
229
236
|
Args:
|
230
237
|
file_path: 要加密的PDF文件路径
|
231
|
-
password:
|
232
|
-
original_password:
|
233
|
-
|
234
|
-
注意:
|
235
|
-
根据API文档,加密操作需要通过password参数指定要设置的新密码。
|
236
|
-
如果文档已经受密码保护,则使用original_password参数提供原密码进行解锁。
|
238
|
+
password: 要设置的新密码
|
239
|
+
original_password: 原始密码,如果文件已经加密,则需要提供(可选)
|
240
|
+
original_name: 原始文件名(可选)
|
237
241
|
|
238
242
|
Returns:
|
239
243
|
EditResult: 加密结果
|
240
244
|
"""
|
241
245
|
# 验证输入文件是否为PDF
|
242
246
|
if not await self._validate_pdf_file(file_path):
|
243
|
-
return EditResult(success=False, file_path=file_path, error_message="非PDF文件")
|
247
|
+
return EditResult(success=False, file_path=file_path, error_message="非PDF文件", original_name=original_name)
|
248
|
+
|
249
|
+
# 验证新密码
|
250
|
+
if not password:
|
251
|
+
await self.logger.error("加密PDF文件需要提供新密码")
|
252
|
+
return EditResult(success=False, file_path=file_path, error_message="加密PDF文件需要提供新密码", original_name=original_name)
|
244
253
|
|
245
254
|
# 构建API参数
|
246
255
|
extra_params = {
|
247
|
-
"password": password #
|
256
|
+
"password": password # 新密码
|
248
257
|
}
|
249
258
|
|
250
259
|
# 记录操作描述
|
251
260
|
await self._log_operation("加密PDF文件")
|
252
261
|
|
253
262
|
# 调用edit_pdf方法处理API请求
|
254
|
-
return await self.edit_pdf(file_path, EditType.ENCRYPT, extra_params, original_password)
|
263
|
+
return await self.edit_pdf(file_path, EditType.ENCRYPT, extra_params, original_password, original_name)
|
255
264
|
|
256
|
-
async def decrypt_pdf(self, file_path: str, password: Optional[str] = None) -> EditResult:
|
265
|
+
async def decrypt_pdf(self, file_path: str, password: Optional[str] = None, original_name: Optional[str] = None) -> EditResult:
|
257
266
|
"""解密PDF文件
|
258
267
|
|
259
268
|
Args:
|
260
269
|
file_path: 要解密的PDF文件路径
|
261
|
-
password:
|
262
|
-
|
263
|
-
注意:
|
264
|
-
该方法调用API的unlock功能,需要提供正确的PDF密码才能成功解密。
|
270
|
+
password: 文档密码,用于解锁已加密的文档
|
271
|
+
original_name: 原始文件名(可选)
|
265
272
|
|
266
273
|
Returns:
|
267
274
|
EditResult: 解密结果
|
268
275
|
"""
|
269
276
|
# 验证输入文件是否为PDF
|
270
277
|
if not await self._validate_pdf_file(file_path):
|
271
|
-
return EditResult(success=False, file_path=file_path, error_message="非PDF文件")
|
278
|
+
return EditResult(success=False, file_path=file_path, error_message="非PDF文件", original_name=original_name)
|
279
|
+
|
280
|
+
# 验证密码
|
281
|
+
if not password:
|
282
|
+
await self.logger.error("解密PDF文件需要提供密码")
|
283
|
+
return EditResult(success=False, file_path=file_path, error_message="解密PDF文件需要提供密码", original_name=original_name)
|
272
284
|
|
273
285
|
# 记录操作描述
|
274
286
|
await self._log_operation("解密PDF文件")
|
275
287
|
|
276
288
|
# 调用edit_pdf方法处理API请求
|
277
|
-
return await self.edit_pdf(file_path, EditType.DECRYPT, {}, password)
|
289
|
+
return await self.edit_pdf(file_path, EditType.DECRYPT, {}, password, original_name)
|
278
290
|
|
279
291
|
async def add_watermark(
|
280
292
|
self,
|
@@ -282,56 +294,47 @@ class Editor(BaseApiClient):
|
|
282
294
|
text: str,
|
283
295
|
position: str, # 必需参数:位置,如"top", "center", "diagonal"等
|
284
296
|
opacity: float = 0.5,
|
285
|
-
deg: str = "-45", # 直接使用字符串格式的角度
|
286
297
|
range: str = "", # 与API保持一致,使用range而非pages
|
287
298
|
layout: Optional[str] = None, # 可选参数: "on"/"under"
|
288
299
|
font_family: Optional[str] = None,
|
289
300
|
font_size: Optional[int] = None,
|
290
301
|
font_color: Optional[str] = None,
|
291
|
-
password: Optional[str] = None
|
302
|
+
password: Optional[str] = None,
|
303
|
+
original_name: Optional[str] = None
|
292
304
|
) -> EditResult:
|
293
|
-
"""为PDF
|
305
|
+
"""为PDF文件添加文本水印
|
294
306
|
|
295
307
|
Args:
|
296
308
|
file_path: 要添加水印的PDF文件路径
|
297
309
|
text: 水印文本内容
|
298
|
-
position:
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
range: 指定页面范围,例如 "1,3,5-7" 或 "" 表示所有页面
|
303
|
-
layout: 布局位置,可选值:"on"(在内容上)/"under"(在内容下)
|
310
|
+
position: 水印位置,如"top", "center", "diagonal"等
|
311
|
+
opacity: 透明度,0.0-1.0,默认0.5
|
312
|
+
range: 页面范围,例如 "1,3,5-7" 或空字符串表示所有页面
|
313
|
+
layout: 布局方式:"on"=在内容上,"under"=在内容下,默认"on"
|
304
314
|
font_family: 字体
|
305
315
|
font_size: 字体大小
|
306
|
-
font_color: 字体颜色,如"#ff0000"
|
316
|
+
font_color: 字体颜色,如 "#ff0000"
|
307
317
|
password: 文档密码,如果文档受密码保护,则需要提供(可选)
|
318
|
+
original_name: 原始文件名(可选)
|
308
319
|
|
309
320
|
Returns:
|
310
321
|
EditResult: 添加水印结果
|
311
322
|
"""
|
312
323
|
# 验证输入文件是否为PDF
|
313
324
|
if not await self._validate_pdf_file(file_path):
|
314
|
-
return EditResult(success=False, file_path=file_path, error_message="非PDF文件")
|
325
|
+
return EditResult(success=False, file_path=file_path, error_message="非PDF文件", original_name=original_name)
|
315
326
|
|
316
|
-
#
|
317
|
-
if not
|
318
|
-
await self.logger.error(
|
319
|
-
return EditResult(success=False, file_path=file_path, error_message=
|
320
|
-
|
321
|
-
# 验证position参数
|
322
|
-
valid_positions = {"topleft", "top", "topright", "left", "center", "right",
|
323
|
-
"bottomleft", "bottom", "bottomright", "diagonal", "reverse-diagonal"}
|
324
|
-
if position not in valid_positions:
|
325
|
-
await self.logger.error(f"无效的位置: {position}")
|
326
|
-
return EditResult(success=False, file_path=file_path, error_message=f"无效的位置: {position}")
|
327
|
+
# 验证必需参数
|
328
|
+
if not text:
|
329
|
+
await self.logger.error("水印文本不能为空")
|
330
|
+
return EditResult(success=False, file_path=file_path, error_message="水印文本不能为空", original_name=original_name)
|
327
331
|
|
328
332
|
# 构建API参数
|
329
333
|
extra_params = {
|
330
|
-
"edit_type": "text",
|
334
|
+
"edit_type": "text",
|
331
335
|
"text": text,
|
332
336
|
"position": position,
|
333
337
|
"opacity": opacity,
|
334
|
-
"deg": deg,
|
335
338
|
"range": range
|
336
339
|
}
|
337
340
|
|
@@ -349,9 +352,9 @@ class Editor(BaseApiClient):
|
|
349
352
|
await self._log_operation("为PDF添加水印", f"文本: {text}, 位置: {position}, 透明度: {opacity}")
|
350
353
|
|
351
354
|
# 调用edit_pdf方法处理API请求
|
352
|
-
return await self.edit_pdf(file_path, EditType.ADD_WATERMARK, extra_params, password)
|
355
|
+
return await self.edit_pdf(file_path, EditType.ADD_WATERMARK, extra_params, password, original_name)
|
353
356
|
|
354
|
-
async def edit_pdf(self, file_path: str, edit_type: EditType, extra_params: Dict[str, Any] = None, password: Optional[str] = None) -> EditResult:
|
357
|
+
async def edit_pdf(self, file_path: str, edit_type: EditType, extra_params: Dict[str, Any] = None, password: Optional[str] = None, original_name: Optional[str] = None) -> EditResult:
|
355
358
|
"""编辑PDF文件
|
356
359
|
|
357
360
|
Args:
|
@@ -359,6 +362,7 @@ class Editor(BaseApiClient):
|
|
359
362
|
edit_type: 编辑操作类型
|
360
363
|
extra_params: 额外的API参数
|
361
364
|
password: 文档密码,如果文档受密码保护,则需要提供(可选)
|
365
|
+
original_name: 原始文件名(可选)
|
362
366
|
|
363
367
|
注意:
|
364
368
|
1. 对于加密操作(protect),需要在extra_params中提供新密码
|
@@ -370,14 +374,14 @@ class Editor(BaseApiClient):
|
|
370
374
|
"""
|
371
375
|
if not self.api_key:
|
372
376
|
await self.logger.error("未找到API_KEY。请在客户端配置API_KEY环境变量。")
|
373
|
-
return EditResult(success=False, file_path=file_path, error_message="未找到API_KEY。请在客户端配置API_KEY环境变量。")
|
377
|
+
return EditResult(success=False, file_path=file_path, error_message="未找到API_KEY。请在客户端配置API_KEY环境变量。", original_name=original_name)
|
374
378
|
|
375
379
|
# 验证文件
|
376
380
|
exists = await self.file_handler.validate_file_exists(file_path)
|
377
381
|
if not exists:
|
378
|
-
return EditResult(success=False, file_path=file_path, error_message="文件不存在")
|
382
|
+
return EditResult(success=False, file_path=file_path, error_message="文件不存在", original_name=original_name)
|
379
383
|
|
380
|
-
async with httpx.AsyncClient(timeout=
|
384
|
+
async with httpx.AsyncClient(timeout=600.0) as client:
|
381
385
|
try:
|
382
386
|
# 初始化extra_params(如果为None)
|
383
387
|
if extra_params is None:
|
@@ -400,7 +404,8 @@ class Editor(BaseApiClient):
|
|
400
404
|
success=True,
|
401
405
|
file_path=file_path,
|
402
406
|
error_message=None,
|
403
|
-
download_url=download_url
|
407
|
+
download_url=download_url,
|
408
|
+
original_name=original_name
|
404
409
|
)
|
405
410
|
|
406
411
|
except Exception as e:
|
@@ -408,7 +413,8 @@ class Editor(BaseApiClient):
|
|
408
413
|
success=False,
|
409
414
|
file_path=file_path,
|
410
415
|
error_message=str(e),
|
411
|
-
download_url=None
|
416
|
+
download_url=None,
|
417
|
+
original_name=original_name
|
412
418
|
)
|
413
419
|
|
414
420
|
async def _create_task(self, client: httpx.AsyncClient, file_path: str, edit_type: EditType, extra_params: Dict[str, Any] = None) -> str:
|
lightpdf_aipdf_mcp/server.py
CHANGED
@@ -4,6 +4,7 @@ import asyncio
|
|
4
4
|
import os
|
5
5
|
import sys
|
6
6
|
import argparse
|
7
|
+
import json
|
7
8
|
from typing import List, Dict, Any, Callable, TypeVar, Optional, Union
|
8
9
|
|
9
10
|
# 第三方库导入
|
@@ -19,80 +20,68 @@ from .common import BaseResult, Logger, FileHandler
|
|
19
20
|
from .converter import Converter, ConversionResult
|
20
21
|
from .editor import Editor, EditResult
|
21
22
|
|
22
|
-
# 加载环境变量
|
23
|
-
load_dotenv()
|
24
|
-
|
25
23
|
# 类型定义
|
26
24
|
T = TypeVar('T', bound=BaseResult)
|
27
25
|
ProcessFunc = Callable[[str], Any]
|
28
26
|
|
29
27
|
def generate_result_report(
|
30
28
|
results: List[BaseResult],
|
31
|
-
operation_name: str
|
32
|
-
success_action: Optional[str] = None,
|
33
|
-
failed_action: Optional[str] = None
|
29
|
+
operation_name: str
|
34
30
|
) -> str:
|
35
31
|
"""生成通用结果报告
|
36
32
|
|
37
33
|
Args:
|
38
34
|
results: 结果列表
|
39
35
|
operation_name: 操作名称
|
40
|
-
success_action: 成功动作描述
|
41
|
-
failed_action: 失败动作描述
|
42
36
|
|
43
37
|
Returns:
|
44
|
-
str:
|
38
|
+
str: JSON格式的报告文本
|
45
39
|
"""
|
46
40
|
# 统计结果
|
47
41
|
success_count = sum(1 for r in results if r.success)
|
48
42
|
failed_count = len(results) - success_count
|
49
43
|
|
50
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
""
|
58
|
-
|
59
|
-
|
60
|
-
# 添加成功的文件信息
|
61
|
-
if success_count > 0 and success_action:
|
62
|
-
report_lines.extend([f"[成功] {success_action}的文件:", ""])
|
63
|
-
for i, result in enumerate(results):
|
64
|
-
if result.success:
|
65
|
-
report_lines.extend([
|
66
|
-
f"[{i+1}] {result.file_path}",
|
67
|
-
f"- 在线下载地址: {result.download_url}",
|
68
|
-
""
|
69
|
-
])
|
44
|
+
# 构建结果JSON对象
|
45
|
+
report_obj = {
|
46
|
+
"operation": operation_name,
|
47
|
+
"total": len(results),
|
48
|
+
"success_count": success_count,
|
49
|
+
"failed_count": failed_count,
|
50
|
+
"success_files": [],
|
51
|
+
"failed_files": []
|
52
|
+
}
|
70
53
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
54
|
+
for result in results:
|
55
|
+
if result.success:
|
56
|
+
# 添加成功的文件信息
|
57
|
+
report_obj["success_files"].append({
|
58
|
+
"file_path": result.file_path,
|
59
|
+
"original_name": result.original_name,
|
60
|
+
"download_url": result.download_url
|
61
|
+
})
|
62
|
+
else:
|
63
|
+
# 添加失败的文件信息
|
64
|
+
report_obj["failed_files"].append({
|
65
|
+
"file_path": result.file_path,
|
66
|
+
"original_name": result.original_name,
|
67
|
+
"error_message": result.error_message
|
68
|
+
})
|
81
69
|
|
82
|
-
|
70
|
+
# 返回JSON字符串
|
71
|
+
return json.dumps(report_obj, ensure_ascii=False)
|
83
72
|
|
84
73
|
async def process_batch_files(
|
85
74
|
file_objects: List[Dict[str, str]],
|
86
75
|
logger: Logger,
|
87
|
-
process_func: Callable[[str, Optional[str]], T],
|
76
|
+
process_func: Callable[[str, Optional[str], Optional[str]], T],
|
88
77
|
operation_desc: Optional[str] = None
|
89
78
|
) -> List[T]:
|
90
79
|
"""通用批处理文件函数
|
91
80
|
|
92
81
|
Args:
|
93
|
-
file_objects: 文件对象列表,每个对象包含path和可选的password
|
82
|
+
file_objects: 文件对象列表,每个对象包含path和可选的password及name
|
94
83
|
logger: 日志记录器
|
95
|
-
process_func: 处理单个文件的异步函数,接收file_path和
|
84
|
+
process_func: 处理单个文件的异步函数,接收file_path、password和original_name参数
|
96
85
|
operation_desc: 操作描述,用于日志记录
|
97
86
|
|
98
87
|
Returns:
|
@@ -108,7 +97,8 @@ async def process_batch_files(
|
|
108
97
|
async with semaphore:
|
109
98
|
file_path = file_obj["path"]
|
110
99
|
password = file_obj.get("password")
|
111
|
-
|
100
|
+
original_name = file_obj.get("name")
|
101
|
+
return await process_func(file_path, password, original_name)
|
112
102
|
|
113
103
|
# 创建任务列表
|
114
104
|
tasks = [process_with_semaphore(file_obj) for file_obj in file_objects]
|
@@ -117,14 +107,16 @@ async def process_batch_files(
|
|
117
107
|
# 单文件处理
|
118
108
|
file_path = file_objects[0]["path"]
|
119
109
|
password = file_objects[0].get("password")
|
120
|
-
|
110
|
+
original_name = file_objects[0].get("name")
|
111
|
+
return [await process_func(file_path, password, original_name)]
|
121
112
|
|
122
113
|
async def process_conversion_file(
|
123
114
|
file_path: str,
|
124
115
|
format: str,
|
125
116
|
converter: Converter,
|
126
117
|
extra_params: Optional[Dict[str, Any]] = None,
|
127
|
-
password: Optional[str] = None
|
118
|
+
password: Optional[str] = None,
|
119
|
+
original_name: Optional[str] = None
|
128
120
|
) -> ConversionResult:
|
129
121
|
"""处理单个文件转换"""
|
130
122
|
is_page_numbering = format == "number-pdf"
|
@@ -136,47 +128,51 @@ async def process_conversion_file(
|
|
136
128
|
extra_params.get("start_num", 1),
|
137
129
|
extra_params.get("position", "5"),
|
138
130
|
extra_params.get("margin", 30),
|
139
|
-
password
|
131
|
+
password,
|
132
|
+
original_name
|
140
133
|
)
|
141
134
|
else:
|
142
135
|
# 对于其他操作,使用convert_file方法
|
143
|
-
return await converter.convert_file(file_path, format, extra_params, password)
|
136
|
+
return await converter.convert_file(file_path, format, extra_params, password, original_name)
|
144
137
|
|
145
138
|
async def process_edit_file(
|
146
139
|
file_path: str,
|
147
140
|
edit_type: str,
|
148
141
|
editor: Editor,
|
149
142
|
extra_params: Dict[str, Any] = None,
|
150
|
-
password: Optional[str] = None
|
143
|
+
password: Optional[str] = None,
|
144
|
+
original_name: Optional[str] = None
|
151
145
|
) -> EditResult:
|
152
146
|
"""处理单个文件编辑"""
|
153
147
|
if edit_type == "decrypt":
|
154
|
-
return await editor.decrypt_pdf(file_path, password)
|
148
|
+
return await editor.decrypt_pdf(file_path, password, original_name)
|
155
149
|
elif edit_type == "add_watermark":
|
156
150
|
return await editor.add_watermark(
|
157
151
|
file_path=file_path,
|
158
152
|
text=extra_params.get("text", "水印"),
|
159
153
|
position=extra_params.get("position", "center"),
|
160
154
|
opacity=extra_params.get("opacity", 0.5),
|
161
|
-
deg=extra_params.get("deg", "0"),
|
162
155
|
range=extra_params.get("range", ""),
|
163
156
|
layout=extra_params.get("layout", "on"),
|
164
157
|
font_family=extra_params.get("font_family"),
|
165
158
|
font_size=extra_params.get("font_size"),
|
166
159
|
font_color=extra_params.get("font_color"),
|
167
|
-
password=password
|
160
|
+
password=password,
|
161
|
+
original_name=original_name
|
168
162
|
)
|
169
163
|
elif edit_type == "encrypt":
|
170
164
|
return await editor.encrypt_pdf(
|
171
165
|
file_path=file_path,
|
172
166
|
password=extra_params.get("password", ""),
|
173
|
-
original_password=password
|
167
|
+
original_password=password,
|
168
|
+
original_name=original_name
|
174
169
|
)
|
175
170
|
elif edit_type == "compress":
|
176
171
|
return await editor.compress_pdf(
|
177
172
|
file_path=file_path,
|
178
173
|
image_quantity=extra_params.get("image_quantity", 60),
|
179
|
-
password=password
|
174
|
+
password=password,
|
175
|
+
original_name=original_name
|
180
176
|
)
|
181
177
|
elif edit_type == "split":
|
182
178
|
return await editor.split_pdf(
|
@@ -184,27 +180,31 @@ async def process_edit_file(
|
|
184
180
|
pages=extra_params.get("pages", ""),
|
185
181
|
password=password,
|
186
182
|
split_type=extra_params.get("split_type", "page"),
|
187
|
-
merge_all=extra_params.get("merge_all", 1)
|
183
|
+
merge_all=extra_params.get("merge_all", 1),
|
184
|
+
original_name=original_name
|
188
185
|
)
|
189
186
|
elif edit_type == "merge":
|
190
187
|
# 对于合并操作,我们需要特殊处理,因为它需要处理多个文件
|
191
188
|
return EditResult(
|
192
189
|
success=False,
|
193
190
|
file_path=file_path,
|
194
|
-
error_message="合并操作需要使用特殊处理流程"
|
191
|
+
error_message="合并操作需要使用特殊处理流程",
|
192
|
+
original_name=original_name
|
195
193
|
)
|
196
194
|
elif edit_type == "rotate":
|
197
195
|
return await editor.rotate_pdf(
|
198
196
|
file_path=file_path,
|
199
197
|
angle=extra_params.get("angle", 90),
|
200
198
|
pages=extra_params.get("pages", ""),
|
201
|
-
password=password
|
199
|
+
password=password,
|
200
|
+
original_name=original_name
|
202
201
|
)
|
203
202
|
else:
|
204
203
|
return EditResult(
|
205
204
|
success=False,
|
206
205
|
file_path=file_path,
|
207
|
-
error_message=f"不支持的编辑类型: {edit_type}"
|
206
|
+
error_message=f"不支持的编辑类型: {edit_type}",
|
207
|
+
original_name=original_name
|
208
208
|
)
|
209
209
|
|
210
210
|
async def process_tool_call(
|
@@ -247,8 +247,8 @@ async def process_tool_call(
|
|
247
247
|
results = await process_batch_files(
|
248
248
|
file_objects,
|
249
249
|
logger,
|
250
|
-
lambda file_path, password: process_edit_file(
|
251
|
-
file_path, edit_type, editor, extra_params, password
|
250
|
+
lambda file_path, password, original_name: process_edit_file(
|
251
|
+
file_path, edit_type, editor, extra_params, password, original_name
|
252
252
|
),
|
253
253
|
operation_desc
|
254
254
|
)
|
@@ -256,9 +256,7 @@ async def process_tool_call(
|
|
256
256
|
# 生成报告
|
257
257
|
report_msg = generate_result_report(
|
258
258
|
results,
|
259
|
-
operation_desc
|
260
|
-
f"{edit_map.get(edit_type, edit_type)}成功",
|
261
|
-
f"{edit_map.get(edit_type, edit_type)}失败"
|
259
|
+
operation_desc
|
262
260
|
)
|
263
261
|
else:
|
264
262
|
# 转换操作
|
@@ -272,25 +270,19 @@ async def process_tool_call(
|
|
272
270
|
if is_watermark_removal:
|
273
271
|
operation_desc = "去除水印"
|
274
272
|
task_type = "水印去除"
|
275
|
-
success_action = "去除水印成功"
|
276
|
-
failed_action = "水印去除失败"
|
277
273
|
elif is_page_numbering:
|
278
274
|
operation_desc = "添加页码"
|
279
275
|
task_type = "添加页码"
|
280
|
-
success_action = "添加页码成功"
|
281
|
-
failed_action = "添加页码失败"
|
282
276
|
else:
|
283
277
|
operation_desc = f"转换为 {format} 格式"
|
284
278
|
task_type = "转换"
|
285
|
-
success_action = "转换成功"
|
286
|
-
failed_action = "转换失败"
|
287
279
|
|
288
280
|
# 处理文件
|
289
281
|
results = await process_batch_files(
|
290
282
|
file_objects,
|
291
283
|
logger,
|
292
|
-
lambda file_path, password: process_conversion_file(
|
293
|
-
file_path, format, converter, extra_params, password
|
284
|
+
lambda file_path, password, original_name: process_conversion_file(
|
285
|
+
file_path, format, converter, extra_params, password, original_name
|
294
286
|
),
|
295
287
|
operation_desc
|
296
288
|
)
|
@@ -298,9 +290,7 @@ async def process_tool_call(
|
|
298
290
|
# 生成报告
|
299
291
|
report_msg = generate_result_report(
|
300
292
|
results,
|
301
|
-
task_type
|
302
|
-
success_action,
|
303
|
-
failed_action
|
293
|
+
task_type
|
304
294
|
)
|
305
295
|
|
306
296
|
# 如果全部失败,记录错误
|
@@ -337,6 +327,10 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
337
327
|
"password": {
|
338
328
|
"type": "string",
|
339
329
|
"description": "文档密码,如果文档受密码保护,则需要提供此参数"
|
330
|
+
},
|
331
|
+
"name": {
|
332
|
+
"type": "string",
|
333
|
+
"description": "文件的原始文件名"
|
340
334
|
}
|
341
335
|
},
|
342
336
|
"required": ["path"]
|
@@ -370,6 +364,10 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
370
364
|
"password": {
|
371
365
|
"type": "string",
|
372
366
|
"description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
|
367
|
+
},
|
368
|
+
"name": {
|
369
|
+
"type": "string",
|
370
|
+
"description": "文件的原始文件名"
|
373
371
|
}
|
374
372
|
},
|
375
373
|
"required": ["path"]
|
@@ -416,6 +414,10 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
416
414
|
"password": {
|
417
415
|
"type": "string",
|
418
416
|
"description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
|
417
|
+
},
|
418
|
+
"name": {
|
419
|
+
"type": "string",
|
420
|
+
"description": "文件的原始文件名"
|
419
421
|
}
|
420
422
|
},
|
421
423
|
"required": ["path"]
|
@@ -444,6 +446,10 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
444
446
|
"password": {
|
445
447
|
"type": "string",
|
446
448
|
"description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
|
449
|
+
},
|
450
|
+
"name": {
|
451
|
+
"type": "string",
|
452
|
+
"description": "文件的原始文件名"
|
447
453
|
}
|
448
454
|
},
|
449
455
|
"required": ["path"]
|
@@ -456,7 +462,7 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
456
462
|
},
|
457
463
|
"position": {
|
458
464
|
"type": "string",
|
459
|
-
"description": "
|
465
|
+
"description": "水印位置: 左上(topleft), 上中(top), 右上(topright), 左(left), 中(center), 右(right), 左下(bottomleft), 下(bottom), 右下(bottomright), 对角线(diagonal,-45度), 反对角线(reverse-diagonal,45度)",
|
460
466
|
"enum": ["topleft", "top", "topright", "left", "center", "right",
|
461
467
|
"bottomleft", "bottom", "bottomright", "diagonal", "reverse-diagonal"],
|
462
468
|
"default": "center"
|
@@ -468,11 +474,6 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
468
474
|
"minimum": 0.0,
|
469
475
|
"maximum": 1.0
|
470
476
|
},
|
471
|
-
"deg": {
|
472
|
-
"type": "string",
|
473
|
-
"description": "倾斜角度,字符串格式",
|
474
|
-
"default": "0"
|
475
|
-
},
|
476
477
|
"range": {
|
477
478
|
"type": "string",
|
478
479
|
"description": "页面范围,例如 '1,3,5-7' 或 ''(空字符串或不设置)表示所有页面"
|
@@ -517,6 +518,10 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
517
518
|
"password": {
|
518
519
|
"type": "string",
|
519
520
|
"description": "PDF文档的密码,用于解锁文档,如果文档受密码保护,则需要提供此参数"
|
521
|
+
},
|
522
|
+
"name": {
|
523
|
+
"type": "string",
|
524
|
+
"description": "文件的原始文件名"
|
520
525
|
}
|
521
526
|
},
|
522
527
|
"required": ["path", "password"]
|
@@ -545,6 +550,10 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
545
550
|
"password": {
|
546
551
|
"type": "string",
|
547
552
|
"description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
|
553
|
+
},
|
554
|
+
"name": {
|
555
|
+
"type": "string",
|
556
|
+
"description": "文件的原始文件名"
|
548
557
|
}
|
549
558
|
},
|
550
559
|
"required": ["path"]
|
@@ -577,6 +586,10 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
577
586
|
"password": {
|
578
587
|
"type": "string",
|
579
588
|
"description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
|
589
|
+
},
|
590
|
+
"name": {
|
591
|
+
"type": "string",
|
592
|
+
"description": "文件的原始文件名"
|
580
593
|
}
|
581
594
|
},
|
582
595
|
"required": ["path"]
|
@@ -612,6 +625,10 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
612
625
|
"password": {
|
613
626
|
"type": "string",
|
614
627
|
"description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
|
628
|
+
},
|
629
|
+
"name": {
|
630
|
+
"type": "string",
|
631
|
+
"description": "文件的原始文件名"
|
615
632
|
}
|
616
633
|
},
|
617
634
|
"required": ["path"]
|
@@ -656,6 +673,10 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
656
673
|
"password": {
|
657
674
|
"type": "string",
|
658
675
|
"description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
|
676
|
+
},
|
677
|
+
"name": {
|
678
|
+
"type": "string",
|
679
|
+
"description": "文件的原始文件名"
|
659
680
|
}
|
660
681
|
},
|
661
682
|
"required": ["path"]
|
@@ -684,6 +705,10 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
684
705
|
"password": {
|
685
706
|
"type": "string",
|
686
707
|
"description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
|
708
|
+
},
|
709
|
+
"name": {
|
710
|
+
"type": "string",
|
711
|
+
"description": "文件的原始文件名"
|
687
712
|
}
|
688
713
|
},
|
689
714
|
"required": ["path"]
|
@@ -739,7 +764,7 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
|
|
739
764
|
"add_watermark": {
|
740
765
|
"edit_type": "add_watermark", # 编辑类型
|
741
766
|
"is_edit_operation": True, # 标记为编辑操作
|
742
|
-
"param_keys": ["text", "position", "opacity", "
|
767
|
+
"param_keys": ["text", "position", "opacity", "range", "layout",
|
743
768
|
"font_family", "font_size", "font_color"] # 需要从arguments获取的参数
|
744
769
|
},
|
745
770
|
"protect_pdf": {
|
@@ -774,7 +799,6 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
|
|
774
799
|
"position_watermark": "center", # 水印的位置默认值
|
775
800
|
"margin": 30,
|
776
801
|
"opacity": 0.5,
|
777
|
-
"deg": "0", # 更新为"0"以匹配handle_list_tools中的默认值
|
778
802
|
"range": "",
|
779
803
|
"layout": "on", # 添加layout默认值
|
780
804
|
"image_quantity": 60,
|
@@ -833,23 +857,33 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
|
|
833
857
|
file_handler = FileHandler(logger)
|
834
858
|
editor = Editor(logger, file_handler)
|
835
859
|
|
836
|
-
#
|
860
|
+
# 提取文件路径、密码和原始名称
|
837
861
|
file_paths = [file_obj["path"] for file_obj in file_objects]
|
838
862
|
passwords = [file_obj.get("password") for file_obj in file_objects]
|
863
|
+
original_names = [file_obj.get("name") for file_obj in file_objects]
|
839
864
|
|
840
865
|
# 由于merge_pdfs方法只接受一个密码参数,如果文件密码不同,可能需要特殊处理
|
841
866
|
# 此处简化处理,使用第一个非空密码
|
842
867
|
password = next((p for p in passwords if p), None)
|
843
868
|
|
869
|
+
# 合并文件名用于结果文件
|
870
|
+
merged_name = None
|
871
|
+
if any(original_names):
|
872
|
+
# 如果有原始文件名,则合并它们(最多使用前两个文件名)
|
873
|
+
valid_names = [name for name in original_names if name]
|
874
|
+
if valid_names:
|
875
|
+
if len(valid_names) == 1:
|
876
|
+
merged_name = valid_names[0]
|
877
|
+
else:
|
878
|
+
merged_name = f"{valid_names[0]}_{valid_names[1]}_等"
|
879
|
+
|
844
880
|
# 直接调用merge_pdfs方法
|
845
|
-
result = await editor.merge_pdfs(file_paths, password)
|
881
|
+
result = await editor.merge_pdfs(file_paths, password, merged_name)
|
846
882
|
|
847
883
|
# 构建结果报告
|
848
884
|
report_msg = generate_result_report(
|
849
885
|
[result],
|
850
|
-
"PDF合并"
|
851
|
-
"合并成功",
|
852
|
-
"合并失败"
|
886
|
+
"PDF合并"
|
853
887
|
)
|
854
888
|
|
855
889
|
# 如果失败,记录错误
|
@@ -868,6 +902,9 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
|
|
868
902
|
|
869
903
|
async def main():
|
870
904
|
"""应用主入口"""
|
905
|
+
# 加载环境变量
|
906
|
+
load_dotenv()
|
907
|
+
|
871
908
|
# 打印版本号
|
872
909
|
try:
|
873
910
|
import importlib.metadata
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: lightpdf-aipdf-mcp
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.44
|
4
4
|
Summary: MCP Server for LightPDF AI-PDF
|
5
5
|
Author: LightPDF Team
|
6
6
|
License: Proprietary
|
@@ -207,7 +207,6 @@ pip install lightpdf-aipdf-mcp
|
|
207
207
|
- `text`: 水印文本内容
|
208
208
|
- `position`: 水印位置,可选值包括"center"、"topleft"等
|
209
209
|
- `opacity`: 透明度(0.0-1.0)
|
210
|
-
- `deg`: 旋转角度
|
211
210
|
- `range`: 页面范围,如 "1,3,5-7"
|
212
211
|
- `layout`: 水印显示位置,"on"(在内容上)或"under"(在内容下)
|
213
212
|
- 可选的字体设置: `font_family`、`font_size`、`font_color`
|
@@ -0,0 +1,9 @@
|
|
1
|
+
lightpdf_aipdf_mcp/__init__.py,sha256=PPnAgpvJLYLVOTxnHDmJAulFnHJD6wuTwS6tRGjqq6s,141
|
2
|
+
lightpdf_aipdf_mcp/common.py,sha256=-7LU6gm-As_F8Ly68ssy15Vc9Zt_eNSnvDLEtVZDwlI,6633
|
3
|
+
lightpdf_aipdf_mcp/converter.py,sha256=gsYBLqE6EnMCpCYHaYjcdBD5mhXVqiTRvl1yLSqA01w,14773
|
4
|
+
lightpdf_aipdf_mcp/editor.py,sha256=KySdMurM8AZHwqSU1PpkSCCesQ4t2h-D8Mm12wf90CA,23539
|
5
|
+
lightpdf_aipdf_mcp/server.py,sha256=dd3lAg3DhFYPdgigpcicY_SiMRnwqXkxjbagj5ijSYY,39974
|
6
|
+
lightpdf_aipdf_mcp-0.1.44.dist-info/METADATA,sha256=Q8EvtD9EOAMKV1xNC-9hrQ97lSVVsXrHb1i2Ri4Kar0,7906
|
7
|
+
lightpdf_aipdf_mcp-0.1.44.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
8
|
+
lightpdf_aipdf_mcp-0.1.44.dist-info/entry_points.txt,sha256=X7TGUe52N4sYH-tYt0YUGApeJgw-efQlZA6uAZmlmr4,63
|
9
|
+
lightpdf_aipdf_mcp-0.1.44.dist-info/RECORD,,
|
@@ -1,9 +0,0 @@
|
|
1
|
-
lightpdf_aipdf_mcp/__init__.py,sha256=PPnAgpvJLYLVOTxnHDmJAulFnHJD6wuTwS6tRGjqq6s,141
|
2
|
-
lightpdf_aipdf_mcp/common.py,sha256=k0oNIKLzqo15UUJAkKJjYZBByLdBGpUPCV59FiqEgcY,6574
|
3
|
-
lightpdf_aipdf_mcp/converter.py,sha256=MxjO1yqMqfOQ9B696IyjRbtB3oZpemtD7x8arz52zoc,13312
|
4
|
-
lightpdf_aipdf_mcp/editor.py,sha256=CkL5mCM7CSUGD4ZAMoRdgBr3xBpJ-7bEC8InCip5q9A,22802
|
5
|
-
lightpdf_aipdf_mcp/server.py,sha256=xErltv5YkV8j3Ja5cm_KgJsc9qqnyCC7RTYs1Id9tWI,37652
|
6
|
-
lightpdf_aipdf_mcp-0.1.43.dist-info/METADATA,sha256=0SKzho1zsef_Ar44QCr2aREq7s9R49kfl1FPQRgYZTg,7928
|
7
|
-
lightpdf_aipdf_mcp-0.1.43.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
8
|
-
lightpdf_aipdf_mcp-0.1.43.dist-info/entry_points.txt,sha256=X7TGUe52N4sYH-tYt0YUGApeJgw-efQlZA6uAZmlmr4,63
|
9
|
-
lightpdf_aipdf_mcp-0.1.43.dist-info/RECORD,,
|
File without changes
|
{lightpdf_aipdf_mcp-0.1.43.dist-info → lightpdf_aipdf_mcp-0.1.44.dist-info}/entry_points.txt
RENAMED
File without changes
|