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.
Files changed (31) hide show
  1. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/PKG-INFO +1 -1
  2. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/pyproject.toml +1 -1
  3. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/content_tools.py +28 -19
  4. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/.gitignore +0 -0
  5. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/Dockerfile +0 -0
  6. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/LICENSE +0 -0
  7. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/README.md +0 -0
  8. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/__init__.py +0 -0
  9. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/mcp-config.json +0 -0
  10. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/ppt_mcp_server.py +0 -0
  11. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/requirements.txt +0 -0
  12. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/setup_mcp.py +0 -0
  13. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/slide_layout_templates.json +0 -0
  14. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/smithery.yaml +0 -0
  15. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/__init__.py +0 -0
  16. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/chart_tools.py +0 -0
  17. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/connector_tools.py +0 -0
  18. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/hyperlink_tools.py +0 -0
  19. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/master_tools.py +0 -0
  20. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/presentation_tools.py +0 -0
  21. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/professional_tools.py +0 -0
  22. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/structural_tools.py +0 -0
  23. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/template_tools.py +0 -0
  24. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/tools/transition_tools.py +0 -0
  25. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/utils/__init__.py +0 -0
  26. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/utils/content_utils.py +0 -0
  27. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/utils/core_utils.py +0 -0
  28. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/utils/design_utils.py +0 -0
  29. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/utils/presentation_utils.py +0 -0
  30. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/utils/template_utils.py +0 -0
  31. {mcpcn_office_powerpoint_mcp_server-2.0.9 → mcpcn_office_powerpoint_mcp_server-2.1.0}/utils/validation_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcpcn-office-powerpoint-mcp-server
3
- Version: 2.0.9
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
@@ -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.9"
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
- parsed = urllib.parse.urlparse(image_source)
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(image_source, stream=True) as resp:
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 content_type.startswith("image/"):
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 (".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tif", ".tiff"):
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(image_source, headers={"User-Agent": "Mozilla/5.0"})
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 (".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tif", ".tiff"):
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: