lightpdf-aipdf-mcp 0.1.39__py3-none-any.whl → 0.1.41__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 +19 -10
- lightpdf_aipdf_mcp/converter.py +67 -35
- lightpdf_aipdf_mcp/editor.py +31 -9
- {lightpdf_aipdf_mcp-0.1.39.dist-info → lightpdf_aipdf_mcp-0.1.41.dist-info}/METADATA +8 -8
- lightpdf_aipdf_mcp-0.1.41.dist-info/RECORD +9 -0
- lightpdf_aipdf_mcp-0.1.39.dist-info/RECORD +0 -9
- {lightpdf_aipdf_mcp-0.1.39.dist-info → lightpdf_aipdf_mcp-0.1.41.dist-info}/WHEEL +0 -0
- {lightpdf_aipdf_mcp-0.1.39.dist-info → lightpdf_aipdf_mcp-0.1.41.dist-info}/entry_points.txt +0 -0
lightpdf_aipdf_mcp/common.py
CHANGED
@@ -2,14 +2,11 @@
|
|
2
2
|
import asyncio
|
3
3
|
import json
|
4
4
|
import os
|
5
|
-
import tempfile
|
6
5
|
import time
|
7
|
-
import
|
8
|
-
from dataclasses import dataclass, field
|
6
|
+
from dataclasses import dataclass
|
9
7
|
from typing import List, Optional, Dict, Any, Tuple
|
10
8
|
|
11
9
|
import httpx
|
12
|
-
from mcp.types import TextContent, LoggingMessageNotification, LoggingMessageNotificationParams, LoggingLevel
|
13
10
|
|
14
11
|
@dataclass
|
15
12
|
class BaseResult:
|
@@ -40,7 +37,7 @@ class Logger:
|
|
40
37
|
|
41
38
|
mcp_level = level_map.get(level.lower(), "info")
|
42
39
|
|
43
|
-
#
|
40
|
+
# 直接调用session的send_log_message方法
|
44
41
|
await self.context.session.send_log_message(mcp_level, message)
|
45
42
|
|
46
43
|
async def error(self, message: str, error_class=RuntimeError):
|
@@ -61,6 +58,11 @@ class FileHandler:
|
|
61
58
|
def is_url(path: str) -> bool:
|
62
59
|
"""检查路径是否为URL"""
|
63
60
|
return path.startswith(("http://", "https://"))
|
61
|
+
|
62
|
+
@staticmethod
|
63
|
+
def is_oss_path(path: str) -> bool:
|
64
|
+
"""检查路径是否为OSS路径"""
|
65
|
+
return path.startswith("oss://")
|
64
66
|
|
65
67
|
@staticmethod
|
66
68
|
def get_file_extension(file_path: str) -> str:
|
@@ -89,20 +91,27 @@ class FileHandler:
|
|
89
91
|
# 实际实现在converter.py
|
90
92
|
return {}
|
91
93
|
|
92
|
-
async def validate_file_exists(self, file_path: str) ->
|
94
|
+
async def validate_file_exists(self, file_path: str) -> bool:
|
93
95
|
"""验证文件是否存在
|
94
96
|
|
95
97
|
Args:
|
96
98
|
file_path: 文件路径
|
97
99
|
|
98
100
|
Returns:
|
99
|
-
|
101
|
+
bool: 文件是否存在
|
100
102
|
"""
|
101
103
|
is_url = self.is_url(file_path)
|
102
|
-
|
104
|
+
is_oss = self.is_oss_path(file_path)
|
105
|
+
|
106
|
+
# 对于URL或OSS路径,假设它们是有效的
|
107
|
+
if is_url or is_oss:
|
108
|
+
return True
|
109
|
+
|
110
|
+
if not os.path.exists(file_path):
|
103
111
|
await self.logger.error(f"文件不存在:{file_path}", FileNotFoundError)
|
104
|
-
return False
|
105
|
-
|
112
|
+
return False
|
113
|
+
|
114
|
+
return True
|
106
115
|
|
107
116
|
class BaseApiClient:
|
108
117
|
"""API客户端基类"""
|
lightpdf_aipdf_mcp/converter.py
CHANGED
@@ -171,43 +171,57 @@ class Converter(BaseApiClient):
|
|
171
171
|
is_special_operation = format in ["doc-repair", "number-pdf"]
|
172
172
|
actual_output_format = "pdf" if is_special_operation else format
|
173
173
|
|
174
|
-
#
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
# 如果是去除水印操作,检查是否PDF文件
|
180
|
-
if format == "doc-repair" and input_format != InputFormat.PDF:
|
181
|
-
await self.logger.error("去除水印功能仅支持PDF文件")
|
174
|
+
# 检查是否为URL或OSS路径,如果是则跳过文件格式检查
|
175
|
+
is_remote_path = self.file_handler.is_url(file_path) or self.file_handler.is_oss_path(file_path)
|
176
|
+
|
177
|
+
if not is_remote_path:
|
178
|
+
# 只对本地文件进行格式验证
|
182
179
|
|
183
|
-
|
184
|
-
|
185
|
-
|
180
|
+
# 验证输入文件格式
|
181
|
+
input_format = self.file_handler.get_input_format(file_path)
|
182
|
+
if not input_format and not is_special_operation:
|
183
|
+
await self.logger.error(f"不支持的输入文件格式: {self.file_handler.get_file_extension(file_path)}")
|
186
184
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
185
|
+
# 如果是去除水印操作,检查是否PDF文件
|
186
|
+
if format == "doc-repair" and input_format != InputFormat.PDF:
|
187
|
+
await self.logger.error("去除水印功能仅支持PDF文件")
|
188
|
+
|
189
|
+
# 如果是添加页码操作,检查是否PDF文件
|
190
|
+
if format == "number-pdf" and input_format != InputFormat.PDF:
|
191
|
+
await self.logger.error("添加页码功能仅支持PDF文件")
|
192
|
+
|
193
|
+
# 验证输出格式(除去特殊操作外)
|
194
|
+
if not is_special_operation:
|
195
|
+
try:
|
196
|
+
output_format = OutputFormat(format)
|
197
|
+
except ValueError:
|
198
|
+
await self.logger.error(f"不支持的输出格式: {format}")
|
193
199
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
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
|
+
)
|
202
208
|
else:
|
203
|
-
#
|
204
|
-
|
209
|
+
# 远程路径的情况,设置必要的变量以便后续使用
|
210
|
+
if not is_special_operation:
|
211
|
+
try:
|
212
|
+
output_format = OutputFormat(format)
|
213
|
+
except ValueError:
|
214
|
+
await self.logger.error(f"不支持的输出格式: {format}")
|
215
|
+
else:
|
216
|
+
output_format = OutputFormat.PDF
|
217
|
+
|
218
|
+
# 对于远程路径,无法确定输入格式,但为了让后续代码能正常运行,设置一个默认值
|
219
|
+
input_format = None
|
205
220
|
|
206
221
|
# 验证文件
|
207
|
-
|
208
|
-
if not
|
222
|
+
exists = await self.file_handler.validate_file_exists(file_path)
|
223
|
+
if not exists:
|
209
224
|
return ConversionResult(success=False, file_path=file_path, error_message="文件不存在")
|
210
|
-
is_url = file_exists_result[1]
|
211
225
|
|
212
226
|
# 操作描述
|
213
227
|
if format == "doc-repair":
|
@@ -215,7 +229,13 @@ class Converter(BaseApiClient):
|
|
215
229
|
elif format == "number-pdf":
|
216
230
|
operation_desc = "添加页码"
|
217
231
|
else:
|
218
|
-
|
232
|
+
if is_remote_path:
|
233
|
+
operation_desc = f"转换为 {output_format.value.upper()} 格式"
|
234
|
+
else:
|
235
|
+
if input_format:
|
236
|
+
operation_desc = f"将 {input_format.value.upper()} 转换为 {output_format.value.upper()} 格式"
|
237
|
+
else:
|
238
|
+
operation_desc = f"转换为 {output_format.value.upper()} 格式"
|
219
239
|
await self.logger.log("info", f"正在{operation_desc}...")
|
220
240
|
|
221
241
|
import httpx
|
@@ -230,7 +250,7 @@ class Converter(BaseApiClient):
|
|
230
250
|
extra_params["password"] = password
|
231
251
|
|
232
252
|
# 创建转换任务
|
233
|
-
task_id = await self._create_task(client, file_path, format,
|
253
|
+
task_id = await self._create_task(client, file_path, format, extra_params)
|
234
254
|
|
235
255
|
# 等待任务完成
|
236
256
|
download_url = await self._wait_for_task(client, task_id, "转换")
|
@@ -253,14 +273,13 @@ class Converter(BaseApiClient):
|
|
253
273
|
download_url=None
|
254
274
|
)
|
255
275
|
|
256
|
-
async def _create_task(self, client: httpx.AsyncClient, file_path: str, format: str,
|
276
|
+
async def _create_task(self, client: httpx.AsyncClient, file_path: str, format: str, extra_params: dict = None) -> str:
|
257
277
|
"""创建转换任务
|
258
278
|
|
259
279
|
Args:
|
260
280
|
client: HTTP客户端
|
261
281
|
file_path: 文件路径
|
262
282
|
format: 目标格式,特殊格式"doc-repair"用于去除水印,"number-pdf"用于添加页码
|
263
|
-
is_url: 是否URL路径
|
264
283
|
extra_params: 额外API参数(可选)
|
265
284
|
|
266
285
|
Returns:
|
@@ -275,7 +294,20 @@ class Converter(BaseApiClient):
|
|
275
294
|
if extra_params:
|
276
295
|
data.update(extra_params)
|
277
296
|
|
278
|
-
|
297
|
+
# 检查是否为OSS路径
|
298
|
+
if self.file_handler.is_oss_path(file_path):
|
299
|
+
# OSS路径处理方式,与URL类似,但提取resource_id
|
300
|
+
data["resource_id"] = file_path.split("oss://")[1]
|
301
|
+
# 使用JSON方式时添加Content-Type
|
302
|
+
headers["Content-Type"] = "application/json"
|
303
|
+
response = await client.post(
|
304
|
+
self.api_base_url,
|
305
|
+
json=data,
|
306
|
+
headers=headers
|
307
|
+
)
|
308
|
+
# 检查是否为URL路径
|
309
|
+
elif self.file_handler.is_url(file_path):
|
310
|
+
data["url"] = file_path
|
279
311
|
# 使用JSON方式时添加Content-Type
|
280
312
|
headers["Content-Type"] = "application/json"
|
281
313
|
response = await client.post(
|
lightpdf_aipdf_mcp/editor.py
CHANGED
@@ -40,6 +40,10 @@ class Editor(BaseApiClient):
|
|
40
40
|
Returns:
|
41
41
|
bool: 如果是PDF文件则返回True,否则返回False
|
42
42
|
"""
|
43
|
+
# 对于URL或OSS路径,跳过文件格式检查
|
44
|
+
if self.file_handler.is_url(file_path) or self.file_handler.is_oss_path(file_path):
|
45
|
+
return True
|
46
|
+
|
43
47
|
input_format = self.file_handler.get_input_format(file_path)
|
44
48
|
if input_format != InputFormat.PDF:
|
45
49
|
await self.logger.error(f"此功能仅支持PDF文件,当前文件格式为: {self.file_handler.get_file_extension(file_path)}")
|
@@ -120,7 +124,8 @@ class Editor(BaseApiClient):
|
|
120
124
|
if not await self._validate_pdf_file(pdf_file):
|
121
125
|
return EditResult(success=False, file_path=pdf_file, error_message="非PDF文件")
|
122
126
|
|
123
|
-
|
127
|
+
exists = await self.file_handler.validate_file_exists(pdf_file)
|
128
|
+
if not exists:
|
124
129
|
return EditResult(success=False, file_path=pdf_file, error_message="文件不存在")
|
125
130
|
|
126
131
|
# 记录操作描述
|
@@ -368,10 +373,9 @@ class Editor(BaseApiClient):
|
|
368
373
|
return EditResult(success=False, file_path=file_path, error_message="未找到API_KEY。请在客户端配置API_KEY环境变量。")
|
369
374
|
|
370
375
|
# 验证文件
|
371
|
-
|
372
|
-
if not
|
376
|
+
exists = await self.file_handler.validate_file_exists(file_path)
|
377
|
+
if not exists:
|
373
378
|
return EditResult(success=False, file_path=file_path, error_message="文件不存在")
|
374
|
-
is_url = file_exists_result[1]
|
375
379
|
|
376
380
|
async with httpx.AsyncClient(timeout=120.0) as client:
|
377
381
|
try:
|
@@ -384,7 +388,7 @@ class Editor(BaseApiClient):
|
|
384
388
|
extra_params["password"] = password
|
385
389
|
|
386
390
|
# 创建编辑任务
|
387
|
-
task_id = await self._create_task(client, file_path, edit_type,
|
391
|
+
task_id = await self._create_task(client, file_path, edit_type, extra_params)
|
388
392
|
|
389
393
|
# 等待任务完成
|
390
394
|
download_url = await self._wait_for_task(client, task_id, "编辑")
|
@@ -407,14 +411,13 @@ class Editor(BaseApiClient):
|
|
407
411
|
download_url=None
|
408
412
|
)
|
409
413
|
|
410
|
-
async def _create_task(self, client: httpx.AsyncClient, file_path: str, edit_type: EditType,
|
414
|
+
async def _create_task(self, client: httpx.AsyncClient, file_path: str, edit_type: EditType, extra_params: Dict[str, Any] = None) -> str:
|
411
415
|
"""创建编辑任务
|
412
416
|
|
413
417
|
Args:
|
414
418
|
client: HTTP客户端
|
415
419
|
file_path: 文件路径
|
416
420
|
edit_type: 编辑操作类型
|
417
|
-
is_url: 是否URL路径
|
418
421
|
extra_params: 额外API参数(可选)
|
419
422
|
|
420
423
|
Returns:
|
@@ -429,7 +432,19 @@ class Editor(BaseApiClient):
|
|
429
432
|
if extra_params:
|
430
433
|
data.update(extra_params)
|
431
434
|
|
432
|
-
|
435
|
+
# 检查是否为OSS路径
|
436
|
+
if self.file_handler.is_oss_path(file_path):
|
437
|
+
# OSS路径处理方式,与URL类似,但提取resource_id
|
438
|
+
data["resource_id"] = file_path.split("oss://")[1]
|
439
|
+
# 使用JSON方式时添加Content-Type
|
440
|
+
headers["Content-Type"] = "application/json"
|
441
|
+
response = await client.post(
|
442
|
+
self.api_base_url,
|
443
|
+
json=data,
|
444
|
+
headers=headers
|
445
|
+
)
|
446
|
+
# 检查是否为URL路径
|
447
|
+
elif self.file_handler.is_url(file_path):
|
433
448
|
data["url"] = file_path
|
434
449
|
# 使用JSON方式时添加Content-Type
|
435
450
|
headers["Content-Type"] = "application/json"
|
@@ -476,12 +491,19 @@ class Editor(BaseApiClient):
|
|
476
491
|
files = {}
|
477
492
|
|
478
493
|
for i, file_path in enumerate(file_paths):
|
494
|
+
# 检查是否为URL或OSS路径
|
479
495
|
if self.file_handler.is_url(file_path):
|
480
|
-
# 对于URL
|
496
|
+
# 对于URL或OSS路径,添加到inputs数组
|
481
497
|
input_item = {"url": file_path}
|
482
498
|
if password:
|
483
499
|
input_item["password"] = password
|
484
500
|
url_inputs.append(input_item)
|
501
|
+
elif self.file_handler.is_oss_path(file_path):
|
502
|
+
# 对于OSS路径,添加到inputs数组
|
503
|
+
input_item = {"resource_id": file_path.split("oss://")[1]}
|
504
|
+
if password:
|
505
|
+
input_item["password"] = password
|
506
|
+
url_inputs.append(input_item)
|
485
507
|
else:
|
486
508
|
# 记录本地文件,需要使用form方式
|
487
509
|
local_files.append(file_path)
|
@@ -1,17 +1,17 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: lightpdf-aipdf-mcp
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.41
|
4
4
|
Summary: MCP Server for LightPDF AI-PDF
|
5
5
|
Author: LightPDF Team
|
6
6
|
License: Proprietary
|
7
7
|
Requires-Python: >=3.8
|
8
|
-
Requires-Dist: httpx
|
9
|
-
Requires-Dist: httpx
|
10
|
-
Requires-Dist: mcp
|
11
|
-
Requires-Dist: pydantic
|
12
|
-
Requires-Dist: pydantic
|
13
|
-
Requires-Dist: python-dotenv
|
14
|
-
Requires-Dist: uvicorn
|
8
|
+
Requires-Dist: httpx
|
9
|
+
Requires-Dist: httpx-sse
|
10
|
+
Requires-Dist: mcp
|
11
|
+
Requires-Dist: pydantic
|
12
|
+
Requires-Dist: pydantic-settings
|
13
|
+
Requires-Dist: python-dotenv
|
14
|
+
Requires-Dist: uvicorn
|
15
15
|
Description-Content-Type: text/markdown
|
16
16
|
|
17
17
|
# LightPDF AI助手 MCP Server
|
@@ -0,0 +1,9 @@
|
|
1
|
+
lightpdf_aipdf_mcp/__init__.py,sha256=PPnAgpvJLYLVOTxnHDmJAulFnHJD6wuTwS6tRGjqq6s,141
|
2
|
+
lightpdf_aipdf_mcp/common.py,sha256=zhd_XxO7cBiQ7A6fc1JJjcpJfRWCQBi3pfBSsrBRusg,6568
|
3
|
+
lightpdf_aipdf_mcp/converter.py,sha256=SbP5CpDn1suoq9QSApw6kgqJXEc4gwF7Po3oLo5namg,13313
|
4
|
+
lightpdf_aipdf_mcp/editor.py,sha256=6ELqub8uOW6kwkVz52YdFUyDmkMsN1BkTApSw6Bi-lg,22802
|
5
|
+
lightpdf_aipdf_mcp/server.py,sha256=jXAcfLWhef5E3cle28bytDTTuMiwTMYAglbAFVF6Wlw,35737
|
6
|
+
lightpdf_aipdf_mcp-0.1.41.dist-info/METADATA,sha256=EsB0LkJG393OYqZbfO-DzcOC0JGBujmuAx2C6sWbxGk,7879
|
7
|
+
lightpdf_aipdf_mcp-0.1.41.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
8
|
+
lightpdf_aipdf_mcp-0.1.41.dist-info/entry_points.txt,sha256=X7TGUe52N4sYH-tYt0YUGApeJgw-efQlZA6uAZmlmr4,63
|
9
|
+
lightpdf_aipdf_mcp-0.1.41.dist-info/RECORD,,
|
@@ -1,9 +0,0 @@
|
|
1
|
-
lightpdf_aipdf_mcp/__init__.py,sha256=PPnAgpvJLYLVOTxnHDmJAulFnHJD6wuTwS6tRGjqq6s,141
|
2
|
-
lightpdf_aipdf_mcp/common.py,sha256=ZPm_Wzn7GpMLl9xJXOrTgNgd0hqdI931JVKm57WRa14,6433
|
3
|
-
lightpdf_aipdf_mcp/converter.py,sha256=5oTLHxXOiZ9_PDatWiYbgPZ0LFFqE0L4qHvamX3g9lM,11732
|
4
|
-
lightpdf_aipdf_mcp/editor.py,sha256=mbCGXd1Xzu-ibDWJnn11fOP5Xlf1Vqxi0yQ8Vs_yUKg,21787
|
5
|
-
lightpdf_aipdf_mcp/server.py,sha256=jXAcfLWhef5E3cle28bytDTTuMiwTMYAglbAFVF6Wlw,35737
|
6
|
-
lightpdf_aipdf_mcp-0.1.39.dist-info/METADATA,sha256=USG1KkrGGjJ75mf2hB0fuoFTas5noxVK0VcxlTN_Ooc,7931
|
7
|
-
lightpdf_aipdf_mcp-0.1.39.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
8
|
-
lightpdf_aipdf_mcp-0.1.39.dist-info/entry_points.txt,sha256=X7TGUe52N4sYH-tYt0YUGApeJgw-efQlZA6uAZmlmr4,63
|
9
|
-
lightpdf_aipdf_mcp-0.1.39.dist-info/RECORD,,
|
File without changes
|
{lightpdf_aipdf_mcp-0.1.39.dist-info → lightpdf_aipdf_mcp-0.1.41.dist-info}/entry_points.txt
RENAMED
File without changes
|