From powerpoint-mcp
Manipulate live, open PowerPoint presentations on macOS via Office.js MCP server. Use when Claude needs to: (1) create, edit, or inspect slides in a running PowerPoint instance, (2) add shapes, text, tables, or formatting to live presentations, (3) capture visual slide screenshots, (4) enable/configure the PowerPoint MCP server in a project, (5) execute Office.js code against open presentations. Distinct from the pptx file-editing skill — this works on presentations currently open in PowerPoint.
How this skill is triggered — by the user, by Claude, or both
Slash command
/powerpoint-mcp:powerpoint-mcpThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Edit live, open PowerPoint presentations through an MCP bridge. Changes appear in real-time.
Edit live, open PowerPoint presentations through an MCP bridge. Changes appear in real-time.
Claude Code ──MCP HTTP (localhost:3001)──> Bridge Server ──WS──> PowerPoint Add-in ──> Live Presentation
When asked to enable or configure PowerPoint MCP in a project — follow the setup guide.
| Tool | Purpose | Key Parameters |
|---|---|---|
list_presentations | Discover connected presentations | — |
inspect_deck | Deck overview: slide list + theme (colors, fonts). Use as first call | presentationId? |
inspect_layouts | All slide layouts with names, indices (for slides.add), placeholders. usedOnly for fast mode | usedOnly?, presentationId? |
add_slide | Add a new slide with a specific layout at a given position. Returns placeholder shape IDs | layoutName, position?, presentationId? |
inspect_slide | Detailed shape inspector (~80 tok/shape): text, positions, fills | slideRange, presentationId? |
scan_slide | Lightweight shape scanner (~40 tok/shape): IDs, types, positions | slideRange, presentationId? |
screenshot_slide | Capture one slide as PNG (~1000 tok) | slideIndex, width? (default 720), presentationId? |
preview_deck | Batch overview of all/selected slides with thumbnails + text | slideRange?, imageWidth? (default 480), includeImages?, presentationId? |
copy_slides | Copy slides between two open presentations (data stays server-side) | sourceSlideIndex, sourcePresentationId, destinationPresentationId, formatting?, targetSlideId? |
insert_image | Insert image from file path, URL, or base64 data (data stays server-side for file/url) | source, sourceType (file/url/base64), slideIndex?, left?, top?, width?, height?, presentationId? |
get_local_copy | Get a local .pptx file path (passthrough for local files, exports cloud files to temp) | presentationId? |
read_deck_text | Lightweight text extractor: titles + body as plain strings (~20x smaller than inspect_slide) | slideRange?, includeNotes?, presentationId? |
read_shape_paragraphs | Read raw OOXML <a:p> paragraphs from a shape (preserves formatting) | slideIndex, shapeId, presentationId? |
edit_shape_paragraphs | Replace paragraph content with raw OOXML (preserves bodyPr/lstStyle) | slideIndex, shapeId, xml, presentationId? |
read_slide_xml | Read full slide OOXML or a specific shape's XML | slideIndex, shapeId?, presentationId? |
edit_slide_xml | Replace full slide XML or a specific shape's XML | slideIndex, xml, shapeId?, presentationId? |
read_slide_zip | Read multiple files from exported slide zip (slide XML, rels, charts) | slideIndex, paths?, presentationId? |
edit_slide_zip | Update multiple zip files and reimport (auto-registers Content_Types for charts) | slideIndex, files, presentationId? |
duplicate_slide | Clone a slide within the same presentation | slideIndex, insertAfter?, presentationId? |
verify_slides | Check for overlapping, out-of-bounds, empty-text, tiny shapes, or unused placeholders | slideIndex, checks?, presentationId? |
edit_slide_chart | Create chart from structured data (generates all OOXML automatically) | slideIndex, chartType, title, categories, series, position?, options?, presentationId? |
search_text | Grep for slides — search text across shapes, tables, and speaker notes with regex support | query, slideRange?, caseSensitive?, regex?, context? (shape/slide/none), includeNotes?, presentationId? |
format_shapes | Batch-apply fill and font formatting to shapes in one call | slideIndex, shapes: [{ id, fill?, font? }], presentationId? |
execute_officejs | Run arbitrary Office.js code in the live presentation | code, presentationId? |
presentationId is required only when multiple presentations are connected. Get it from list_presentations.
All positioning values are in points (1 pt = 1/72 inch). Always read slideWidth and slideHeight from inspect_deck or inspect_slide response — never assume 960 × 540. Common sizes: 960×540 (standard 16:9), 1440×810 (widescreen), 960×720 (4:3).
Slide numbering: Users refer to slides by 1-based number ("slide 3"), but all tools use 0-based indices. When a user says "slide 3", use slideIndex: 2. When they say "after slide 2", use insertAfter: 1.
| Tool | Scope | ~Cost | When to use | When NOT to use |
|---|---|---|---|---|
inspect_deck | All slides | ~5 tok/slide | First call — learn deck structure, slide count, dimensions | Need shape details |
scan_slide | 1+ slides | ~40 tok/shape | Need shape layout/positions for editing | Need text content or fills |
inspect_slide | 1+ slides | ~80 tok/shape | Need full details (text, positions, fills) | Just need positions — use scan_slide |
screenshot_slide | 1 slide | ~1000 tok | Visual verification after changes | Looping over all slides — use preview_deck |
preview_deck text-only | All slides | ~35 tok/slide | Content audit, find which slide has what | Need shape positions or fills |
preview_deck + images | All slides | ~900 tok/slide | Visual audit of entire deck | Just need one slide — use screenshot_slide |
Example: a 20-slide deck: inspect_deck ≈ 100 tokens, preview_deck text-only ≈ 700 tokens, preview_deck with images ≈ 18000 tokens. Always prefer the lightest option.
Key return formats to know:
scan_slide returns { slideWidth, slideHeight, slides: [{ slideIndex, slideId, shapes: [{ id, name, type, left, top, width, height }] }] } — id is a stable numeric string (use this for read/edit tools); name is locale-dependent (never use as selector); type is one of "GeometricShape", "TextBox", "Table", "Chart", "Picture", "Group"verify_slides returns { slideIndex, issues: [{ type, description, shapeIds }] } — type is "overlap", "bounds", "empty_text", "tiny_shapes", "unused_placeholder", or "background_cover"; shapeIds are stable IDssearch_fluent_icons returns [{ id, description, isMono, contentTier, searchScore, svgUrl }] — isMono: false = filled/colorful, isMono: true = outline/mono; pick highest searchScore matching intent; use svgUrl with insert_image (sourceType: "url") to insertread_shape_paragraphs returns raw OOXML <a:p> paragraph elements (does NOT include <a:bodyPr> or <a:lstStyle>)read_slide_zip returns { zipContents: { path: content }, allPaths: [...] }| Tool | Key non-obvious behavior |
|---|---|
edit_shape_paragraphs | The xml field takes raw OOXML paragraph XML, not executable code. Preserves <a:bodyPr> and <a:lstStyle> automatically. Must auto-size shapes after edit. |
edit_slide_xml | Two modes: xml (finished XML string) or code (JS that manipulates pre-parsed DOM server-side). Code mode preserves untouched attributes. Exported slide is ALWAYS ppt/slides/slide1.xml in the zip regardless of slideIndex. |
format_shapes | Uses getTextFrameOrNullObject() internally. Cannot set corner radius, borders, or gradients — use edit_slide_xml code mode for those. Color format: hex without #. |
verify_slides | Must auto-size shapes first or stale dimensions cause missed overlaps. Table overflow needs API fix, not OOXML. |
insert_image | color param recolors SVG images only (e.g. Fluent UI icons from search_fluent_icons). Errors on non-SVG. # prefix required: "#FF5733". |
execute_officejs | Loaded values are snapshots — don't branch on stale reads after writes without re-load + re-sync. |
| sz value | Point size | Use |
|---|---|---|
1400 | 14pt | Body minimum |
1600 | 16pt | Preferred body |
2000 | 20pt | Subheading |
2800 | 28pt | Section header |
3600 | 36pt | Slide title |
4400 | 44pt | Large title |
Before editing, determine the deck type. This determines the entire approach.
Detection: inspect_deck shows only default slides, inspect_slide shows no custom content or colors.
Use edit_slide_zip FIRST to set up a complete theme before adding any slides. Do ALL of the following in a single edit_slide_zip call (targeting the slide master PPTX structure):
a:clrScheme: dk1, dk2, lt1, lt2, and all six accents. Pick a cohesive palette suited to the topic and audience.a:majorFont) and body font (a:minorFont) that pair well. Avoid Calibri for both.p:bg on the slide master.p:txStyles (title and body default text) so text contrasts the background. NEVER override font colors on individual slides.Palette diversity rule: Do NOT default to dark backgrounds. Light, warm, pastel, earthy, vibrant, and muted palettes are all valid choices. Match the tone of the content.
Detection: inspect_deck shows default theme but inspect_slide reveals custom colors, fonts, and shapes on existing slides.
Do NOT create or modify the slide master. The existing slides ARE the design system.
Detection: inspect_deck shows a non-default theme.
Default to PRESERVING the existing theme. New slides and additions should blend with existing colors, fonts, and layouts.
If the user requests a restyle or redesign — STOP before making edits:
For complex tasks (multi-slide decks, redesigns, data-heavy presentations), ask for missing context BEFORE doing any work. Do NOT assume details the user has not provided.
Triggers:
Simple, unambiguous requests ("Add a title slide", "Change the font to Arial", "Move this chart to slide 3") -- just do it. Factual or how-to questions -- just answer.
For multi-slide decks, PROPOSE THE STORYLINE FIRST -- slide titles and key points -- and get approval BEFORE creating any slides. Do NOT build 10+ slides without the user confirming the narrative arc.
When creating multiple slides that share a layout (e.g., one slide per team member, one per product), build ONE example slide first. Show it, get feedback on the design, then replicate across the remaining slides.
For multi-step work, check in at key milestones. Show interim outputs and confirm before moving on. Do NOT build end-to-end without feedback.
list_presentations — find connected presentationspreview_deck for a visual overview, or inspect_deck then inspect_slide per slide. This is essential for resuming partial builds or modifying existing decks.search_text — grep for slides. Searches shapes, tables, and speaker notes. Use context: "none" for just slide indices, "shape" (default) for matching shapes, or "slide" for full slide context with all shapes. Supports regex.screenshot_slide — visually inspect specific slidesexecute_officejs — build entire slides in a single call (all shapes, text, connectors, accents at once) for efficiency and to avoid mid-build visual flashingverify_slides + /review-slide-visual (see below). Both steps are mandatory. Never skip /review-slide-visual.Always inspect before modifying. Always verify after modifying. Every modified slide must pass both structural AND visual review before you move on or declare done.
For 3+ slides: build one slide at a time. Announce each slide before creating it ("Creating the Market Analysis slide..."). Use separate execute_officejs calls per slide -- this allows user feedback between slides.
Recommended flow for a multi-slide deck:
edit_slide_zip -- define theme, colors, fonts, background, decorative shapes (via slide master PPTX structure)execute_officejs callverify_slides -- check all slides for overlaps and unused placeholdersDo NOT build an entire multi-slide deck in a single call.
MANDATORY — run this after EVERY slide edit, including fixes. No exceptions.
autoSizeSetting = "AutoSizeShapeToFitText" on edited text shapes via execute_officejs — otherwise verify_slides sees stale dimensionsverify_slides — overlap, bounds, empty text, tiny shapes, unused placeholdersp:txStyles) contrasts the slide background. Flag and fix any per-shape color override that reduces legibility./review-slide-visual N presentationId — this is NOT optional. The independent reviewer catches issues you cannot see from data alone (spacing, alignment, visual weight, contrast).Do NOT declare success until the verify → fix → re-verify loop converges. Skipping /review-slide-visual means you have NOT verified.
If overlaps/overflow: shorten text, reduce font, reposition body content (not title), or split across slides.
Layout background rule: NEVER create full-bleed rectangles to cover a layout-provided background. The layout background is part of a design system — it determines text colors, placeholder styling, and chrome visibility (logo, breadcrumb, dividers). Covering it destroys all of this and forces manual recreation. If a slide has the wrong background, switch to a layout that has the correct one.
Manual checklist (verify before declaring done):
left + top setverify_slides background_cover check — severity: error)Intentional overlaps: When using card patterns (TextBoxes + icons inside RoundedRectangles), verify_slides will report many overlaps — these are expected by design. Also, decorative HR lines spanning the full width will overlap with adjacent elements. Only act on overlaps between shapes that should NOT be layered, or on overflow (shapes going off-slide).
Efficient verification: For large decks, visually verify only the most complex slides (high shape count, dense content) rather than every slide. Run verify_slides on all slides structurally, but pick 4-5 key slides for the visual subagent check.
/review-slide-visualWhen: After every slide edit — step 4 of the verification loop. This is the final gate before declaring a slide done.
How: Invoke /review-slide-visual N presentationId (N = 0-based slide index). Always pass the full presentationId — skips lookup and avoids ambiguity. The skill runs in a forked context with no conversation knowledge — it evaluates purely what it sees, eliminating confirmation bias.
Why mandatory: verify_slides catches structural issues (overlaps, bounds) but cannot detect spacing problems, visual imbalance, contrast issues, misaligned elements, or text overflow that Office.js doesn't report. Only a visual screenshot review catches these.
Rules:
verify_slides structurally on all slides, but /review-slide-visual only on the 4-5 most complex slides.For execute_officejs code patterns, see code-patterns.md.
Choose the right tool for each edit. Prefer higher tools in this table — fall back only when the preferred tool cannot express the change.
| Change type | Tool | Why |
|---|---|---|
| Fill color, font bold/italic/size/color/name | format_shapes | Declarative, 1 call per slide, no XML risk |
| Mixed formatting within one shape (some words bold, some not) | edit_shape_paragraphs | Office.js has no paragraph-level font API |
| Geometry (corners, borders), gradients, attributes Office.js cannot set | edit_slide_xml with code | DOM manipulation preserves untouched attributes |
| Complex layouts, new shapes, diagrams | edit_slide_xml with code | Full OOXML control |
| Simple text writes, shape creation, positioning | execute_officejs | Direct Office.js API |
Key rule: If format_shapes can express it, use format_shapes. Fall back to edit_slide_xml code mode only for properties Office.js cannot set (corner radius, borders, gradients, precise paragraph formatting).
Never: Use raw XML string replacement (edit_slide_xml with xml param) for formatting-only changes. OOXML is fully explicit — every omitted attribute is lost. Prefer format_shapes or edit_slide_xml with code (DOM manipulation preserves untouched attributes).
When fixing style issues across multiple slides (e.g., after an audit finds inconsistent colors, fonts, or formatting):
inspect_slide when you already have the information.format_shapes (one call per slide, all shapes)edit_slide_xml with code (one call per slide, all shapes via DOM)format_shapes first, then edit_slide_xml codescreenshot_slide per slide to confirm all fixes applied correctly before moving on.Detect broad style preferences that apply across presentations and save them to memory:
Before saving, check existing memory for duplicates. If the preference already exists, do not re-save it.
Use /memory or the project CLAUDE.md to persist preferences across sessions. Keep entries minimal -- one line per preference, grouped under a ## Slide Preferences heading.
When the user provides data files (Excel, CSV, PDF) to populate slides:
python3 (pandas, openpyxl, pdfplumber are available) to extract structured dataexecute_officejs to populate slides with the extracted dataFor .pptx template files: use copy_slides to import slides from another open presentation.
insertSlidesFromBase64 rejects VBA macros, external references, OLE objects, and ActiveX controls. Only clean .pptx files pass through.
Prerequisite: Load the /pptx skill for OOXML structure knowledge (namespaces, element anatomy, formatting rules).
For fine-grained formatting control beyond what Office.js properties expose, use the OOXML tools. See ooxml-reference.md for unit conversion, batching, and pipeline gotchas.
edit_slide_xml with code)One call — the code reads and modifies the pre-parsed DOM server-side. Only touched attributes change; everything else is preserved.
inspect_slide(slideRange: "N") → find shape IDsedit_slide_xml with code — DOM manipulation in one callscreenshot_slide — confirm visual resultSandbox context (available in your code):
| Variable | Type | Purpose |
|---|---|---|
doc | Document | Pre-parsed slide XML DOM |
findShapeById(id) | (string) → Element | null | Find <p:sp> by <p:cNvPr id="..."> |
NS_P | string | PresentationML namespace |
NS_A | string | DrawingML namespace |
escapeXml(text) | (string) → string | Escape & < > " ' for XML |
serializeXml(node) | (Node) → string | Serialize DOM node to XML string |
DOMParser | constructor | Create new DOM documents for fragments |
Example — remove rounded corners from multiple shapes:
edit_slide_xml(slideIndex: 0, code: `
var ids = ["5", "7", "9"];
for (var i = 0; i < ids.length; i++) {
var shape = findShapeById(ids[i]);
if (!shape) continue;
var geom = shape.getElementsByTagNameNS(NS_A, "prstGeom")[0];
if (!geom) continue;
var avLst = geom.getElementsByTagNameNS(NS_A, "avLst")[0];
if (avLst) while (avLst.firstChild) avLst.removeChild(avLst.firstChild);
}
`, explanation: "Remove rounded corners")
Example — change text color on a shape without losing formatting:
edit_slide_xml(slideIndex: 2, code: `
var shape = findShapeById("3");
var runs = shape.getElementsByTagNameNS(NS_A, "rPr");
for (var i = 0; i < runs.length; i++) {
var rPr = runs[i];
var fills = rPr.getElementsByTagNameNS(NS_A, "solidFill");
if (fills.length > 0) rPr.removeChild(fills[0]);
var fill = doc.createElementNS(NS_A, "a:solidFill");
var clr = doc.createElementNS(NS_A, "a:srgbClr");
clr.setAttribute("val", "FFFFFF");
fill.appendChild(clr);
rPr.insertBefore(fill, rPr.firstChild);
}
`, explanation: "Set text to white")
For single-shape paragraph edits, read_shape_paragraphs / edit_shape_paragraphs preserves <a:bodyPr> and <a:lstStyle> automatically. For full slide XML replacement, use edit_slide_xml with xml — but prefer code mode to avoid accidentally dropping attributes.
Cannot do via Office.js — do not attempt:
insert_image tool — positions via Common API, not shape API)a:gradFill in edit_slide_zip)For charts, use edit_slide_chart (declarative) or edit_slide_zip (raw OOXML). Never approximate charts with geometric shapes. For slide masters/themes, use edit_slide_zip for full theme editing (colors, fonts, backgrounds, decorative shapes).
font.size — do not rely on defaultssearch_fluent_icons before falling back to geometric shapes.Common visual patterns for building slides. Adapt colors and content to the user's design system.
RoundedRectangle as background → TextBox for title (offset ~85pt from left edge for icon space) → TextBox for body below title → Icon (36-48pt) at top-left corner of card.
Calculate card width: (contentWidth - gaps) / numColumns. Common configurations: 2x2, 3-across, 4-across, 5-across.
Intentional overlaps: Card patterns always report overlaps in verify_slides because TextBoxes and icons sit inside the RoundedRectangle. These are expected — only worry about overflow (shapes going off-slide) or unintended sibling overlaps.
Icon (36-48pt) left-aligned → Title TextBox at icon's right → Description TextBox below title, all inside a large RoundedRectangle container. Good for feature lists, "about us" sections, service descriptions.
Large font number (accent color, 28-36pt) + small label below (14-16pt), stacked vertically with separator lines between entries. Good for KPIs, proof points, metrics panels.
Vertical tall cards (equal width, evenly spaced) + horizontal bar spanning all pillars at bottom + dashed arrow connectors from each pillar down to the bar. Shows hierarchy: categories above → shared foundation below.
Content panel (left, ~45% width) + stats/data panel (right, ~45% width) with a gap between. Good for combining narrative text with data points or proof points.
Horizontal rectangles stacked vertically with graduated fill color (darkest at top or bottom). Each layer has a title and description. Shows hierarchy, maturity levels, or technology stacks.
Two contrasting colored panels side by side (e.g., muted red for "without" vs green for "with"). Each panel lists bullet points. Optional full-width CTA bar below.
3 equal-width tall cards, each with: header area (company/project name), description body, and metrics/outcomes section at the bottom.
Standard cards with a small colored RoundedRectangle "badge" overlaid (e.g., showing a tier level, category label, or status tag). Badge is typically 80-120pt wide, 20-28pt tall, positioned at top-right of the card.
XML:
& as & in <a:t> — #1 cause of missing textformat_shapes for Office.js properties or edit_slide_xml with code for DOM manipulation. Both preserve untouched attributes. Avoid raw XML string replacement for formatting changes.<!-- --> comments in code strings — sandbox rejects with SES_HTML_COMMENT_REJECTEDOffice.js:
textRange.text returns plain text with all formatting stripped. Use read_shape_paragraphs for formatted content. Office.js is for shape metadata (IDs, positions, dimensions) and simple writes.getTextFrameOrNullObject() — never .textFrame directly (tables/images/charts throw)hasText stays stale after setting textRange.text)paragraphs collection in PowerPoint Office.jsadd_slide tool to add slides with a layout at a given position (handles slides.add() + moveTo internally)masters.items[masters.items.length - 1] — earlier may be stale# prefix for background colors: { color: "1A1A1E" } not "#1A1A1E"hasText is stale, you'll delete what you just wrote. But DO delete genuinely unused placeholders (ones you never wrote to) — never leave empty placeholders on a finished slide.Charts:
[Content_Types].xml, include <c:style val="2"/>, don't hardcode series colors<c:overlap val="100"/>, category axis majorTickMark val="none"Tables:
shape.height and OOXML <a:ext cy> are overriddencell.font.size + row.heightFor features Office.js cannot access (comments, chart data, embedded objects, master slides, custom XML parts), use get_local_copy to get a .pptx file path, then use python-pptx to process it.
get_local_copy returns the existing file path for local files, or exports cloud files to a temp .pptxpresentationIdscreenshot_slide instead (returns MCP image block, not text)Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub kzarzycki/powerpoint-mcp --plugin powerpoint-mcp