mcpcn-office-powerpoint-mcp-server 2.0.8__tar.gz → 2.0.9__tar.gz
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.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/PKG-INFO +1 -1
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/ppt_mcp_server.py +24 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/pyproject.toml +1 -1
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/tools/content_tools.py +118 -5
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/.gitignore +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/Dockerfile +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/LICENSE +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/README.md +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/__init__.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/mcp-config.json +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/requirements.txt +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/setup_mcp.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/slide_layout_templates.json +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/smithery.yaml +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/tools/__init__.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/tools/chart_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/tools/connector_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/tools/hyperlink_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/tools/master_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/tools/presentation_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/tools/professional_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/tools/structural_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/tools/template_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/tools/transition_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/utils/__init__.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/utils/content_utils.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/utils/core_utils.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/utils/design_utils.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/utils/presentation_utils.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/utils/template_utils.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/utils/validation_utils.py +0 -0
{mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/PKG-INFO
RENAMED
|
@@ -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
|
|
@@ -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
|
{mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/pyproject.toml
RENAMED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mcpcn-office-powerpoint-mcp-server"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.9"
|
|
8
8
|
description = "MCP Server for PowerPoint manipulation using python-pptx - Consolidated Edition"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {file = "LICENSE"}
|
|
@@ -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):
|
|
@@ -516,6 +524,7 @@ def register_content_tools(app: FastMCP, presentations: Dict, get_current_presen
|
|
|
516
524
|
- filter_type: Optional[str] — 过滤器类型(如 "DETAIL"、"SMOOTH" 等,取决于 Pillow 支持)。
|
|
517
525
|
- output_path: Optional[str] — 增强后图片的输出路径(不传则生成临时文件)。
|
|
518
526
|
- presentation_id: Optional[str] — 指定演示文稿 ID;不传则使用当前打开的演示文稿。
|
|
527
|
+
注:在某些部署环境(如你当前环境)中,必须显式传入 presentation_id 才会对目标文档生效。
|
|
519
528
|
|
|
520
529
|
返回
|
|
521
530
|
- operation="add":
|
|
@@ -528,33 +537,40 @@ def register_content_tools(app: FastMCP, presentations: Dict, get_current_presen
|
|
|
528
537
|
- 失败时返回 {"error": str}
|
|
529
538
|
|
|
530
539
|
注意事项
|
|
531
|
-
-
|
|
540
|
+
- 现在支持通过 URL 插入图片(仅 operation="add"):source_type="url",仅允许 http/https 协议。
|
|
541
|
+
会在内部下载到临时文件后插入并自动清理。若返回的 Content-Type 非 image/* 将返回错误。
|
|
542
|
+
enhance 仍然仅支持本地文件路径。
|
|
543
|
+
- 在某些部署环境(如你当前环境)中,需显式提供 presentation_id 参数,否则可能插入到非预期文档或不生效。
|
|
532
544
|
- operation="enhance" 不接受 Base64,必须提供可访问的本地文件路径。
|
|
533
545
|
- 插入 Base64 图片时,内部会写入临时文件后再插入,操作完成后临时文件会被清理。
|
|
534
546
|
- slide_index 必须在当前演示文稿的有效范围内,否则将返回错误。
|
|
535
547
|
|
|
536
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")
|
|
537
553
|
- 插入本地图片:
|
|
538
554
|
manage_image(slide_index=0, operation="add",
|
|
539
555
|
image_source="D:/images/logo.png", source_type="file",
|
|
540
|
-
left=1.0, top=1.0, width=3.0)
|
|
556
|
+
left=1.0, top=1.0, width=3.0, presentation_id="YOUR_PRESENTATION_ID")
|
|
541
557
|
|
|
542
558
|
- 插入 Base64 图片:
|
|
543
559
|
manage_image(slide_index=1, operation="add",
|
|
544
560
|
image_source="<BASE64字符串>", source_type="base64",
|
|
545
|
-
left=2.0, top=1.5, width=4.0, height=2.5)
|
|
561
|
+
left=2.0, top=1.5, width=4.0, height=2.5, presentation_id="YOUR_PRESENTATION_ID")
|
|
546
562
|
|
|
547
563
|
- 插入并应用专业增强(演示风格):
|
|
548
564
|
manage_image(slide_index=2, operation="add",
|
|
549
565
|
image_source="assets/photo.jpg", source_type="file",
|
|
550
|
-
enhancement_style="presentation", left=1.0, top=2.0)
|
|
566
|
+
enhancement_style="presentation", left=1.0, top=2.0, presentation_id="YOUR_PRESENTATION_ID")
|
|
551
567
|
|
|
552
568
|
- 增强已有图片文件(自定义参数):
|
|
553
569
|
manage_image(slide_index=0, operation="enhance",
|
|
554
570
|
image_source="assets/photo.jpg", source_type="file",
|
|
555
571
|
brightness=1.2, contrast=1.1, saturation=1.3,
|
|
556
572
|
sharpness=1.1, blur_radius=0, filter_type=None,
|
|
557
|
-
output_path="assets/photo_enhanced.jpg")
|
|
573
|
+
output_path="assets/photo_enhanced.jpg", presentation_id="YOUR_PRESENTATION_ID")
|
|
558
574
|
"""
|
|
559
575
|
pres_id = presentation_id if presentation_id is not None else get_current_presentation_id()
|
|
560
576
|
|
|
@@ -596,6 +612,103 @@ def register_content_tools(app: FastMCP, presentations: Dict, get_current_presen
|
|
|
596
612
|
return {
|
|
597
613
|
"error": f"Failed to process base64 image: {str(e)}"
|
|
598
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)}"}
|
|
599
712
|
else:
|
|
600
713
|
# Handle file path
|
|
601
714
|
if not os.path.exists(image_source):
|
{mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/.gitignore
RENAMED
|
File without changes
|
{mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/Dockerfile
RENAMED
|
File without changes
|
{mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/LICENSE
RENAMED
|
File without changes
|
{mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/README.md
RENAMED
|
File without changes
|
{mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/setup_mcp.py
RENAMED
|
File without changes
|
|
File without changes
|
{mcpcn_office_powerpoint_mcp_server-2.0.8 → mcpcn_office_powerpoint_mcp_server-2.0.9}/smithery.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|