mcpcn-office-powerpoint-mcp-server 2.0.7__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.
@@ -0,0 +1,25 @@
1
+ ppt_mcp_server.py,sha256=IX9oqlVBRpaBIhWJcIwoUQpdAmeKowtBQatHYSEiDU0,14264
2
+ slide_layout_templates.json,sha256=ryA8zIZv6WBwjhBQaJXRWES3uqE7esqqFmXyiPEdauU,107256
3
+ tools/__init__.py,sha256=b53px-U7Ny4-FMnAdFLoJMtPxD7lt7DB8ydvcdATCz0,983
4
+ tools/chart_tools.py,sha256=vvXI53gm9_2mfNOiG-qCfwLdqqr2pe_uDXHN9adVsZk,3124
5
+ tools/connector_tools.py,sha256=8cUPAfTZ-5vvooLCVQIWfblMzcFe3cPdh_bG5UezykQ,3402
6
+ tools/content_tools.py,sha256=uoIg7dJNIJEO_NA5QZfDB7cxGGixN6BTOAaxg8Fnmk4,24996
7
+ tools/hyperlink_tools.py,sha256=EweAxbYGvOpNpzDxH5DnrFzGMQmnzNGGehEoCCx9d18,5920
8
+ tools/master_tools.py,sha256=05a7ZUEDN5a7ySwG97Sj4UJK7Y0pcCxang553Bh7iOM,4986
9
+ tools/presentation_tools.py,sha256=rnVRYOu4A0Sw97sf8qKBK6-q5dYnK1vO2XNAzN8je4o,7977
10
+ tools/professional_tools.py,sha256=1nC45Zx2zV-zbWUwqFhgcejIDyxNdbTfi1kymFi-v88,12109
11
+ tools/structural_tools.py,sha256=0MHdwkpYRj-BqkTF31uavtDoPviNQ4GSQofaQ66WlLg,14444
12
+ tools/template_tools.py,sha256=Iz9TIYF2R42FDPX2usU1xk327shgeVDFt3rgJ1aTch8,23516
13
+ tools/transition_tools.py,sha256=lpWw8dwziNzxvqPcVJsBer2XlkXBAYskkTh6gzwQMRk,3116
14
+ utils/__init__.py,sha256=9GRC7Ms2ZGNlZkZTaFicLO2iaPRcyNkESTaVLV911eg,1698
15
+ utils/content_utils.py,sha256=0nuOSUpx6D84zwbkQIkbAg0esgYtMdikq_WgxuqhoJo,19700
16
+ utils/core_utils.py,sha256=k4sZOdY5XesyrpVDInrBomL07QjuzYP4lnCkxNFwm2A,2113
17
+ utils/design_utils.py,sha256=vEstbqsBe202PYS4NvqdOLL3Aq5na7G2SBJnY-BYD_g,23761
18
+ utils/presentation_utils.py,sha256=9Y4F0twPvr0srjq53_Szkwxcn7dh06IWy3snRLaMuM8,6262
19
+ utils/template_utils.py,sha256=8iSJcJIgoOmi53TtlRnMwRLTDI84EfSW2PWj8EW3Z1k,44574
20
+ utils/validation_utils.py,sha256=0OiwFE1WZh05ZP-0hLzidyxKSUIJaJ3QrV0_s6FpxZk,11775
21
+ mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info/METADATA,sha256=ZCHC_yMRsYUyAJ-5fKfQOEG8kkTcX6DhgbE09dRyKVY,35855
22
+ mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info/entry_points.txt,sha256=4iWuiBR6HdlTqSJjCVyqyqVGpSGiD4ssw7LkEzmgvXw,75
24
+ mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info/licenses/LICENSE,sha256=A-aSf9c0K2FKh7xtMkSr5Ot76NX8mrdLffsajnmZGKs,1064
25
+ mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mcpcn-office-powerpoint-mcp-server = ppt_mcp_server:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 GongRzhe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
ppt_mcp_server.py ADDED
@@ -0,0 +1,450 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ MCP Server for PowerPoint manipulation using python-pptx.
4
+ Consolidated version with 20 tools organized into multiple modules.
5
+ """
6
+ import os
7
+ import argparse
8
+ from typing import Dict, Any
9
+ from mcp.server.fastmcp import FastMCP
10
+
11
+ # import utils # Currently unused
12
+ from tools import (
13
+ register_presentation_tools,
14
+ register_content_tools,
15
+ register_structural_tools,
16
+ register_professional_tools,
17
+ register_template_tools,
18
+ register_hyperlink_tools,
19
+ register_chart_tools,
20
+ register_connector_tools,
21
+ register_master_tools,
22
+ register_transition_tools
23
+ )
24
+
25
+ # Initialize the FastMCP server
26
+ app = FastMCP(
27
+ name="ppt-mcp-server"
28
+ )
29
+
30
+ # Global state to store presentations in memory
31
+ presentations = {}
32
+ current_presentation_id = None
33
+
34
+ # Template configuration
35
+ def get_template_search_directories():
36
+ """
37
+ Get list of directories to search for templates.
38
+ Uses environment variable PPT_TEMPLATE_PATH if set, otherwise uses default directories.
39
+
40
+ Returns:
41
+ List of directories to search for templates
42
+ """
43
+ template_env_path = os.environ.get('PPT_TEMPLATE_PATH')
44
+
45
+ if template_env_path:
46
+ # If environment variable is set, use it as the primary template directory
47
+ # Support multiple paths separated by colon (Unix) or semicolon (Windows)
48
+ import platform
49
+ separator = ';' if platform.system() == "Windows" else ':'
50
+ env_dirs = [path.strip() for path in template_env_path.split(separator) if path.strip()]
51
+
52
+ # Verify that the directories exist
53
+ valid_env_dirs = []
54
+ for dir_path in env_dirs:
55
+ expanded_path = os.path.expanduser(dir_path)
56
+ if os.path.exists(expanded_path) and os.path.isdir(expanded_path):
57
+ valid_env_dirs.append(expanded_path)
58
+
59
+ if valid_env_dirs:
60
+ # Add default fallback directories
61
+ return valid_env_dirs + ['.', './templates', './assets', './resources']
62
+ else:
63
+ print(f"Warning: PPT_TEMPLATE_PATH directories not found: {template_env_path}")
64
+
65
+ # Default search directories when no environment variable or invalid paths
66
+ return ['.', './templates', './assets', './resources']
67
+
68
+ # ---- Helper Functions ----
69
+
70
+ def get_current_presentation():
71
+ """Get the current presentation object or raise an error if none is loaded."""
72
+ if current_presentation_id is None or current_presentation_id not in presentations:
73
+ raise ValueError("No presentation is currently loaded. Please create or open a presentation first.")
74
+ return presentations[current_presentation_id]
75
+
76
+ def get_current_presentation_id():
77
+ """Get the current presentation ID."""
78
+ return current_presentation_id
79
+
80
+ def set_current_presentation_id(pres_id):
81
+ """Set the current presentation ID."""
82
+ global current_presentation_id
83
+ current_presentation_id = pres_id
84
+
85
+ def validate_parameters(params):
86
+ """
87
+ Validate parameters against constraints.
88
+
89
+ Args:
90
+ params: Dictionary of parameter name: (value, constraints) pairs
91
+
92
+ Returns:
93
+ (True, None) if all valid, or (False, error_message) if invalid
94
+ """
95
+ for param_name, (value, constraints) in params.items():
96
+ for constraint_func, error_msg in constraints:
97
+ if not constraint_func(value):
98
+ return False, f"Parameter '{param_name}': {error_msg}"
99
+ return True, None
100
+
101
+ def is_positive(value):
102
+ """Check if a value is positive."""
103
+ return value > 0
104
+
105
+ def is_non_negative(value):
106
+ """Check if a value is non-negative."""
107
+ return value >= 0
108
+
109
+ def is_in_range(min_val, max_val):
110
+ """Create a function that checks if a value is in a range."""
111
+ return lambda x: min_val <= x <= max_val
112
+
113
+ def is_in_list(valid_list):
114
+ """Create a function that checks if a value is in a list."""
115
+ return lambda x: x in valid_list
116
+
117
+ def is_valid_rgb(color_list):
118
+ """Check if a color list is a valid RGB tuple."""
119
+ if not isinstance(color_list, list) or len(color_list) != 3:
120
+ return False
121
+ return all(isinstance(c, int) and 0 <= c <= 255 for c in color_list)
122
+
123
+ def add_shape_direct(slide, shape_type: str, left: float, top: float, width: float, height: float) -> Any:
124
+ """
125
+ Add an auto shape to a slide using direct integer values instead of enum objects.
126
+
127
+ This implementation provides a reliable alternative that bypasses potential
128
+ enum-related issues in the python-pptx library.
129
+
130
+ Args:
131
+ slide: The slide object
132
+ shape_type: Shape type string (e.g., 'rectangle', 'oval', 'triangle')
133
+ left: Left position in inches
134
+ top: Top position in inches
135
+ width: Width in inches
136
+ height: Height in inches
137
+
138
+ Returns:
139
+ The created shape
140
+ """
141
+ from pptx.util import Inches
142
+
143
+ # Direct mapping of shape types to their integer values
144
+ # These values are directly from the MS Office VBA documentation
145
+ shape_type_map = {
146
+ 'rectangle': 1,
147
+ 'rounded_rectangle': 2,
148
+ 'oval': 9,
149
+ 'diamond': 4,
150
+ 'triangle': 5, # This is ISOSCELES_TRIANGLE
151
+ 'right_triangle': 6,
152
+ 'pentagon': 56,
153
+ 'hexagon': 10,
154
+ 'heptagon': 11,
155
+ 'octagon': 12,
156
+ 'star': 12, # This is STAR_5_POINTS (value 12)
157
+ 'arrow': 13,
158
+ 'cloud': 35,
159
+ 'heart': 21,
160
+ 'lightning_bolt': 22,
161
+ 'sun': 23,
162
+ 'moon': 24,
163
+ 'smiley_face': 17,
164
+ 'no_symbol': 19,
165
+ 'flowchart_process': 112,
166
+ 'flowchart_decision': 114,
167
+ 'flowchart_data': 115,
168
+ 'flowchart_document': 119
169
+ }
170
+
171
+ # Check if shape type is valid before trying to use it
172
+ shape_type_lower = str(shape_type).lower()
173
+ if shape_type_lower not in shape_type_map:
174
+ available_shapes = ', '.join(sorted(shape_type_map.keys()))
175
+ raise ValueError(f"Unsupported shape type: '{shape_type}'. Available shape types: {available_shapes}")
176
+
177
+ # Get the integer value for the shape type
178
+ shape_value = shape_type_map[shape_type_lower]
179
+
180
+ # Create the shape using the direct integer value
181
+ try:
182
+ # The integer value is passed directly to add_shape
183
+ shape = slide.shapes.add_shape(
184
+ shape_value, Inches(left), Inches(top), Inches(width), Inches(height)
185
+ )
186
+ return shape
187
+ except Exception as e:
188
+ raise ValueError(f"Failed to create '{shape_type}' shape using direct value {shape_value}: {str(e)}")
189
+
190
+ # ---- Custom presentation management wrapper ----
191
+
192
+ class PresentationManager:
193
+ """Wrapper to handle presentation state updates."""
194
+
195
+ def __init__(self, presentations_dict):
196
+ self.presentations = presentations_dict
197
+
198
+ def store_presentation(self, pres, pres_id):
199
+ """Store a presentation and set it as current."""
200
+ self.presentations[pres_id] = pres
201
+ set_current_presentation_id(pres_id)
202
+ return pres_id
203
+
204
+ # ---- Register Tools ----
205
+
206
+ # Create presentation manager wrapper
207
+ presentation_manager = PresentationManager(presentations)
208
+
209
+ # Wrapper functions to handle state management
210
+ def create_presentation_wrapper(original_func):
211
+ """Wrapper to handle presentation creation with state management."""
212
+ def wrapper(*args, **kwargs):
213
+ result = original_func(*args, **kwargs)
214
+ if "presentation_id" in result and result["presentation_id"] in presentations:
215
+ set_current_presentation_id(result["presentation_id"])
216
+ return result
217
+ return wrapper
218
+
219
+ def open_presentation_wrapper(original_func):
220
+ """Wrapper to handle presentation opening with state management."""
221
+ def wrapper(*args, **kwargs):
222
+ result = original_func(*args, **kwargs)
223
+ if "presentation_id" in result and result["presentation_id"] in presentations:
224
+ set_current_presentation_id(result["presentation_id"])
225
+ return result
226
+ return wrapper
227
+
228
+ # Register all tool modules
229
+ register_presentation_tools(
230
+ app,
231
+ presentations,
232
+ get_current_presentation_id,
233
+ get_template_search_directories
234
+ )
235
+
236
+ register_content_tools(
237
+ app,
238
+ presentations,
239
+ get_current_presentation_id,
240
+ validate_parameters,
241
+ is_positive,
242
+ is_non_negative,
243
+ is_in_range,
244
+ is_valid_rgb
245
+ )
246
+
247
+ register_structural_tools(
248
+ app,
249
+ presentations,
250
+ get_current_presentation_id,
251
+ validate_parameters,
252
+ is_positive,
253
+ is_non_negative,
254
+ is_in_range,
255
+ is_valid_rgb,
256
+ add_shape_direct
257
+ )
258
+
259
+ register_professional_tools(
260
+ app,
261
+ presentations,
262
+ get_current_presentation_id
263
+ )
264
+
265
+ register_template_tools(
266
+ app,
267
+ presentations,
268
+ get_current_presentation_id
269
+ )
270
+
271
+ register_hyperlink_tools(
272
+ app,
273
+ presentations,
274
+ get_current_presentation_id,
275
+ validate_parameters,
276
+ is_positive,
277
+ is_non_negative,
278
+ is_in_range,
279
+ is_valid_rgb
280
+ )
281
+
282
+ register_chart_tools(
283
+ app,
284
+ presentations,
285
+ get_current_presentation_id,
286
+ validate_parameters,
287
+ is_positive,
288
+ is_non_negative,
289
+ is_in_range,
290
+ is_valid_rgb
291
+ )
292
+
293
+
294
+ register_connector_tools(
295
+ app,
296
+ presentations,
297
+ get_current_presentation_id,
298
+ validate_parameters,
299
+ is_positive,
300
+ is_non_negative,
301
+ is_in_range,
302
+ is_valid_rgb
303
+ )
304
+
305
+ register_master_tools(
306
+ app,
307
+ presentations,
308
+ get_current_presentation_id,
309
+ validate_parameters,
310
+ is_positive,
311
+ is_non_negative,
312
+ is_in_range,
313
+ is_valid_rgb
314
+ )
315
+
316
+ register_transition_tools(
317
+ app,
318
+ presentations,
319
+ get_current_presentation_id,
320
+ validate_parameters,
321
+ is_positive,
322
+ is_non_negative,
323
+ is_in_range,
324
+ is_valid_rgb
325
+ )
326
+
327
+
328
+ # ---- Additional Utility Tools ----
329
+
330
+ @app.tool()
331
+ def list_presentations() -> Dict:
332
+ """List all loaded presentations."""
333
+ return {
334
+ "presentations": [
335
+ {
336
+ "id": pres_id,
337
+ "slide_count": len(pres.slides),
338
+ "is_current": pres_id == current_presentation_id
339
+ }
340
+ for pres_id, pres in presentations.items()
341
+ ],
342
+ "current_presentation_id": current_presentation_id,
343
+ "total_presentations": len(presentations)
344
+ }
345
+
346
+ @app.tool()
347
+ def switch_presentation(presentation_id: str) -> Dict:
348
+ """Switch to a different loaded presentation."""
349
+ if presentation_id not in presentations:
350
+ return {
351
+ "error": f"Presentation '{presentation_id}' not found. Available presentations: {list(presentations.keys())}"
352
+ }
353
+
354
+ global current_presentation_id
355
+ old_id = current_presentation_id
356
+ current_presentation_id = presentation_id
357
+
358
+ return {
359
+ "message": f"Switched from presentation '{old_id}' to '{presentation_id}'",
360
+ "previous_presentation_id": old_id,
361
+ "current_presentation_id": current_presentation_id
362
+ }
363
+
364
+ @app.tool()
365
+ def get_server_info() -> Dict:
366
+ """Get information about the MCP server."""
367
+ return {
368
+ "name": "PowerPoint MCP Server - Enhanced Edition",
369
+ "version": "2.1.0",
370
+ "total_tools": 32, # Organized into 11 specialized modules
371
+ "loaded_presentations": len(presentations),
372
+ "current_presentation": current_presentation_id,
373
+ "features": [
374
+ "Presentation Management (7 tools)",
375
+ "Content Management (6 tools)",
376
+ "Template Operations (7 tools)",
377
+ "Structural Elements (4 tools)",
378
+ "Professional Design (3 tools)",
379
+ "Specialized Features (5 tools)"
380
+ ],
381
+ "improvements": [
382
+ "32 specialized tools organized into 11 focused modules",
383
+ "68+ utility functions across 7 organized utility modules",
384
+ "Enhanced parameter handling and validation",
385
+ "Unified operation interfaces with comprehensive coverage",
386
+ "Advanced template system with auto-generation capabilities",
387
+ "Professional design tools with multiple effects and styling",
388
+ "Specialized features including hyperlinks, connectors, slide masters",
389
+ "Dynamic text sizing and intelligent wrapping",
390
+ "Advanced visual effects and styling",
391
+ "Content-aware optimization and validation",
392
+ "Complete PowerPoint lifecycle management",
393
+ "Modular architecture for better maintainability"
394
+ ],
395
+ "new_enhanced_features": [
396
+ "Hyperlink Management - Add, update, remove, and list hyperlinks in text",
397
+ "Advanced Chart Data Updates - Replace chart data with new categories and series",
398
+ "Advanced Text Run Formatting - Apply formatting to specific text runs",
399
+ "Shape Connectors - Add connector lines and arrows between points",
400
+ "Slide Master Management - Access and manage slide masters and layouts",
401
+ "Slide Transitions - Basic transition management (placeholder for future)"
402
+ ]
403
+ }
404
+
405
+ # ---- Main Function ----
406
+ def main(transport: str = "stdio", port: int = 8000):
407
+ if transport == "http":
408
+ import asyncio
409
+ # Set the port for HTTP transport
410
+ app.settings.port = port
411
+ # Start the FastMCP server with HTTP transport
412
+ try:
413
+ app.run(transport='streamable-http')
414
+ except asyncio.exceptions.CancelledError:
415
+ print("Server stopped by user.")
416
+ except KeyboardInterrupt:
417
+ print("Server stopped by user.")
418
+ except Exception as e:
419
+ print(f"Error starting server: {e}")
420
+
421
+ elif transport == "sse":
422
+ # Run the FastMCP server in SSE (Server Side Events) mode
423
+ app.run(transport='sse')
424
+
425
+ else:
426
+ # Run the FastMCP server
427
+ app.run(transport='stdio')
428
+
429
+ if __name__ == "__main__":
430
+ # Parse command line arguments
431
+ parser = argparse.ArgumentParser(description="MCP Server for PowerPoint manipulation using python-pptx")
432
+
433
+ parser.add_argument(
434
+ "-t",
435
+ "--transport",
436
+ type=str,
437
+ default="stdio",
438
+ choices=["stdio", "http", "sse"],
439
+ help="Transport method for the MCP server (default: stdio)"
440
+ )
441
+
442
+ parser.add_argument(
443
+ "-p",
444
+ "--port",
445
+ type=int,
446
+ default=8000,
447
+ help="Port to run the MCP server on (default: 8000)"
448
+ )
449
+ args = parser.parse_args()
450
+ main(args.transport, args.port)