mcpcn-office-powerpoint-mcp-server 2.0.9__tar.gz → 2.1.0__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.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/PKG-INFO +1 -1
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/pyproject.toml +1 -1
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/content_tools.py +28 -19
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/.gitignore +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/Dockerfile +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/LICENSE +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/README.md +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/__init__.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/mcp-config.json +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/ppt_mcp_server.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/requirements.txt +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/setup_mcp.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/slide_layout_templates.json +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/smithery.yaml +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/__init__.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/chart_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/connector_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/hyperlink_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/master_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/presentation_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/professional_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/structural_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/template_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/transition_tools.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/utils/__init__.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/utils/content_utils.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/utils/core_utils.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/utils/design_utils.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/utils/presentation_utils.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/utils/template_utils.py +0 -0
- {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/utils/validation_utils.py +0 -0
{mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/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.1.0
|
|
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
|
{mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/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.1.0"
|
|
8
8
|
description = "MCP Server for PowerPoint manipulation using python-pptx - Consolidated Edition"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {file = "LICENSE"}
|
|
@@ -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,
|
|
@@ -615,59 +618,63 @@ def register_content_tools(app: FastMCP, presentations: Dict, get_current_presen
|
|
|
615
618
|
elif source_type == "url":
|
|
616
619
|
# Handle image URL (http/https)
|
|
617
620
|
try:
|
|
618
|
-
|
|
621
|
+
# Normalize and percent-encode URL path/query to support spaces and non-ASCII characters
|
|
622
|
+
parsed = urllib.parse.urlsplit(image_source)
|
|
619
623
|
if parsed.scheme not in ("http", "https"):
|
|
620
624
|
return {"error": f"Unsupported URL scheme: {parsed.scheme}. Only http/https allowed."}
|
|
625
|
+
encoded_path = urllib.parse.quote(parsed.path or "", safe="/%")
|
|
626
|
+
# Re-encode query preserving keys and multiple values
|
|
627
|
+
qsl = urllib.parse.parse_qsl(parsed.query or "", keep_blank_values=True)
|
|
628
|
+
encoded_query = urllib.parse.urlencode(qsl, doseq=True)
|
|
629
|
+
encoded_url = urllib.parse.urlunsplit((parsed.scheme, parsed.netloc, encoded_path, encoded_query, parsed.fragment))
|
|
621
630
|
|
|
622
631
|
# Download helper using requests if available, else urllib
|
|
623
632
|
content_type = None
|
|
624
633
|
temp_path = None
|
|
634
|
+
image_exts = (".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tif", ".tiff")
|
|
625
635
|
|
|
626
636
|
if requests is not None:
|
|
627
|
-
with requests.get(
|
|
637
|
+
with requests.get(encoded_url, stream=True) as resp:
|
|
628
638
|
if resp.status_code != 200:
|
|
629
639
|
return {"error": f"Failed to download image. HTTP {resp.status_code}"}
|
|
630
|
-
content_type = resp.headers.get("Content-Type", "")
|
|
640
|
+
content_type = resp.headers.get("Content-Type", "") or ""
|
|
631
641
|
|
|
632
|
-
if not
|
|
633
|
-
return {"error": f"URL content is not an image (Content-Type: {content_type or 'unknown'})"}
|
|
634
|
-
|
|
635
|
-
# Determine suffix
|
|
642
|
+
# Determine suffix and allow fallback by URL extension if Content-Type missing or not image/*
|
|
636
643
|
suffix = ".png"
|
|
644
|
+
is_image = content_type.startswith("image/")
|
|
637
645
|
try:
|
|
638
|
-
main_type = content_type.split(";")[0].strip()
|
|
646
|
+
main_type = (content_type.split(";")[0].strip() if content_type else "")
|
|
639
647
|
if "/" in main_type:
|
|
640
648
|
ext = main_type.split("/")[1].lower()
|
|
641
|
-
# Basic normalization
|
|
642
649
|
if ext in ("jpeg", "pjpeg"):
|
|
643
650
|
suffix = ".jpg"
|
|
644
651
|
elif ext in ("png", "gif", "bmp", "webp", "tiff"):
|
|
645
652
|
suffix = f".{ext}"
|
|
646
653
|
except Exception:
|
|
647
654
|
pass
|
|
648
|
-
# Fallback to URL path extension
|
|
649
655
|
if suffix == ".png":
|
|
650
656
|
path_ext = os.path.splitext(parsed.path or "")[1].lower()
|
|
651
|
-
if path_ext in
|
|
657
|
+
if path_ext in image_exts:
|
|
652
658
|
suffix = path_ext
|
|
653
659
|
|
|
660
|
+
if not is_image and suffix not in image_exts:
|
|
661
|
+
return {"error": f"URL content is not an image (Content-Type: {content_type or 'unknown'})"}
|
|
662
|
+
|
|
654
663
|
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
|
|
655
664
|
temp_path = temp_file.name
|
|
656
|
-
total = 0
|
|
657
665
|
for chunk in resp.iter_content(chunk_size=8192):
|
|
658
666
|
if not chunk:
|
|
659
667
|
continue
|
|
660
668
|
temp_file.write(chunk)
|
|
661
669
|
else:
|
|
662
|
-
req = urllib.request.Request(
|
|
670
|
+
req = urllib.request.Request(encoded_url, headers={"User-Agent": "Mozilla/5.0"})
|
|
663
671
|
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'})"}
|
|
672
|
+
content_type = resp.headers.get("Content-Type", "") or ""
|
|
667
673
|
|
|
668
674
|
suffix = ".png"
|
|
675
|
+
is_image = content_type.startswith("image/")
|
|
669
676
|
try:
|
|
670
|
-
main_type = content_type.split(";")[0].strip()
|
|
677
|
+
main_type = (content_type.split(";")[0].strip() if content_type else "")
|
|
671
678
|
if "/" in main_type:
|
|
672
679
|
ext = main_type.split("/")[1].lower()
|
|
673
680
|
if ext in ("jpeg", "pjpeg"):
|
|
@@ -678,12 +685,14 @@ def register_content_tools(app: FastMCP, presentations: Dict, get_current_presen
|
|
|
678
685
|
pass
|
|
679
686
|
if suffix == ".png":
|
|
680
687
|
path_ext = os.path.splitext(parsed.path or "")[1].lower()
|
|
681
|
-
if path_ext in
|
|
688
|
+
if path_ext in image_exts:
|
|
682
689
|
suffix = path_ext
|
|
683
690
|
|
|
691
|
+
if not is_image and suffix not in image_exts:
|
|
692
|
+
return {"error": f"URL content is not an image (Content-Type: {content_type or 'unknown'})"}
|
|
693
|
+
|
|
684
694
|
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
|
|
685
695
|
temp_path = temp_file.name
|
|
686
|
-
total = 0
|
|
687
696
|
while True:
|
|
688
697
|
chunk = resp.read(8192)
|
|
689
698
|
if not chunk:
|
{mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/.gitignore
RENAMED
|
File without changes
|
{mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/Dockerfile
RENAMED
|
File without changes
|
{mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/LICENSE
RENAMED
|
File without changes
|
{mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/README.md
RENAMED
|
File without changes
|
{mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/setup_mcp.py
RENAMED
|
File without changes
|
|
File without changes
|
{mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/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
|