mcpcn-office-powerpoint-mcp-server 2.0.9__py3-none-any.whl → 2.1.1__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.9.dist-info → mcpcn_office_powerpoint_mcp_server-2.1.1.dist-info}/METADATA +1 -1
- {mcpcn_office_powerpoint_mcp_server-2.0.9.dist-info → mcpcn_office_powerpoint_mcp_server-2.1.1.dist-info}/RECORD +6 -6
- tools/content_tools.py +33 -23
- {mcpcn_office_powerpoint_mcp_server-2.0.9.dist-info → mcpcn_office_powerpoint_mcp_server-2.1.1.dist-info}/WHEEL +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9.dist-info → mcpcn_office_powerpoint_mcp_server-2.1.1.dist-info}/entry_points.txt +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9.dist-info → mcpcn_office_powerpoint_mcp_server-2.1.1.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.
|
|
3
|
+
Version: 2.1.1
|
|
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
|
|
@@ -3,7 +3,7 @@ slide_layout_templates.json,sha256=ryA8zIZv6WBwjhBQaJXRWES3uqE7esqqFmXyiPEdauU,1
|
|
|
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=cJcAy8ZH3yD7icduajgIX8swb81jOyOzN6mwz2JmPdU,36813
|
|
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.
|
|
22
|
-
mcpcn_office_powerpoint_mcp_server-2.
|
|
23
|
-
mcpcn_office_powerpoint_mcp_server-2.
|
|
24
|
-
mcpcn_office_powerpoint_mcp_server-2.
|
|
25
|
-
mcpcn_office_powerpoint_mcp_server-2.
|
|
21
|
+
mcpcn_office_powerpoint_mcp_server-2.1.1.dist-info/METADATA,sha256=mLpc5PNzUJ2IkoQdVS2oJQCHbbiF4WvLaiL4cK_rpS0,35856
|
|
22
|
+
mcpcn_office_powerpoint_mcp_server-2.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
23
|
+
mcpcn_office_powerpoint_mcp_server-2.1.1.dist-info/entry_points.txt,sha256=4iWuiBR6HdlTqSJjCVyqyqVGpSGiD4ssw7LkEzmgvXw,75
|
|
24
|
+
mcpcn_office_powerpoint_mcp_server-2.1.1.dist-info/licenses/LICENSE,sha256=A-aSf9c0K2FKh7xtMkSr5Ot76NX8mrdLffsajnmZGKs,1064
|
|
25
|
+
mcpcn_office_powerpoint_mcp_server-2.1.1.dist-info/RECORD,,
|
tools/content_tools.py
CHANGED
|
@@ -333,6 +333,9 @@ def register_content_tools(app: FastMCP, presentations: Dict, get_current_presen
|
|
|
333
333
|
|
|
334
334
|
try:
|
|
335
335
|
if operation == "add":
|
|
336
|
+
# Auto-detect URL even if source_type is not explicitly "url"
|
|
337
|
+
if isinstance(image_source, str) and (image_source.startswith("http://") or image_source.startswith("https://")):
|
|
338
|
+
source_type = "url"
|
|
336
339
|
# Add new textbox
|
|
337
340
|
shape = ppt_utils.add_textbox(
|
|
338
341
|
slide, left, top, width, height, text,
|
|
@@ -504,7 +507,7 @@ def register_content_tools(app: FastMCP, presentations: Dict, get_current_presen
|
|
|
504
507
|
统一的图片处理工具(添加/增强)。
|
|
505
508
|
|
|
506
509
|
功能
|
|
507
|
-
- operation="add":将图片插入到指定幻灯片位置,支持本地文件或 Base64
|
|
510
|
+
- operation="add":将图片插入到指定幻灯片位置,支持本地文件或 Base64 图片源或图片地址。
|
|
508
511
|
- operation="enhance":对已有图片文件进行画质增强与风格化处理,输出增强后的图片路径。
|
|
509
512
|
|
|
510
513
|
参数
|
|
@@ -513,9 +516,10 @@ def register_content_tools(app: FastMCP, presentations: Dict, get_current_presen
|
|
|
513
516
|
- image_source: str —
|
|
514
517
|
* 当 source_type="file":本地图片文件路径。
|
|
515
518
|
* 当 source_type="base64":图片的 Base64 字符串。
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
*
|
|
519
|
+
* 当 source_type="url":图片的 http/https 地址。
|
|
520
|
+
- source_type: str — "file"、"base64" 或 "url"。
|
|
521
|
+
* add 支持 "file"、"base64"、"url"(仅允许 http/https)。
|
|
522
|
+
* enhance 仅支持 "file"(不接受 base64 或 url)。
|
|
519
523
|
- left, top: float — 插入位置(英寸)。
|
|
520
524
|
- width, height: Optional[float] — 插入尺寸(英寸)。可只提供一项以按比例缩放;都不提供则按图片原始尺寸。
|
|
521
525
|
- enhancement_style: Optional[str] — "presentation" 或 "custom"。当 operation="add" 且需要自动增强时可用;"presentation" 走预设的专业增强流程。
|
|
@@ -615,59 +619,63 @@ def register_content_tools(app: FastMCP, presentations: Dict, get_current_presen
|
|
|
615
619
|
elif source_type == "url":
|
|
616
620
|
# Handle image URL (http/https)
|
|
617
621
|
try:
|
|
618
|
-
|
|
622
|
+
# Normalize and percent-encode URL path/query to support spaces and non-ASCII characters
|
|
623
|
+
parsed = urllib.parse.urlsplit(image_source)
|
|
619
624
|
if parsed.scheme not in ("http", "https"):
|
|
620
625
|
return {"error": f"Unsupported URL scheme: {parsed.scheme}. Only http/https allowed."}
|
|
626
|
+
encoded_path = urllib.parse.quote(parsed.path or "", safe="/%")
|
|
627
|
+
# Re-encode query preserving keys and multiple values
|
|
628
|
+
qsl = urllib.parse.parse_qsl(parsed.query or "", keep_blank_values=True)
|
|
629
|
+
encoded_query = urllib.parse.urlencode(qsl, doseq=True)
|
|
630
|
+
encoded_url = urllib.parse.urlunsplit((parsed.scheme, parsed.netloc, encoded_path, encoded_query, parsed.fragment))
|
|
621
631
|
|
|
622
632
|
# Download helper using requests if available, else urllib
|
|
623
633
|
content_type = None
|
|
624
634
|
temp_path = None
|
|
635
|
+
image_exts = (".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tif", ".tiff")
|
|
625
636
|
|
|
626
637
|
if requests is not None:
|
|
627
|
-
with requests.get(
|
|
638
|
+
with requests.get(encoded_url, stream=True) as resp:
|
|
628
639
|
if resp.status_code != 200:
|
|
629
640
|
return {"error": f"Failed to download image. HTTP {resp.status_code}"}
|
|
630
|
-
content_type = resp.headers.get("Content-Type", "")
|
|
641
|
+
content_type = resp.headers.get("Content-Type", "") or ""
|
|
631
642
|
|
|
632
|
-
if not
|
|
633
|
-
return {"error": f"URL content is not an image (Content-Type: {content_type or 'unknown'})"}
|
|
634
|
-
|
|
635
|
-
# Determine suffix
|
|
643
|
+
# Determine suffix and allow fallback by URL extension if Content-Type missing or not image/*
|
|
636
644
|
suffix = ".png"
|
|
645
|
+
is_image = content_type.startswith("image/")
|
|
637
646
|
try:
|
|
638
|
-
main_type = content_type.split(";")[0].strip()
|
|
647
|
+
main_type = (content_type.split(";")[0].strip() if content_type else "")
|
|
639
648
|
if "/" in main_type:
|
|
640
649
|
ext = main_type.split("/")[1].lower()
|
|
641
|
-
# Basic normalization
|
|
642
650
|
if ext in ("jpeg", "pjpeg"):
|
|
643
651
|
suffix = ".jpg"
|
|
644
652
|
elif ext in ("png", "gif", "bmp", "webp", "tiff"):
|
|
645
653
|
suffix = f".{ext}"
|
|
646
654
|
except Exception:
|
|
647
655
|
pass
|
|
648
|
-
# Fallback to URL path extension
|
|
649
656
|
if suffix == ".png":
|
|
650
657
|
path_ext = os.path.splitext(parsed.path or "")[1].lower()
|
|
651
|
-
if path_ext in
|
|
658
|
+
if path_ext in image_exts:
|
|
652
659
|
suffix = path_ext
|
|
653
660
|
|
|
661
|
+
if not is_image and suffix not in image_exts:
|
|
662
|
+
return {"error": f"URL content is not an image (Content-Type: {content_type or 'unknown'})"}
|
|
663
|
+
|
|
654
664
|
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
|
|
655
665
|
temp_path = temp_file.name
|
|
656
|
-
total = 0
|
|
657
666
|
for chunk in resp.iter_content(chunk_size=8192):
|
|
658
667
|
if not chunk:
|
|
659
668
|
continue
|
|
660
669
|
temp_file.write(chunk)
|
|
661
670
|
else:
|
|
662
|
-
req = urllib.request.Request(
|
|
671
|
+
req = urllib.request.Request(encoded_url, headers={"User-Agent": "Mozilla/5.0"})
|
|
663
672
|
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'})"}
|
|
673
|
+
content_type = resp.headers.get("Content-Type", "") or ""
|
|
667
674
|
|
|
668
675
|
suffix = ".png"
|
|
676
|
+
is_image = content_type.startswith("image/")
|
|
669
677
|
try:
|
|
670
|
-
main_type = content_type.split(";")[0].strip()
|
|
678
|
+
main_type = (content_type.split(";")[0].strip() if content_type else "")
|
|
671
679
|
if "/" in main_type:
|
|
672
680
|
ext = main_type.split("/")[1].lower()
|
|
673
681
|
if ext in ("jpeg", "pjpeg"):
|
|
@@ -678,12 +686,14 @@ def register_content_tools(app: FastMCP, presentations: Dict, get_current_presen
|
|
|
678
686
|
pass
|
|
679
687
|
if suffix == ".png":
|
|
680
688
|
path_ext = os.path.splitext(parsed.path or "")[1].lower()
|
|
681
|
-
if path_ext in
|
|
689
|
+
if path_ext in image_exts:
|
|
682
690
|
suffix = path_ext
|
|
683
691
|
|
|
692
|
+
if not is_image and suffix not in image_exts:
|
|
693
|
+
return {"error": f"URL content is not an image (Content-Type: {content_type or 'unknown'})"}
|
|
694
|
+
|
|
684
695
|
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
|
|
685
696
|
temp_path = temp_file.name
|
|
686
|
-
total = 0
|
|
687
697
|
while True:
|
|
688
698
|
chunk = resp.read(8192)
|
|
689
699
|
if not chunk:
|
|
File without changes
|
|
File without changes
|