dial-pptx-mcp
Remote multi-tenant MCP server for generating PowerPoint presentations from corporate templates, preserving theme and branding. Integrates with EPAM AI DIAL file storage and Quick Apps for template input and deck export.
README
dial-pptx-mcp
A remote, multi-tenant PowerPoint-generation MCP server for EPAM AI DIAL.
Generates .pptx presentations from corporate templates while preserving the template's theme, layouts, masters, and branding. Designed to be registered as an MCPToolSet in DIAL Quick Apps, with file input/output flowing through DIAL Core file storage.
This project extends GongRzhe/Office-PowerPoint-MCP-Server (MIT) — see Credits.
What this adds over upstream
| Area | Upstream | This project |
|---|---|---|
| Transport | stdio (single local client) | streamable-http / SSE, container-ready (PPT_MCP_* env vars) |
| State | process globals, guessable sequential IDs | per-deck UUID handles (unguessable), thread-safe store with TTL + LRU bounds, per-deck locking |
| File I/O | local disk paths | DIAL Files API: template in via Quick Apps file:data:: references, deck out via export_presentation returning a DIAL file URL |
| Deployment | — | Dockerfile (non-root, HTTP defaults) + generic Kubernetes example |
The ~30 upstream content/formatting tools (slides, text, charts, tables, connectors, hyperlinks, masters, transitions) are unchanged — see docs/UPSTREAM_README.md for the full tool reference.
Configuration
All environment-specific settings come from environment variables. Nothing is hardcoded.
| Variable | Required | Default | Purpose |
|---|---|---|---|
PPT_MCP_TRANSPORT |
no | stdio |
http (streamable-http, recommended for DIAL), sse, or stdio |
PPT_MCP_HOST |
no | 127.0.0.1 |
Bind address; set 0.0.0.0 in containers (the Dockerfile does) |
PPT_MCP_PORT |
no | 8000 |
Listen port for http/sse |
PPT_MCP_ALLOWED_HOSTS |
no | dial-pptx-mcp.dial.svc.cluster.local |
Comma-separated Host-header allowlist for http/sse. Only listed hosts (plus localhost) are accepted; others get 421. The default matches the standard in-cluster service name — override with your own hostname(s), or set * to disable Host checking entirely (e.g. behind ingress under other names). Loopback binds keep the SDK's localhost-only protection |
DIAL_CORE_URL |
for DIAL export | — | Base URL of DIAL Core, e.g. https://dial.example.com. Unset → DIAL upload/download tools return a clear error; local-path tools still work |
DIAL_AUTH_MODE |
no | auto |
auto: credentials from the incoming MCP request first (the end user's bearer, attached by Quick Apps for servers deployed under the DIAL host — exports land in that user's own bucket), falling back to DIAL_API_KEY. caller: incoming credentials only — fail loudly instead of falling back. server: always DIAL_API_KEY (single shared bucket) |
DIAL_API_KEY |
no | — | Server's own DIAL API key — the fallback identity in auto mode, the only identity in server mode |
DIAL_UPLOAD_FOLDER |
no | pptx-mcp |
Folder inside the bucket for exported decks |
PPT_MCP_STATE_TTL_SECONDS |
no | 3600 |
Idle time before an in-memory presentation expires |
PPT_MCP_STATE_MAX_PRESENTATIONS |
no | 50 |
Max concurrently held presentations (LRU eviction) |
PPT_TEMPLATE_PATH |
no | — | Extra local directories searched by the local-path template tools (:-separated) |
VISION_LLM_MODEL |
for visual QA | — | Vision model: the model name (direct endpoint) or the DIAL deployment name (DIAL provider); must accept image input |
VISION_LLM_ENDPOINT |
direct provider | — | OpenAI Responses-API endpoint, e.g. https://<resource>.openai.azure.com/openai/responses?api-version=2025-04-01-preview. When unset, the model is called through DIAL Core instead: {DIAL_CORE_URL}/openai/deployments/{model}/chat/completions with DIAL credentials (caller headers first, DIAL_API_KEY fallback) |
VISION_LLM_API_KEY |
direct provider | — | Key for the direct endpoint (sent as api-key and Authorization: Bearer) |
VISION_LLM_PROVIDER |
no | auto | Force the backend: direct or dial (default: direct when VISION_LLM_ENDPOINT is set, else dial) |
VISION_LLM_API_VERSION |
no | — | Optional ?api-version= for the DIAL-routed call |
VISION_LLM_MAX_SLIDES |
no | 15 |
Cap on slides sent per inspection |
SOFFICE_PATH |
no | soffice on PATH |
LibreOffice binary used to render slides (the Docker image includes LibreOffice) |
Copy .env.example to .env for local runs — the server loads it at startup, and .env is gitignored.
Running
Local (stdio, for MCP-client desktop use):
pip install -r requirements.txt
python ppt_mcp_server.py
Remote (streamable-http):
docker build -t dial-pptx-mcp .
docker run -p 8000:8000 -e DIAL_CORE_URL=https://dial.example.com -e DIAL_API_KEY=... dial-pptx-mcp
# MCP endpoint: http://<host>:8000/mcp
Kubernetes: see deploy/kubernetes.yaml (generic example — replace placeholders).
DIAL Quick Apps integration
Register the deployed server as an MCP tool set in your Quick App manifest:
{
"name": "powerpoint",
"description": "Generate PowerPoint presentations from corporate templates",
"type": "mcp",
"mcp_server_info": {
"url": "https://YOUR-DEPLOYED-HOST/mcp",
"protocol": "streamable_http",
"authorization": null
},
"attachment": {
"supported_types": ["*/*"],
"propagate_types_to_choice": [
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
]
}
}
propagate_types_to_choicemakes the exported.pptxattachment visible to the end user in DIAL Chat (tool-call results are hidden by default).- Deploy the server under the DIAL host (behind DIAL Core routing) for per-user storage. Quick Apps attaches the end user's
Authorization: Beareronly to MCP servers whose URL starts with the DIAL host — with it, the defaultautomode uploads every export to that user's own bucket. For a server at an external URL, Quick Apps sends no user credentials (it deliberately refuses to forwardapi-key/authorizationas custom headers), soautofalls back to the server'sDIAL_API_KEYand exports land in the server's single bucket; setDIAL_AUTH_MODE=callerif you'd rather exports fail loudly than fall back. - Template input: the orchestrating agent passes the template to
create_presentation_from_template_contentasfile:data::files/{bucket}/{path}— Quick Apps' file preprocessing resolves that reference to a data: URI before this server receives it (base64 viafile:base64::also accepted). Note Quick Apps' default 10 MiB file-loading limit (features.file_loading.size_limit) if your templates are large. - Deck output:
export_presentationuploads to DIAL file storage and returns thefiles/{bucket}/{path}URL; the tool description instructs the agent to include it in its final answer.
Automatic visual QA (internal inspect-and-repair loop)
When a vision LLM is configured (VISION_LLM_*), quality assurance is entirely internal to the server — the calling agent only ever receives a finished, verified presentation. On every export_presentation/save_presentation of a deck that was created or edited since it last passed:
- All slides are rendered (LibreOffice → PDF → PNG) and reviewed by the vision LLM for template/brand fidelity and visible errors (overflowing or clipped text, overlaps, unfilled placeholders, broken charts, illegibility).
- If issues are found, the server repairs the deck itself: the LLM is shown the issues, the affected slides' structure, and their images, and returns a plan of whitelisted operations (move/resize shape, set font size, set text, word wrap, delete shape) that are validated and applied with python-pptx.
- The deck is re-rendered and re-inspected; the loop repeats up to
VISUAL_QA_MAX_ITERATIONS(default 10) inspections, stopping early if no repair makes progress. - Only a deck that passes is exported.
VISUAL_QA_ON_UNRESOLVEDcontrols what happens if the loop cannot reach a pass:report(default) fails the export with the unresolved issue list — a genuine failure report, not a retry request — whileexport_as_isships the best-effort deck anyway.
Passed decks aren't re-inspected unless edited again, and exports skip QA entirely when the feature is unconfigured. VISUAL_QA_ENFORCE=false disables it; VISUAL_QA_ON_ERROR=allow lets exports through when inspection itself cannot run (renderer/LLM outage — default blocks); VISUAL_QA_EXPOSE_TOOL=true additionally exposes a standalone visual_inspect_presentation tool for debugging. The reviewer model can be reached two ways: a direct OpenAI Responses-API endpoint with image input (Azure OpenAI included), or as a DIAL Core deployment via {DIAL_CORE_URL}/openai/deployments/{model}/chat/completions — see the VISION_LLM_* variables. Cost note: each loop iteration is one render plus one or two LLM calls, so a worst-case export adds a few minutes and a handful of vision-model requests.
Multi-tenancy and scaling notes
- Presentation handles are server-generated UUIDs and act as unguessable capabilities; clients cannot enumerate or guess other conversations' decks.
- Presentation state lives in process memory (bounded by TTL + LRU). Run a single replica, or use session affinity if you scale out — a deck created on one replica is not visible on another.
- Calls targeting the same presentation are serialized (python-pptx is not thread-safe); different presentations are handled concurrently.
Development
uv venv --python 3.12 .venv && uv pip install -r requirements.txt
.venv/bin/python -m unittest discover -s tests # unit tests
.venv/bin/python spike/fidelity_spike.py <template> # template-fidelity check
.venv/bin/python spike/http_client_check.py # transport smoke test
spike/fidelity_spike.py creates a deck from a template through the same code paths the MCP tools use and byte-compares the theme, slide-master, layout, and media parts of template vs output. Local test templates belong in templates-local/ (gitignored).
Credits
This project is built on Office-PowerPoint-MCP-Server by GongRzhe, used under the MIT license, with full git history preserved. The core PowerPoint manipulation tools and utilities are upstream work; this fork adds the remote-transport, multi-tenant state, and DIAL integration layers. The original LICENSE and copyright notice are retained; the upstream README is preserved at docs/UPSTREAM_README.md.
License
MIT — see LICENSE.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.