lightpdf-aipdf-mcp 0.1.145__py3-none-any.whl → 0.1.147__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.
@@ -167,7 +167,6 @@ class PDFCreator(BaseApiClient):
167
167
  async def _get_real_user_info(self, language: str) -> dict:
168
168
  """获取真实的用户信息"""
169
169
  import platform
170
- import socket
171
170
 
172
171
  # 获取真实的用户代理字符串
173
172
  system = platform.system()
@@ -225,9 +224,12 @@ class PDFCreator(BaseApiClient):
225
224
  return language_codes.get(language, "en-US,en;q=0.9")
226
225
 
227
226
  def _get_local_ip(self) -> str:
228
- """获取本地IP地址的稳定方法"""
227
+ """获取本地IP地址的稳定方法,优先获取物理网络接口IP"""
229
228
  import socket
230
229
 
230
+ # 收集所有可能的IP地址
231
+ candidate_ips = []
232
+
231
233
  # 方法1: 尝试连接外部DNS服务器
232
234
  try:
233
235
  with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
@@ -236,9 +238,9 @@ class PDFCreator(BaseApiClient):
236
238
  try:
237
239
  s.connect((dns_server, 80))
238
240
  ip = s.getsockname()[0]
239
- # 验证是否为私有IP地址
240
241
  if self._is_valid_local_ip(ip):
241
- return ip
242
+ candidate_ips.append(ip)
243
+ break # 找到第一个有效IP就停止
242
244
  except Exception:
243
245
  continue
244
246
  except Exception:
@@ -249,58 +251,121 @@ class PDFCreator(BaseApiClient):
249
251
  hostname = socket.gethostname()
250
252
  ip_list = socket.gethostbyname_ex(hostname)[2]
251
253
  for ip in ip_list:
252
- if self._is_valid_local_ip(ip):
253
- return ip
254
+ if self._is_valid_local_ip(ip) and ip not in candidate_ips:
255
+ candidate_ips.append(ip)
254
256
  except Exception:
255
257
  pass
256
258
 
257
- # 方法3: 遍历网络接口
259
+ # 方法3: 使用系统网络接口信息
258
260
  try:
259
- import subprocess
260
261
  import platform
261
-
262
- if platform.system() == "Windows":
263
- # Windows系统
264
- result = subprocess.run(['ipconfig'], capture_output=True, text=True)
265
- for line in result.stdout.split('\n'):
266
- if 'IPv4' in line and 'Address' in line:
267
- ip = line.split(':')[-1].strip()
268
- if self._is_valid_local_ip(ip):
269
- return ip
270
- else:
271
- # Unix/Linux/macOS系统
272
- result = subprocess.run(['hostname', '-I'], capture_output=True, text=True)
262
+ if platform.system() != "Windows":
263
+ # Unix/Linux/macOS系统:尝试使用更详细的网络接口信息
264
+ import subprocess
265
+ result = subprocess.run(['ip', 'route', 'get', '8.8.8.8'],
266
+ capture_output=True, text=True)
273
267
  if result.returncode == 0:
274
- ips = result.stdout.strip().split()
275
- for ip in ips:
276
- if self._is_valid_local_ip(ip):
277
- return ip
268
+ for line in result.stdout.split('\n'):
269
+ if 'src' in line:
270
+ parts = line.split()
271
+ if 'src' in parts:
272
+ src_index = parts.index('src')
273
+ if src_index + 1 < len(parts):
274
+ ip = parts[src_index + 1]
275
+ if self._is_valid_local_ip(ip) and ip not in candidate_ips:
276
+ candidate_ips.append(ip)
277
+ break
278
278
  except Exception:
279
279
  pass
280
280
 
281
- # 方法4: 尝试绑定本地socket
282
- try:
283
- with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
284
- s.bind(('', 0))
285
- ip = s.getsockname()[0]
286
- if self._is_valid_local_ip(ip):
287
- return ip
288
- except Exception:
289
- pass
281
+ # 选择最优IP地址
282
+ if candidate_ips:
283
+ return self._select_best_ip(candidate_ips)
290
284
 
291
285
  # 最后的后备方案
292
286
  return "127.0.0.1"
293
287
 
