deckbuilder 1.0.0b1__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.
mcp_server/main.py ADDED
@@ -0,0 +1,550 @@
1
+ import asyncio
2
+ import json
3
+ import os
4
+ import sys
5
+ from collections.abc import AsyncIterator
6
+ from contextlib import asynccontextmanager
7
+ from dataclasses import dataclass
8
+
9
+ from dotenv import load_dotenv
10
+ from mcp.server.fastmcp import Context, FastMCP
11
+
12
+ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
13
+
14
+ from deckbuilder.engine import get_deckbuilder_client # noqa: E402
15
+
16
+ # Import content-first tools (will be implemented later)
17
+ try:
18
+ from .content_analysis import analyze_presentation_needs
19
+ from .content_optimization import optimize_content_for_layout
20
+ from .layout_recommendations import recommend_slide_approach
21
+ except ImportError:
22
+ # Placeholder functions for when these modules don't exist yet
23
+ def analyze_presentation_needs(*args, **kwargs):
24
+ return "Content analysis tool not implemented yet"
25
+
26
+ def recommend_slide_approach(*args, **kwargs):
27
+ return "Layout recommendation tool not implemented yet"
28
+
29
+ def optimize_content_for_layout(*args, **kwargs):
30
+ return "Content optimization tool not implemented yet"
31
+
32
+
33
+ deck = get_deckbuilder_client()
34
+
35
+
36
+ load_dotenv()
37
+
38
+
39
+ # Create a dataclass for our application context
40
+ @dataclass
41
+ class DeckbuilderContext:
42
+ """Context for the Deckbuilder MCP server."""
43
+
44
+ deckbuilder_client: str
45
+
46
+
47
+ @asynccontextmanager
48
+ async def deckbuilder_lifespan(server: FastMCP) -> AsyncIterator[DeckbuilderContext]:
49
+ """
50
+ Manages the Deckbuilder client lifecycle.
51
+
52
+ Args:
53
+ server: The Deckbuilder server instance
54
+
55
+ Yields:
56
+ PresentationContext: The context containing the Deckbuilder client
57
+ """
58
+
59
+ # Create and return the Deckbuilder Client with the helper function in deckbuilder.py
60
+ deckbuilder_client = get_deckbuilder_client()
61
+
62
+ try:
63
+ yield DeckbuilderContext(deckbuilder_client=deckbuilder_client)
64
+ finally:
65
+ # Explicit cleanup goes here if any is required
66
+ pass
67
+
68
+
69
+ # Initialize FastMCP server with the Deckbuilder client as context
70
+ mcp = FastMCP(
71
+ "deckbuilder",
72
+ description="MCP server for creation of powerpoint decks",
73
+ lifespan=deckbuilder_lifespan,
74
+ host=os.getenv("HOST", "0.0.0.0"), # nosec B104
75
+ port=os.getenv("PORT", "8050"),
76
+ )
77
+
78
+
79
+ @mcp.tool()
80
+ async def create_presentation(
81
+ ctx: Context,
82
+ json_data: dict,
83
+ fileName: str = "Sample_Presentation",
84
+ templateName: str = "default",
85
+ ) -> str:
86
+ """Create a complete PowerPoint presentation from JSON data
87
+
88
+ This tool accepts JSON data containing all slides and creates a complete presentation
89
+ with automatic saving. Supports all slide types: title, content, table, and all
90
+ available layouts with inline formatting.
91
+
92
+ IMPORTANT: Use the provided JSON data exactly as given by the user. Do not modify,
93
+ reformat, or adjust the JSON structure unless the tool fails with specific errors
94
+ that require fixes.
95
+
96
+ Args:
97
+ ctx: MCP context
98
+ json_data: JSON object containing presentation data with slides (use as-is)
99
+ fileName: Output filename (default: Sample_Presentation)
100
+ templateName: Template to use (default: default)
101
+
102
+ Example JSON format:
103
+ {
104
+ "presentation": {
105
+ "slides": [
106
+ {
107
+ "type": "title",
108
+ "title": "**My Title** with *formatting*",
109
+ "subtitle": "Subtitle with ___underline___"
110
+ },
111
+ {
112
+ "type": "content",
113
+ "title": "Content Slide",
114
+ "content": [
115
+ "**Bold** bullet point",
116
+ "*Italic* text with ___underline___",
117
+ "***Bold italic*** combination"
118
+ ]
119
+ },
120
+ {
121
+ "type": "table",
122
+ "title": "Table Example",
123
+ "table": {
124
+ "header_style": "dark_blue_white_text",
125
+ "row_style": "alternating_light_gray",
126
+ "data": [
127
+ ["**Header 1**", "*Header 2*", "___Header 3___"],
128
+ ["Data 1", "Data 2", "Data 3"]
129
+ ]
130
+ }
131
+ },
132
+ {
133
+ "type": "Picture with Caption",
134
+ "title": "Image Example",
135
+ "image_1": "path/to/image.png",
136
+ "text_caption_1": "**Professional** image with *automatic* fallback"
137
+ }
138
+ ]
139
+ }
140
+ }
141
+
142
+ Supported slide types:
143
+ - title: Title slide with title and subtitle
144
+ - content: Content slide with rich text, bullets, headings
145
+ - table: Table slide with full styling support
146
+ - Picture with Caption: Image slides with automatic PlaceKitten fallback
147
+ - All PowerPoint layout types are supported via the template mapping
148
+
149
+ Image support:
150
+ - Direct image insertion via image_1, image_2, etc. fields
151
+ - Automatic PlaceKitten fallback for missing images
152
+ - Professional styling: grayscale filter + smart cropping
153
+ - Intelligent face detection and rule-of-thirds composition
154
+ - Optimized caching for performance
155
+
156
+ Inline formatting support:
157
+ - **bold** - Bold text
158
+ - *italic* - Italic text
159
+ - ___underline___ - Underlined text
160
+ - ***bold italic*** - Combined bold and italic
161
+ - ***___all three___*** - Bold, italic, and underlined
162
+
163
+ Table styling options:
164
+ - header_style: dark_blue_white_text, light_blue_dark_text, etc.
165
+ - row_style: alternating_light_gray, solid_white, etc.
166
+ - border_style: thin_gray, thick_gray, no_borders, etc.
167
+ - custom_colors: Custom hex color overrides
168
+
169
+ IMPORTANT: Do NOT include markdown table separator lines (|---|---|---|) in table data.
170
+ Only include actual table rows with content.
171
+
172
+ USER CONTENT POLICY: When users provide JSON or markdown content (pasted or referenced),
173
+ use it exactly as provided. Do not modify, reformat, or "improve" the structure unless
174
+ the tool fails with specific errors requiring fixes. Respect the user's formatting choices.
175
+ """
176
+ try:
177
+ # Create presentation
178
+ deck.create_presentation(templateName, fileName)
179
+
180
+ # Add slides from JSON data
181
+ deck.add_slide_from_json(json_data)
182
+
183
+ # Automatically save the presentation
184
+ write_result = deck.write_presentation(fileName)
185
+
186
+ return f"Successfully created presentation: {fileName}. {write_result}"
187
+ except Exception as e:
188
+ return f"Error creating presentation: {str(e)}"
189
+
190
+
191
+ @mcp.tool()
192
+ async def analyze_presentation_needs_tool(
193
+ ctx: Context,
194
+ user_input: str,
195
+ audience: str = "general",
196
+ constraints: str = None,
197
+ presentation_goal: str = "inform",
198
+ ) -> str:
199
+ """
200
+ Analyze user's presentation needs and recommend structure.
201
+
202
+ Content-first approach: Understand communication goals before suggesting layouts.
203
+ Acts as intelligent presentation consultant, not layout picker.
204
+
205
+ Args:
206
+ ctx: MCP context
207
+ user_input: Raw description of what they want to present
208
+ audience: Target audience ("board", "team", "customers", "technical", "general")
209
+ constraints: Time/slide constraints ("10 minutes", "5 slides max", "data-heavy")
210
+ presentation_goal: Primary goal ("persuade", "inform", "report", "update", "train")
211
+
212
+ Returns:
213
+ JSON string with content analysis and structural recommendations
214
+
215
+ Example:
216
+ user_input: "I need to present our Q3 results to the board. We had 23% revenue
217
+ growth, expanded to 3 new markets, but customer churn increased to 8%.
218
+ I want to show we're growing but acknowledge the churn issue and present
219
+ our retention strategy."
220
+ audience: "board"
221
+ presentation_goal: "report"
222
+
223
+ Returns analysis with:
224
+ - Content analysis (key messages, narrative arc, complexity level)
225
+ - Audience considerations (expertise level, attention span, preferred format)
226
+ - Recommended structure (slide sequence with purpose and timing)
227
+ - Presentation strategy (opening/closing approach, engagement tactics)
228
+ """
229
+ try:
230
+ analysis_result = analyze_presentation_needs(
231
+ user_input, audience, constraints, presentation_goal
232
+ )
233
+ return json.dumps(analysis_result, indent=2)
234
+ except Exception as e:
235
+ return f"Error analyzing presentation needs: {str(e)}"
236
+
237
+
238
+ @mcp.tool()
239
+ async def recommend_slide_approach_tool(
240
+ ctx: Context, content_piece: str, message_intent: str, presentation_context: str = None
241
+ ) -> str:
242
+ """
243
+ Recommend optimal slide layouts based on specific content and communication intent.
244
+
245
+ Content-first approach: Analyzes what you want to communicate with this specific
246
+ content piece and recommends the most effective slide layouts.
247
+
248
+ Args:
249
+ ctx: MCP context
250
+ content_piece: Specific content to present (e.g., "We increased revenue 25%,
251
+ expanded to 3 markets, but churn rose to 8%")
252
+ message_intent: What you want this content to communicate (e.g.,
253
+ "show growth while acknowledging challenges")
254
+ presentation_context: Optional JSON string from analyze_presentation_needs_tool output
255
+
256
+ Returns:
257
+ JSON string with layout recommendations, confidence scores, and content suggestions
258
+
259
+ Example:
260
+ content_piece: "Our mobile app has these key features: real-time notifications,
261
+ offline mode, cloud sync, and advanced analytics dashboard"
262
+ message_intent: "showcase the comprehensive feature set to potential customers"
263
+
264
+ Returns recommendations like:
265
+ - Four Columns layout (confidence: 0.90) for feature comparison grid
266
+ - Title and Content layout (confidence: 0.75) for traditional feature list
267
+ - Content structuring suggestions and YAML preview
268
+
269
+ This tool bridges the gap between content analysis (Tool #1) and content optimization (Tool #3)
270
+ by providing specific layout guidance for individual content pieces.
271
+ """
272
+ try:
273
+ # Parse presentation context if provided
274
+ context_dict = None
275
+ if presentation_context:
276
+ try:
277
+ context_dict = json.loads(presentation_context)
278
+ except json.JSONDecodeError:
279
+ # If parsing fails, continue without context
280
+ pass
281
+
282
+ # Get layout recommendations
283
+ recommendations = recommend_slide_approach(content_piece, message_intent, context_dict)
284
+
285
+ return json.dumps(recommendations, indent=2)
286
+
287
+ except Exception as e:
288
+ return f"Error recommending slide approach: {str(e)}"
289
+
290
+
291
+ @mcp.tool()
292
+ async def optimize_content_for_layout_tool(
293
+ ctx: Context, content: str, chosen_layout: str, slide_context: str = None
294
+ ) -> str:
295
+ """
296
+ Optimize content structure and generate ready-to-use YAML for immediate presentation creation.
297
+
298
+ Final step in content-first workflow: Takes raw content and chosen layout, then optimizes
299
+ the content structure and generates production-ready YAML that can be used directly
300
+ with create_presentation_from_markdown.
301
+
302
+ Args:
303
+ ctx: MCP context
304
+ content: Raw content to optimize (e.g., "Our platform offers real-time
305
+ analytics, automated reporting, custom dashboards, and API integration")
306
+ chosen_layout: Layout to optimize for (e.g., "Four Columns" from
307
+ recommend_slide_approach_tool)
308
+ slide_context: Optional JSON string with context from previous tools
309
+ (analyze_presentation_needs, recommend_slide_approach)
310
+
311
+ Returns:
312
+ JSON string with optimized YAML structure, gap analysis, and presentation tips
313
+
314
+ Example:
315
+ content: "Traditional approach costs $50K annually with 2-week deployment
316
+ vs our solution at $30K with same-day setup"
317
+ chosen_layout: "Comparison"
318
+
319
+ Returns:
320
+ - optimized_content.yaml_structure: Ready-to-use YAML for create_presentation_from_markdown
321
+ - gap_analysis: Content fit assessment and recommendations
322
+ - presentation_tips: Delivery guidance and timing estimates
323
+ - image_recommendations: Suggested visual content with PlaceKitten fallback support
324
+
325
+ Complete Content-First Workflow:
326
+ 1. analyze_presentation_needs_tool() -> overall structure
327
+ 2. recommend_slide_approach_tool() -> layout recommendations
328
+ 3. optimize_content_for_layout_tool() -> production-ready YAML ✅ THIS TOOL
329
+ 4. create_presentation_from_markdown() -> final PowerPoint
330
+ """
331
+ try:
332
+ # Parse slide context if provided
333
+ context_dict = None
334
+ if slide_context:
335
+ try:
336
+ context_dict = json.loads(slide_context)
337
+ except json.JSONDecodeError:
338
+ # If parsing fails, continue without context
339
+ pass
340
+
341
+ # Optimize content for the chosen layout
342
+ optimization_result = optimize_content_for_layout(content, chosen_layout, context_dict)
343
+
344
+ return json.dumps(optimization_result, indent=2)
345
+
346
+ except Exception as e:
347
+ return f"Error optimizing content for layout: {str(e)}"
348
+
349
+
350
+ @mcp.tool()
351
+ async def create_presentation_from_file(
352
+ ctx: Context,
353
+ file_path: str,
354
+ fileName: str = "Sample_Presentation",
355
+ templateName: str = "default",
356
+ ) -> str:
357
+ """Create a complete PowerPoint presentation from JSON or markdown file
358
+
359
+ This tool reads presentation data directly from a local file without passing
360
+ content through the context window.
361
+ Supports both JSON files (.json) and markdown files (.md) with frontmatter.
362
+ Automatically detects file type and processes accordingly.
363
+
364
+ IMPORTANT: Process the file content exactly as provided. Do not modify the JSON
365
+ structure or markdown formatting unless the tool fails with specific errors
366
+ that require fixes.
367
+
368
+ Args:
369
+ ctx: MCP context
370
+ file_path: Absolute path to JSON or markdown file (process content as-is)
371
+ fileName: Output filename (default: Sample_Presentation)
372
+ templateName: Template to use (default: default)
373
+
374
+ Supported file types:
375
+ - .json files: JSON format with presentation data
376
+ - .md files: Markdown with frontmatter slide definitions
377
+
378
+ Example usage:
379
+ file_path: "/path/to/test_comprehensive_layouts.json"
380
+ file_path: "/path/to/presentation.md"
381
+
382
+ Benefits:
383
+ - No token usage for large presentation files
384
+ - Direct file system access
385
+ - Supports both JSON and markdown formats
386
+ - Automatic file type detection
387
+ """
388
+ try:
389
+ # Check if file exists
390
+ if not os.path.exists(file_path):
391
+ return f"Error: File not found: {file_path}"
392
+
393
+ # Determine file type and process accordingly
394
+ file_extension = os.path.splitext(file_path)[1].lower()
395
+
396
+ if file_extension == ".json":
397
+ # Read JSON file
398
+ with open(file_path, "r", encoding="utf-8") as f:
399
+ json_data = json.load(f)
400
+
401
+ # Create presentation from JSON
402
+ deck.create_presentation(templateName, fileName)
403
+ deck.add_slide_from_json(json_data)
404
+ write_result = deck.write_presentation(fileName)
405
+
406
+ return f"Successfully created presentation from JSON file: {file_path}. {write_result}"
407
+
408
+ elif file_extension == ".md":
409
+ # Read markdown file
410
+ with open(file_path, "r", encoding="utf-8") as f:
411
+ markdown_content = f.read()
412
+
413
+ # Create presentation from markdown
414
+ slides = deck.parse_markdown_with_frontmatter(markdown_content)
415
+ deck.create_presentation(templateName, fileName)
416
+
417
+ for slide_data in slides:
418
+ deck._add_slide(slide_data)
419
+
420
+ write_result = deck.write_presentation(fileName)
421
+
422
+ return (
423
+ f"Successfully created presentation from markdown file: "
424
+ f"{file_path} with {len(slides)} slides. {write_result}"
425
+ )
426
+
427
+ else:
428
+ return f"Error: Unsupported file type '{file_extension}'. Supported types: .json, .md"
429
+
430
+ except json.JSONDecodeError as e:
431
+ return f"Error parsing JSON file: {str(e)}"
432
+ except Exception as e:
433
+ return f"Error creating presentation from file: {str(e)}"
434
+
435
+
436
+ @mcp.tool()
437
+ async def create_presentation_from_markdown(
438
+ ctx: Context,
439
+ markdown_content: str,
440
+ fileName: str = "Sample_Presentation",
441
+ templateName: str = "default",
442
+ ) -> str:
443
+ """Create presentation from formatted markdown with frontmatter
444
+
445
+ This tool accepts markdown content with frontmatter slide definitions and
446
+ creates a complete presentation.
447
+ Each slide is defined using YAML frontmatter followed by markdown content.
448
+ This tool automatically saves the presentation to disk after creation.
449
+
450
+ IMPORTANT: Use the provided markdown content exactly as given by the user. Do not
451
+ modify the frontmatter structure, markdown formatting, or content unless the tool
452
+ fails with specific errors that require fixes.
453
+
454
+ Args:
455
+ ctx: MCP context
456
+ markdown_content: Markdown string with frontmatter (use as-is)
457
+ fileName: Output filename (default: Sample_Presentation)
458
+ templateName: Template/theme to use (default: default)
459
+
460
+ Example markdown format:
461
+ ---
462
+ layout: title
463
+ ---
464
+ # Main Title
465
+ ## Subtitle
466
+
467
+ ---
468
+ layout: content
469
+ ---
470
+ # Key Points
471
+
472
+ ## Overview
473
+ This section covers the main features of our product.
474
+
475
+ - Advanced analytics dashboard
476
+ - Real-time data processing
477
+ - Seamless API integration
478
+
479
+ The system scales automatically based on demand.
480
+
481
+ ---
482
+ layout: table
483
+ style: dark_blue_white_text
484
+ ---
485
+ # Sales Report
486
+ | Name | Sales | Region |
487
+ | John Smith | $125,000 | North |
488
+ | Sarah Johnson | $98,500 | South |
489
+
490
+ ---
491
+ layout: Picture with Caption
492
+ title: System **Architecture** Overview
493
+ media:
494
+ image_path: "diagrams/system_architecture.png"
495
+ alt_text: "System architecture diagram showing microservices"
496
+ caption: "***Scalable*** microservices with *intelligent* load balancing"
497
+ ---
498
+
499
+ Supported layouts:
500
+ - title: Title slide with title and subtitle
501
+ - content: Content slide with rich text support (headings, paragraphs, bullets)
502
+ - table: Table slide with styling options
503
+ - Picture with Caption: Image slides with PlaceKitten fallback support
504
+
505
+ Image features:
506
+ - Smart fallback: Missing images automatically use PlaceKitten placeholders
507
+ - Professional styling: Grayscale filter + intelligent cropping
508
+ - Face detection: Optimized cropping for portrait images
509
+ - Performance: Intelligent caching system for repeated use
510
+ - Accessibility: Alt text support for screen readers
511
+
512
+ Table styling options:
513
+ - style: Header style (dark_blue_white_text, light_blue_dark_text, etc.)
514
+ - row_style: Row style (alternating_light_gray, solid_white, etc.)
515
+ - border_style: Border style (thin_gray, thick_gray, no_borders, etc.)
516
+ - custom_colors: Custom color overrides (header_bg, header_text, alt_row, border_color)
517
+ """
518
+ try:
519
+ slides = deck.parse_markdown_with_frontmatter(markdown_content)
520
+
521
+ # Create presentation
522
+ deck.create_presentation(templateName, fileName)
523
+
524
+ # Add all slides to the presentation
525
+ for slide_data in slides:
526
+ deck._add_slide(slide_data)
527
+
528
+ # Automatically save the presentation to disk after creation
529
+ write_result = deck.write_presentation(fileName)
530
+
531
+ return (
532
+ f"Successfully created presentation with {len(slides)} slides "
533
+ f"from markdown. {write_result}"
534
+ )
535
+ except Exception as e:
536
+ return f"Error creating presentation from markdown: {str(e)}"
537
+
538
+
539
+ async def main():
540
+ transport = os.getenv("TRANSPORT", "sse")
541
+ if transport == "sse":
542
+ # Run the MCP server with sse transport
543
+ await mcp.run_sse_async()
544
+ else:
545
+ # Run the MCP server with stdio transport
546
+ await mcp.run_stdio_async()
547
+
548
+
549
+ if __name__ == "__main__":
550
+ asyncio.run(main())