mcpcn-office-powerpoint-mcp-server 2.0.7__py3-none-any.whl → 2.0.9__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.
- {mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info → mcpcn_office_powerpoint_mcp_server-2.0.9.dist-info}/METADATA +2 -2
- {mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info → mcpcn_office_powerpoint_mcp_server-2.0.9.dist-info}/RECORD +7 -7
- ppt_mcp_server.py +24 -0
- tools/content_tools.py +177 -1
- {mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info → mcpcn_office_powerpoint_mcp_server-2.0.9.dist-info}/WHEEL +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info → mcpcn_office_powerpoint_mcp_server-2.0.9.dist-info}/entry_points.txt +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info → mcpcn_office_powerpoint_mcp_server-2.0.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcpcn-office-powerpoint-mcp-server
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.9
|
|
4
4
|
Summary: MCP Server for PowerPoint manipulation using python-pptx - Consolidated Edition
|
|
5
5
|
Project-URL: Homepage, https://github.com/GongRzhe/Office-PowerPoint-MCP-Server.git
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/GongRzhe/Office-PowerPoint-MCP-Server.git/issues
|
|
@@ -30,7 +30,7 @@ License-File: LICENSE
|
|
|
30
30
|
Classifier: License :: OSI Approved :: MIT License
|
|
31
31
|
Classifier: Operating System :: OS Independent
|
|
32
32
|
Classifier: Programming Language :: Python :: 3
|
|
33
|
-
Requires-Python: >=3.
|
|
33
|
+
Requires-Python: >=3.10
|
|
34
34
|
Requires-Dist: fonttools>=4.0.0
|
|
35
35
|
Requires-Dist: mcp[cli]>=1.3.0
|
|
36
36
|
Requires-Dist: pillow>=8.0.0
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
ppt_mcp_server.py,sha256=
|
|
1
|
+
ppt_mcp_server.py,sha256=YOmBZc_Yh1RWk8QNOveCz-2i5qi3udcmWN0XdvRJopc,15232
|
|
2
2
|
slide_layout_templates.json,sha256=ryA8zIZv6WBwjhBQaJXRWES3uqE7esqqFmXyiPEdauU,107256
|
|
3
3
|
tools/__init__.py,sha256=b53px-U7Ny4-FMnAdFLoJMtPxD7lt7DB8ydvcdATCz0,983
|
|
4
4
|
tools/chart_tools.py,sha256=vvXI53gm9_2mfNOiG-qCfwLdqqr2pe_uDXHN9adVsZk,3124
|
|
5
5
|
tools/connector_tools.py,sha256=8cUPAfTZ-5vvooLCVQIWfblMzcFe3cPdh_bG5UezykQ,3402
|
|
6
|
-
tools/content_tools.py,sha256=
|
|
6
|
+
tools/content_tools.py,sha256=7rciu_J5N2X4LhwYakBhpPaRxpCLPVM3ObJSskLJniU,35735
|
|
7
7
|
tools/hyperlink_tools.py,sha256=EweAxbYGvOpNpzDxH5DnrFzGMQmnzNGGehEoCCx9d18,5920
|
|
8
8
|
tools/master_tools.py,sha256=05a7ZUEDN5a7ySwG97Sj4UJK7Y0pcCxang553Bh7iOM,4986
|
|
9
9
|
tools/presentation_tools.py,sha256=rnVRYOu4A0Sw97sf8qKBK6-q5dYnK1vO2XNAzN8je4o,7977
|
|
@@ -18,8 +18,8 @@ utils/design_utils.py,sha256=vEstbqsBe202PYS4NvqdOLL3Aq5na7G2SBJnY-BYD_g,23761
|
|
|
18
18
|
utils/presentation_utils.py,sha256=9Y4F0twPvr0srjq53_Szkwxcn7dh06IWy3snRLaMuM8,6262
|
|
19
19
|
utils/template_utils.py,sha256=8iSJcJIgoOmi53TtlRnMwRLTDI84EfSW2PWj8EW3Z1k,44574
|
|
20
20
|
utils/validation_utils.py,sha256=0OiwFE1WZh05ZP-0hLzidyxKSUIJaJ3QrV0_s6FpxZk,11775
|
|
21
|
-
mcpcn_office_powerpoint_mcp_server-2.0.
|
|
22
|
-
mcpcn_office_powerpoint_mcp_server-2.0.
|
|
23
|
-
mcpcn_office_powerpoint_mcp_server-2.0.
|
|
24
|
-
mcpcn_office_powerpoint_mcp_server-2.0.
|
|
25
|
-
mcpcn_office_powerpoint_mcp_server-2.0.
|
|
21
|
+
mcpcn_office_powerpoint_mcp_server-2.0.9.dist-info/METADATA,sha256=jI7MB6Ie5TvfkTq8Up8zPNNLnU10BUhbLtCnROz0Ftc,35856
|
|
22
|
+
mcpcn_office_powerpoint_mcp_server-2.0.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
23
|
+
mcpcn_office_powerpoint_mcp_server-2.0.9.dist-info/entry_points.txt,sha256=4iWuiBR6HdlTqSJjCVyqyqVGpSGiD4ssw7LkEzmgvXw,75
|
|
24
|
+
mcpcn_office_powerpoint_mcp_server-2.0.9.dist-info/licenses/LICENSE,sha256=A-aSf9c0K2FKh7xtMkSr5Ot76NX8mrdLffsajnmZGKs,1064
|
|
25
|
+
mcpcn_office_powerpoint_mcp_server-2.0.9.dist-info/RECORD,,
|
ppt_mcp_server.py
CHANGED
|
@@ -7,6 +7,8 @@ import os
|
|
|
7
7
|
import argparse
|
|
8
8
|
from typing import Dict, Any
|
|
9
9
|
from mcp.server.fastmcp import FastMCP
|
|
10
|
+
import functools
|
|
11
|
+
import inspect
|
|
10
12
|
|
|
11
13
|
# import utils # Currently unused
|
|
12
14
|
from tools import (
|
|
@@ -27,6 +29,28 @@ app = FastMCP(
|
|
|
27
29
|
name="ppt-mcp-server"
|
|
28
30
|
)
|
|
29
31
|
|
|
32
|
+
# Global response wrapper: if a tool returns {"error": ...}, raise to mark outer isError=true
|
|
33
|
+
_original_app_tool = app.tool
|
|
34
|
+
def _tool_with_error_promotion(*t_args, **t_kwargs):
|
|
35
|
+
def _decorator(func):
|
|
36
|
+
@functools.wraps(func)
|
|
37
|
+
def _wrapped(*args, **kwargs):
|
|
38
|
+
result = func(*args, **kwargs)
|
|
39
|
+
# Promote dict-with-error to exception so outer layer sets isError=true
|
|
40
|
+
if isinstance(result, dict) and "error" in result and result["error"]:
|
|
41
|
+
raise RuntimeError(str(result["error"]))
|
|
42
|
+
return result
|
|
43
|
+
# Preserve original callable signature for FastMCP parameter binding
|
|
44
|
+
try:
|
|
45
|
+
_wrapped.__signature__ = inspect.signature(func)
|
|
46
|
+
except Exception:
|
|
47
|
+
pass
|
|
48
|
+
return _original_app_tool(*t_args, **t_kwargs)(_wrapped)
|
|
49
|
+
return _decorator
|
|
50
|
+
|
|
51
|
+
# Monkey patch app.tool before any registrations
|
|
52
|
+
app.tool = _tool_with_error_promotion
|
|
53
|
+
|
|
30
54
|
# Global state to store presentations in memory
|
|
31
55
|
presentations = {}
|
|
32
56
|
current_presentation_id = None
|
tools/content_tools.py
CHANGED
|
@@ -8,6 +8,14 @@ import utils as ppt_utils
|
|
|
8
8
|
import tempfile
|
|
9
9
|
import base64
|
|
10
10
|
import os
|
|
11
|
+
import urllib.request
|
|
12
|
+
import urllib.parse
|
|
13
|
+
|
|
14
|
+
# Optional: requests for better HTTP handling
|
|
15
|
+
try:
|
|
16
|
+
import requests # type: ignore
|
|
17
|
+
except Exception:
|
|
18
|
+
requests = None
|
|
11
19
|
|
|
12
20
|
|
|
13
21
|
def register_content_tools(app: FastMCP, presentations: Dict, get_current_presentation_id, validate_parameters, is_positive, is_non_negative, is_in_range, is_valid_rgb):
|
|
@@ -492,7 +500,78 @@ def register_content_tools(app: FastMCP, presentations: Dict, get_current_presen
|
|
|
492
500
|
output_path: Optional[str] = None,
|
|
493
501
|
presentation_id: Optional[str] = None
|
|
494
502
|
) -> Dict:
|
|
495
|
-
"""
|
|
503
|
+
"""
|
|
504
|
+
统一的图片处理工具(添加/增强)。
|
|
505
|
+
|
|
506
|
+
功能
|
|
507
|
+
- operation="add":将图片插入到指定幻灯片位置,支持本地文件或 Base64 图片源。
|
|
508
|
+
- operation="enhance":对已有图片文件进行画质增强与风格化处理,输出增强后的图片路径。
|
|
509
|
+
|
|
510
|
+
参数
|
|
511
|
+
- slide_index: int — 目标幻灯片索引(从 0 开始)。
|
|
512
|
+
- operation: str — "add" 或 "enhance"。
|
|
513
|
+
- image_source: str —
|
|
514
|
+
* 当 source_type="file":本地图片文件路径。
|
|
515
|
+
* 当 source_type="base64":图片的 Base64 字符串。
|
|
516
|
+
- source_type: str — "file" 或 "base64"。
|
|
517
|
+
* add 支持 "file" 与 "base64"。
|
|
518
|
+
* enhance 仅支持 "file"(不接受 base64)。
|
|
519
|
+
- left, top: float — 插入位置(英寸)。
|
|
520
|
+
- width, height: Optional[float] — 插入尺寸(英寸)。可只提供一项以按比例缩放;都不提供则按图片原始尺寸。
|
|
521
|
+
- enhancement_style: Optional[str] — "presentation" 或 "custom"。当 operation="add" 且需要自动增强时可用;"presentation" 走预设的专业增强流程。
|
|
522
|
+
- brightness, contrast, saturation, sharpness: float — 亮度/对比度/饱和度/锐度(默认 1.0,>1 增强,<1 减弱)。
|
|
523
|
+
- blur_radius: float — 模糊半径(默认 0)。
|
|
524
|
+
- filter_type: Optional[str] — 过滤器类型(如 "DETAIL"、"SMOOTH" 等,取决于 Pillow 支持)。
|
|
525
|
+
- output_path: Optional[str] — 增强后图片的输出路径(不传则生成临时文件)。
|
|
526
|
+
- presentation_id: Optional[str] — 指定演示文稿 ID;不传则使用当前打开的演示文稿。
|
|
527
|
+
注:在某些部署环境(如你当前环境)中,必须显式传入 presentation_id 才会对目标文档生效。
|
|
528
|
+
|
|
529
|
+
返回
|
|
530
|
+
- operation="add":
|
|
531
|
+
* message: str
|
|
532
|
+
* shape_index: int — 新增形状索引
|
|
533
|
+
* image_path: str(当 source_type="file" 时返回)
|
|
534
|
+
- operation="enhance":
|
|
535
|
+
* message: str
|
|
536
|
+
* enhanced_path: str — 增强后图片文件路径
|
|
537
|
+
- 失败时返回 {"error": str}
|
|
538
|
+
|
|
539
|
+
注意事项
|
|
540
|
+
- 现在支持通过 URL 插入图片(仅 operation="add"):source_type="url",仅允许 http/https 协议。
|
|
541
|
+
会在内部下载到临时文件后插入并自动清理。若返回的 Content-Type 非 image/* 将返回错误。
|
|
542
|
+
enhance 仍然仅支持本地文件路径。
|
|
543
|
+
- 在某些部署环境(如你当前环境)中,需显式提供 presentation_id 参数,否则可能插入到非预期文档或不生效。
|
|
544
|
+
- operation="enhance" 不接受 Base64,必须提供可访问的本地文件路径。
|
|
545
|
+
- 插入 Base64 图片时,内部会写入临时文件后再插入,操作完成后临时文件会被清理。
|
|
546
|
+
- slide_index 必须在当前演示文稿的有效范围内,否则将返回错误。
|
|
547
|
+
|
|
548
|
+
示例
|
|
549
|
+
- 通过 URL 插入图片(仅 add):
|
|
550
|
+
manage_image(slide_index=0, operation="add",
|
|
551
|
+
image_source="https://example.com/logo.png", source_type="url",
|
|
552
|
+
left=1.0, top=1.0, width=3.0, presentation_id="YOUR_PRESENTATION_ID")
|
|
553
|
+
- 插入本地图片:
|
|
554
|
+
manage_image(slide_index=0, operation="add",
|
|
555
|
+
image_source="D:/images/logo.png", source_type="file",
|
|
556
|
+
left=1.0, top=1.0, width=3.0, presentation_id="YOUR_PRESENTATION_ID")
|
|
557
|
+
|
|
558
|
+
- 插入 Base64 图片:
|
|
559
|
+
manage_image(slide_index=1, operation="add",
|
|
560
|
+
image_source="<BASE64字符串>", source_type="base64",
|
|
561
|
+
left=2.0, top=1.5, width=4.0, height=2.5, presentation_id="YOUR_PRESENTATION_ID")
|
|
562
|
+
|
|
563
|
+
- 插入并应用专业增强(演示风格):
|
|
564
|
+
manage_image(slide_index=2, operation="add",
|
|
565
|
+
image_source="assets/photo.jpg", source_type="file",
|
|
566
|
+
enhancement_style="presentation", left=1.0, top=2.0, presentation_id="YOUR_PRESENTATION_ID")
|
|
567
|
+
|
|
568
|
+
- 增强已有图片文件(自定义参数):
|
|
569
|
+
manage_image(slide_index=0, operation="enhance",
|
|
570
|
+
image_source="assets/photo.jpg", source_type="file",
|
|
571
|
+
brightness=1.2, contrast=1.1, saturation=1.3,
|
|
572
|
+
sharpness=1.1, blur_radius=0, filter_type=None,
|
|
573
|
+
output_path="assets/photo_enhanced.jpg", presentation_id="YOUR_PRESENTATION_ID")
|
|
574
|
+
"""
|
|
496
575
|
pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
|
|
497
576
|
|
|
498
577
|
if pres_id is None or pres_id not in presentations:
|
|
@@ -533,6 +612,103 @@ def register_content_tools(app: FastMCP, presentations: Dict, get_current_presen
|
|
|
533
612
|
return {
|
|
534
613
|
"error": f"Failed to process base64 image: {str(e)}"
|
|
535
614
|
}
|
|
615
|
+
elif source_type == "url":
|
|
616
|
+
# Handle image URL (http/https)
|
|
617
|
+
try:
|
|
618
|
+
parsed = urllib.parse.urlparse(image_source)
|
|
619
|
+
if parsed.scheme not in ("http", "https"):
|
|
620
|
+
return {"error": f"Unsupported URL scheme: {parsed.scheme}. Only http/https allowed."}
|
|
621
|
+
|
|
622
|
+
# Download helper using requests if available, else urllib
|
|
623
|
+
content_type = None
|
|
624
|
+
temp_path = None
|
|
625
|
+
|
|
626
|
+
if requests is not None:
|
|
627
|
+
with requests.get(image_source, stream=True) as resp:
|
|
628
|
+
if resp.status_code != 200:
|
|
629
|
+
return {"error": f"Failed to download image. HTTP {resp.status_code}"}
|
|
630
|
+
content_type = resp.headers.get("Content-Type", "")
|
|
631
|
+
|
|
632
|
+
if not content_type.startswith("image/"):
|
|
633
|
+
return {"error": f"URL content is not an image (Content-Type: {content_type or 'unknown'})"}
|
|
634
|
+
|
|
635
|
+
# Determine suffix
|
|
636
|
+
suffix = ".png"
|
|
637
|
+
try:
|
|
638
|
+
main_type = content_type.split(";")[0].strip()
|
|
639
|
+
if "/" in main_type:
|
|
640
|
+
ext = main_type.split("/")[1].lower()
|
|
641
|
+
# Basic normalization
|
|
642
|
+
if ext in ("jpeg", "pjpeg"):
|
|
643
|
+
suffix = ".jpg"
|
|
644
|
+
elif ext in ("png", "gif", "bmp", "webp", "tiff"):
|
|
645
|
+
suffix = f".{ext}"
|
|
646
|
+
except Exception:
|
|
647
|
+
pass
|
|
648
|
+
# Fallback to URL path extension
|
|
649
|
+
if suffix == ".png":
|
|
650
|
+
path_ext = os.path.splitext(parsed.path or "")[1].lower()
|
|
651
|
+
if path_ext in (".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tif", ".tiff"):
|
|
652
|
+
suffix = path_ext
|
|
653
|
+
|
|
654
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
|
|
655
|
+
temp_path = temp_file.name
|
|
656
|
+
total = 0
|
|
657
|
+
for chunk in resp.iter_content(chunk_size=8192):
|
|
658
|
+
if not chunk:
|
|
659
|
+
continue
|
|
660
|
+
temp_file.write(chunk)
|
|
661
|
+
else:
|
|
662
|
+
req = urllib.request.Request(image_source, headers={"User-Agent": "Mozilla/5.0"})
|
|
663
|
+
with urllib.request.urlopen(req) as resp:
|
|
664
|
+
content_type = resp.headers.get("Content-Type", "")
|
|
665
|
+
if not content_type.startswith("image/"):
|
|
666
|
+
return {"error": f"URL content is not an image (Content-Type: {content_type or 'unknown'})"}
|
|
667
|
+
|
|
668
|
+
suffix = ".png"
|
|
669
|
+
try:
|
|
670
|
+
main_type = content_type.split(";")[0].strip()
|
|
671
|
+
if "/" in main_type:
|
|
672
|
+
ext = main_type.split("/")[1].lower()
|
|
673
|
+
if ext in ("jpeg", "pjpeg"):
|
|
674
|
+
suffix = ".jpg"
|
|
675
|
+
elif ext in ("png", "gif", "bmp", "webp", "tiff"):
|
|
676
|
+
suffix = f".{ext}"
|
|
677
|
+
except Exception:
|
|
678
|
+
pass
|
|
679
|
+
if suffix == ".png":
|
|
680
|
+
path_ext = os.path.splitext(parsed.path or "")[1].lower()
|
|
681
|
+
if path_ext in (".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tif", ".tiff"):
|
|
682
|
+
suffix = path_ext
|
|
683
|
+
|
|
684
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
|
|
685
|
+
temp_path = temp_file.name
|
|
686
|
+
total = 0
|
|
687
|
+
while True:
|
|
688
|
+
chunk = resp.read(8192)
|
|
689
|
+
if not chunk:
|
|
690
|
+
break
|
|
691
|
+
temp_file.write(chunk)
|
|
692
|
+
|
|
693
|
+
# Add image from temporary file
|
|
694
|
+
shape = ppt_utils.add_image(slide, temp_path, left, top, width, height)
|
|
695
|
+
|
|
696
|
+
# Clean up temporary file
|
|
697
|
+
if temp_path and os.path.exists(temp_path):
|
|
698
|
+
os.unlink(temp_path)
|
|
699
|
+
|
|
700
|
+
return {
|
|
701
|
+
"message": f"Added image from URL to slide {slide_index}",
|
|
702
|
+
"shape_index": len(slide.shapes) - 1
|
|
703
|
+
}
|
|
704
|
+
except Exception as e:
|
|
705
|
+
# Best-effort cleanup if temp_path was created
|
|
706
|
+
try:
|
|
707
|
+
if temp_path and os.path.exists(temp_path):
|
|
708
|
+
os.unlink(temp_path)
|
|
709
|
+
except Exception:
|
|
710
|
+
pass
|
|
711
|
+
return {"error": f"Failed to process image URL: {str(e)}"}
|
|
536
712
|
else:
|
|
537
713
|
# Handle file path
|
|
538
714
|
if not os.path.exists(image_source):
|
|
File without changes
|
|
File without changes
|