288
+ def _select_best_ip(self, candidate_ips: list) -> str:
289
+ """从候选IP中选择最佳的IP地址"""
290
+ import ipaddress
291
+
292
+ # 按优先级排序IP地址
293
+ def ip_priority(ip: str) -> int:
294
+ try:
295
+ ip_obj = ipaddress.ip_address(ip)
296
+
297
+ # 最高优先级:公网IP
298
+ if ip_obj.is_global:
299
+ return 1
300
+
301
+ # 高优先级:常见的家庭/办公网络
302
+ common_networks = [
303
+ ipaddress.ip_network('192.168.0.0/16'), # 家庭网络
304
+ ipaddress.ip_network('10.0.0.0/8'), # 企业网络
305
+ ipaddress.ip_network('172.16.0.0/12'), # 企业网络(但要避免虚拟网络段)
306
+ ]
307
+
308
+ for network in common_networks:
309
+ if ip_obj in network:
310
+ # 进一步细分优先级
311
+ if ip.startswith('192.168.1.') or ip.startswith('192.168.0.'):
312
+ return 2 # 最常见的家庭网络
313
+ elif ip.startswith('192.168.'):
314
+ return 3 # 其他家庭网络
315
+ elif ip.startswith('10.'):
316
+ return 4 # 企业网络
317
+ else:
318
+ return 5 # 其他私有网络
319
+
320
+ # 较低优先级:其他私有IP
321
+ if ip_obj.is_private:
322
+ return 6
323
+
324
+ # 最低优先级:其他地址
325
+ return 7
326
+
327
+ except ValueError:
328
+ return 99 # 无效IP地址
329
+
330
+ # 按优先级排序并返回最佳IP
331
+ candidate_ips.sort(key=ip_priority)
332
+ return candidate_ips[0]
333
+
294
334
  def _is_valid_local_ip(self, ip: str) -> bool:
295
- """验证IP地址是否为有效的本地IP"""
335
+ """验证IP地址是否为有效的本地IP,排除虚拟网络接口"""
296
336
  import ipaddress
297
337
 
298
338
  try:
299
339
  ip_obj = ipaddress.ip_address(ip)
340
+
300
341
  # 排除回环地址和链路本地地址
301
342
  if ip_obj.is_loopback or ip_obj.is_link_local:
302
343
  return False
303
- # 接受私有地址和公网地址
344
+
345
+ # 排除常见的虚拟网络IP段
346
+ virtual_networks = [
347
+ # Docker默认网段
348
+ ipaddress.ip_network('172.17.0.0/16'),
349
+ ipaddress.ip_network('172.18.0.0/16'),
350
+ ipaddress.ip_network('172.19.0.0/16'),
351
+ ipaddress.ip_network('172.20.0.0/16'),
352
+ # VMware默认网段
353
+ ipaddress.ip_network('192.168.56.0/24'),
354
+ ipaddress.ip_network('192.168.57.0/24'),
355
+ # VirtualBox默认网段
356
+ ipaddress.ip_network('192.168.100.0/24'),
357
+ # Hyper-V默认网段
358
+ ipaddress.ip_network('172.16.0.0/12'),
359
+ # 其他常见虚拟网段
360
+ ipaddress.ip_network('10.0.75.0/24'), # Parallels
361
+ ipaddress.ip_network('169.254.0.0/16'), # APIPA
362
+ ]
363
+
364
+ # 检查是否在虚拟网络范围内
365
+ for network in virtual_networks:
366
+ if ip_obj in network:
367
+ return False
368
+
304
369
  return True
305
370
  except ValueError:
306
371
  return False
@@ -149,6 +149,11 @@ async def process_conversion_file(
149
149
  # 处理extra_params
150
150
  if extra_params is None:
151
151
  extra_params = {}
152
+
153
+ # 参数名称映射:将image_quality映射为image-quality
154
+ if "image_quality" in extra_params:
155
+ extra_params["image-quality"] = extra_params.pop("image_quality")
156
+
152
157
  # 直接传递 merge_all 参数(如有)
153
158
  # 其它逻辑交由 converter.convert_file 处理
154
159
  return await converter.convert_file(file_path, format, extra_params, password, original_name)
@@ -526,6 +531,13 @@ async def handle_list_tools() -> list[types.Tool]:
526
531
  "type": "boolean",
527
532
  "default": False,
528
533
  "description": "Only effective when converting Excel to PDF. If true, each sheet will be forced to fit into a single PDF page (even if content overflows; no additional pages will be created). If false, each sheet may be split into multiple PDF pages if the content is too large."
534
+ },
535
+ "image_quality": {
536
+ "type": "integer",
537
+ "minimum": 0,
538
+ "maximum": 200,
539
+ "default": 100,
540
+ "description": "Image quality setting, 0-200. Only effective when converting PDF to image formats (jpg, jpeg, png). Higher values produce better quality but larger file sizes."
529
541
  }
530
542
  },
531
543
  "required": ["files", "format"]
@@ -1377,7 +1389,7 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
1377
1389
  "convert_document": {
1378
1390
  "format_key": "format", # 从arguments获取format
1379
1391
  "is_edit_operation": False,
1380
- "param_keys": ["merge_all", "one_page_per_sheet"]
1392
+ "param_keys": ["merge_all", "one_page_per_sheet", "image_quality"]
1381
1393
  },
1382
1394
  "remove_watermark": {
1383
1395
  "format": "doc-repair", # 固定format
@@ -1485,6 +1497,7 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
1485
1497
  "format": "png", # 提取图片的默认格式
1486
1498
  "page_size": "",
1487
1499
  "resolution": 0,
1500
+ "image_quality": 100, # PDF转图片的图片质量默认值
1488
1501
  }
1489
1502
 
1490
1503
  if name in TOOL_CONFIG:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lightpdf-aipdf-mcp
3
- Version: 0.1.145
3
+ Version: 0.1.147
4
4
  Summary: MCP Server for LightPDF AI-PDF
5
5
  Author: LightPDF Team
6
6
  License: Proprietary
@@ -1,13 +1,13 @@
1
1
  lightpdf_aipdf_mcp/__init__.py,sha256=PPnAgpvJLYLVOTxnHDmJAulFnHJD6wuTwS6tRGjqq6s,141
2
2
  lightpdf_aipdf_mcp/common.py,sha256=VOipRuz2veRMhpvr0lJ2nZwuEZntx1MiRxDSNx0fSWs,9310
3
3
  lightpdf_aipdf_mcp/converter.py,sha256=r8iO5R5vLNNKWdb6WSnwzTwwmp2TvgLXSIvvA4y___o,15336
4
- lightpdf_aipdf_mcp/create_pdf.py,sha256=JC8VIkmc5Hg1-q2M-DCbf0bGRaZDCem1D4qrqAfeD1Q,13062
4
+ lightpdf_aipdf_mcp/create_pdf.py,sha256=FKWttbR48foiwUwmSsCN6n6PfI25IpaXETPUscz9DjI,16073
5
5
  lightpdf_aipdf_mcp/editor.py,sha256=BR-sWW9L7tybEPOhdc8W-uwdBoom19EPTmGDvy_2gMc,27941
6
6
  lightpdf_aipdf_mcp/ocr.py,sha256=IyzxisA6qtXcGTHZofpUYXYDdcIjUaaHcVUKpM7DH9A,2832
7
- lightpdf_aipdf_mcp/server.py,sha256=vOWDu2j-m7sumNt_cVCPAu1xXO2N6Z5jfyO1yDtvGZ4,80747
7
+ lightpdf_aipdf_mcp/server.py,sha256=Y4jF1nl-YCCZkbLaiuEZ2EuSnK0Am8OaxN9hGzBDWNQ,81468
8
8
  lightpdf_aipdf_mcp/summarizer.py,sha256=UPAftDKjp2NFE2Wfoi2yAsGfaWqihu-c_W_BwfhVy0c,3671
9
9
  lightpdf_aipdf_mcp/translator.py,sha256=nuZa4FpsA0xeRWAEGqSPIM55aJuazJX1m32uajowo7I,2778
10
- lightpdf_aipdf_mcp-0.1.145.dist-info/METADATA,sha256=8un8BLPQd6FZfLYKpIEu3CmRPh22WeE_SsI-AhOswdg,8120
11
- lightpdf_aipdf_mcp-0.1.145.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
- lightpdf_aipdf_mcp-0.1.145.dist-info/entry_points.txt,sha256=X7TGUe52N4sYH-tYt0YUGApeJgw-efQlZA6uAZmlmr4,63
13
- lightpdf_aipdf_mcp-0.1.145.dist-info/RECORD,,
10
+ lightpdf_aipdf_mcp-0.1.147.dist-info/METADATA,sha256=SSOOrZXi6fTDgfnSJFrUJuScJ-AwodAEEy0OIjax-Pk,8120
11
+ lightpdf_aipdf_mcp-0.1.147.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
+ lightpdf_aipdf_mcp-0.1.147.dist-info/entry_points.txt,sha256=X7TGUe52N4sYH-tYt0YUGApeJgw-efQlZA6uAZmlmr4,63
13
+ lightpdf_aipdf_mcp-0.1.147.dist-info/RECORD,,