# Slides and Shapes

Reference for the `slide` tool (`add-blank`, `get-count`, `delete`, `duplicate`, `move-to`,
`set-background-color`, `get-background-color`, visibility, sections, comments, import) and the `shape` tool
(`add-rectangle`, `add-text-box`, `add-text-effect`, `add-auto-shape`, `add-line`, `add-connector`,
`add-attached-connector`, `get-count`,
`delete`, `set-position`, `set-size`, `align`, `distribute`, `merge`, plus the fill/line/rotation/flip/z-order/shadow/glow/
reflection/soft-edge/bevel/group/name/alt-text/hyperlink formatting actions below).

## Slide Actions

| Tool | Action | Parameters | Notes |
|------|--------|------------|-------|
| `slide` | `add-blank` | `presentation_session_id` | Adds a **blank** slide at the end. No insert-at-index. |
| `slide` | `get-count` | `presentation_session_id` | Returns current slide count (`slideCount`). Call before/after mutations to confirm state. |
| `slide` | `inspect` | `presentation_session_id`, optional `max_slides` (default `20`, max `100`), optional `max_text_chars_per_slide` (default `500`, max `2000`) | Returns a bounded overview of slide names, layouts, shape counts, and text previews. It scans at most 200 shapes per slide; each preview reports whether text was truncated by its character or scan limit. `omittedSlideCount` reports slides beyond the limit. Set the text limit to `0` for metadata only. |
| `slide` | `delete` | `presentation_session_id`, `slide_index` (1-based) | Removes the slide; later slides shift down by one index. |
| `slide` | `duplicate` | `presentation_session_id`, `slide_index` | Inserts a copy of the slide immediately after the source. Returns the duplicate's new `slideIndex` and total `slideCount`. |
| `slide` | `move-to` | `presentation_session_id`, `slide_index`, `to_position` | Moves a slide to a new 1-based position, renumbering the rest. Returns the slide's new `slideIndex`. |
| `slide` | `set-background-color` | `presentation_session_id`, `slide_index`, `red`, `green`, `blue` | Sets a solid per-slide background color, overriding the slide master for that slide only. Returns `colorRgb` and `followsMasterBackground: false`. |
| `slide` | `get-background-color` | `presentation_session_id`, `slide_index` | Returns the slide's background `colorRgb` and `followsMasterBackground`. |
| `slide` | `set-gradient-background` | `presentation_session_id`, `slide_index`, `red1`, `green1`, `blue1`, `red2`, `green2`, `blue2`, `gradient_style?`, `gradient_variant?` | Sets a two-color gradient per-slide background, overriding the slide master for that slide only. `gradient_style` is one of `msoGradientHorizontal` (default), `msoGradientVertical`, `msoGradientDiagonalUp`, `msoGradientDiagonalDown`, `msoGradientFromCorner`, `msoGradientFromTitle`, `msoGradientFromCenter`. `gradient_variant` is `1`-`4` (default `1`). |
| `slide` | `get-gradient-background` | `presentation_session_id`, `slide_index` | Returns `colorRgb`, `colorRgb2`, `gradientStyleName`, `gradientVariant`. Fails if the slide's current background fill is solid (not a gradient). |
| `slide` | `add-section` | `presentation_session_id`, `section_index`, `section_name` (optional) | Adds a new section before `section_index` (pass `sectionCount + 1` to append). Returns the new `sectionIndex` and total `sectionCount`. |
| `slide` | `rename-section` | `presentation_session_id`, `section_index`, `section_name` | Renames an existing section. |
| `slide` | `delete-section` | `presentation_session_id`, `section_index`, `delete_slides` (optional, default `false`) | Deletes a section. If `delete_slides` is true, its slides are deleted too; otherwise they're kept and merged into a neighboring section. **PowerPoint disallows deleting section 1 unless `delete_slides` is true** — delete/reorder other sections first if you need to remove the first one's boundary without losing its slides. |
| `slide` | `get-section-count` | `presentation_session_id` | Returns the current number of sections (`sectionCount`, `0` if none exist). |
| `slide` | `get-section-name` | `presentation_session_id`, `section_index` | Returns a section's name (`sectionName`). |
| `slide` | `list-comments` | `presentation_session_id`, `slide_index` | Lists legacy comments exposed by native PowerPoint COM. Modern threaded comments are not available through this API. |
| `slide` | `add-comment` | `presentation_session_id`, `slide_index`, `author`, `initials`, `text`, optional `left`/`top` | Adds a legacy comment. PowerPoint may replace author details with the signed-in Office identity. |
| `slide` | `delete-comment` | `presentation_session_id`, `slide_index`, `comment_index` | Deletes one legacy comment by 1-based index. |
| `slide` | `clear-comments` | `presentation_session_id`, `slide_index` | Deletes all legacy comments on the slide. |
| `slide` | `set-hidden` | `presentation_session_id`, `slide_index`, `hidden` | Excludes the slide from slide-show playback when `hidden` is true; the slide remains in the deck and editable. Returns `hidden`. |
| `slide` | `set-display-master-shapes` | `presentation_session_id`, `slide_index`, `display` | Shows or hides shapes inherited from the slide master. This does not control headers, footers, or the slide background. Returns `displaysMasterShapes`. |
| `slide` | `import-from-file` | `presentation_session_id`, `source_file_path`, `destination_slide_index`, optional source range | Inserts an inclusive 1-based source range after the destination slide; it never replaces destination slides. |

Slides always append at the end via `add-blank` — there is no "insert blank at position N" action;
use `add-blank` then `move-to` if you need a blank slide inserted mid-deck. See `deck-builder.md`
for planning multi-slide order.

MCP clients can use `slide_read` for the explicitly read-only slide actions, including `inspect`,
`get-count`, and background, section, comment, and tag queries. The original `slide` tool remains
available unchanged. `pptcli` continues to use `pptcli slide <action>`.

## Sections

Sections group contiguous ranges of slides for organizational purposes (visible in PowerPoint's
slide thumbnail panel) — they do not affect slide content or rendering. A slide's section
membership is purely positional (determined by where the slide sits between section boundaries),
so reordering slides with `move-to` can move them into a different section.

```
slide(action: "add-section", presentation_session_id: ..., section_index: 1, section_name: "Introduction")
slide(action: "add-section", presentation_session_id: ..., section_index: 2, section_name: "Deep Dive")
  → splits the deck into two sections at the current slide count boundary

slide(action: "get-section-count", presentation_session_id: ...) → sectionCount: 2
slide(action: "rename-section", presentation_session_id: ..., section_index: 2, section_name: "Details")
```

## Shape Actions

| Tool | Action | Parameters | Notes |
|------|--------|------------|-------|
| `shape` | `add-rectangle` | `presentation_session_id`, `slide_index`, `left`, `top`, `width`, `height` | Plain rectangle, no fill/line color parameters — style comes from PowerPoint's theme default. Returns `shapeIndex`. |
| `shape` | `add-text-box` | `presentation_session_id`, `slide_index`, `left`, `top`, `width`, `height`, `text` | Creates the text box AND sets its initial text in one call. Returns `shapeIndex`. |
| `shape` | `add-text-effect` | `presentation_session_id`, `slide_index`, `preset_effect`, `text`, `font_name`, `font_size`, `left`, `top`, optional `bold`/`italic` | Adds editable WordArt using `msoTextEffect1` through `msoTextEffect50`. PowerPoint determines the shape's width and height. Returns `shapeIndex` and `shapeCount`. |
| `shape` | `add-auto-shape` | `presentation_session_id`, `slide_index`, `shape_type`, `left`, `top`, `width`, `height` | Adds any non-rectangle built-in shape (oval, diamond, arrow, star bracket, etc.) by its `MsoAutoShapeType` name. Returns `shapeIndex` and echoes `shapeTypeName`. See "Auto Shape Types" below for the supported name list. |
| `shape` | `add-line` | `presentation_session_id`, `slide_index`, `begin_x`, `begin_y`, `end_x`, `end_y` | Straight line between two points. Returns `shapeIndex` and echoes `beginX`/`beginY`/`endX`/`endY`. |
| `shape` | `add-connector` | `presentation_session_id`, `slide_index`, `connector_type`, `begin_x`, `begin_y`, `end_x`, `end_y` | Adds a connector shape (`msoConnectorStraight`, `msoConnectorElbow`, or `msoConnectorCurve`) between two points. Free-floating — not glued to other shapes. Returns `shapeIndex` and echoes `connectorTypeName`. |
| `shape` | `add-attached-connector` | `presentation_session_id`, `slide_index`, `connector_type`, `begin_shape_index`, `begin_connection_site`, `end_shape_index`, `end_connection_site` | Adds a connector whose endpoints stay attached to connection sites on two existing shapes, so it follows them when they move. Shape indexes and connection sites are 1-based; each site must be within the selected shape's `ConnectionSiteCount`. Returns `shapeIndex` and echoes `connectorTypeName`. |
| `shape` | `get-count` | `presentation_session_id`, `slide_index` | Number of shapes currently on the slide (`shapeCount`). |
| `shape` | `delete` | `presentation_session_id`, `slide_index`, `shape_index` (1-based) | Removes one shape; later shapes on that slide shift down by one index. |
| `shape` | `set-position` | `presentation_session_id`, `slide_index`, `shape_index`, `left`, `top` | Moves an existing shape. |
| `shape` | `set-size` | `presentation_session_id`, `slide_index`, `shape_index`, `width`, `height` | Resizes an existing shape. |
| `shape` | `list-placeholders` | `presentation_session_id`, `slide_index` | Returns each native placeholder's shape index, type, name, bounds, alt text, and content state. |
| `shape` | `set-placeholder-text` | `presentation_session_id`, `slide_index`, `shape_index`, `text` | Replaces text in a native text placeholder; rejects ordinary shapes. |
| `shape` | `set-placeholder-image` | `presentation_session_id`, `slide_index`, `shape_index`, `image_path` | Places an image in a compatible picture/content placeholder while preserving its native geometry and metadata. |
| `shape` | `get-link-info` | `presentation_session_id`, `slide_index`, `shape_index` | Returns a linked picture's full source path; `linkAutoUpdate` is null because some PowerPoint builds reject reads of that typed property. |
| `shape` | `update-link` | `presentation_session_id`, `slide_index`, `shape_index` | Refreshes a linked picture immediately from its current source file. |
| `shape` | `break-link` | `presentation_session_id`, `slide_index`, `shape_index` | Permanently removes the file link while retaining the current picture. |
| `shape` | `set-link-auto-update` | `presentation_session_id`, `slide_index`, `shape_index`, `auto_update` | Enables or disables automatic refresh; PowerPoint errors propagate to the MCP or CLI boundary. |

All position/size values are **points** (see `deck-builder.md` for the 960×540pt 16:9 reference).

## Align and Distribute

Use `shape(action: "align", ..., shape_indexes: [3, 1, 2], align_cmd: "msoAlignTops")`
to align a selection. `align_cmd` accepts `msoAlignLefts`, `msoAlignCenters`,
`msoAlignRights`, `msoAlignTops`, `msoAlignMiddles`, or `msoAlignBottoms`.
Names are case-insensitive; numeric enum values are rejected.

By default, alignment uses the selection's original bounding rectangle and needs two
shapes. Set `relative_to_slide: true` to use slide bounds; this also allows one shape.
Array order does not choose an anchor shape.

Use `shape(action: "distribute", ..., shape_indexes: [3, 1, 2],
distribute_cmd: "msoDistributeHorizontally")` for equal horizontal edge gaps, or
`msoDistributeVertically` for vertical gaps. At least three shapes are required.
By default the outer shapes retain the selection's original span.
With `relative_to_slide: true`, equal gaps include the margins at both slide edges.
This spaces edges, not centers: unequal-sized shapes have unequal center distances.

Both actions require `presentation_session_id`, `slide_index`, and distinct 1-based top-level
`shape_indexes`. Invalid indexes, duplicates, or commands fail before any movement.
The result returns the total slide `shapeCount`. No shapes are resized or regrouped.
Groups are treated as whole top-level shapes; rotated shapes use native PowerPoint
geometry. This is not collision avoidance: insufficient space can produce overlap.
Export the slide to verify the result after arranging it.

## Shape Formatting Actions

| Tool | Action | Parameters | Notes |
|------|--------|------------|-------|
| `shape` | `set-fill` | `presentation_session_id`, `slide_index`, `shape_index`, `red`, `green`, `blue` (each 0-255) | Sets a solid fill color. Returns `colorRgb`. |
| `shape` | `get-fill` | `presentation_session_id`, `slide_index`, `shape_index` | Returns the current fill color as `colorRgb`. |
| `shape` | `set-line` | `presentation_session_id`, `slide_index`, `shape_index`, plus optional `red`/`green`/`blue`, `weight`, `dash_style`, `visible` | All formatting params are optional and independently applied — pass only what you want to change. `red`/`green`/`blue` must be passed together to set the line color. `dash_style` is an `MsoLineDashStyle` name (see below). Returns the shape's full line state (`colorRgb`, `lineWeight`, `dashStyleName`, `visible`). |
| `shape` | `get-line` | `presentation_session_id`, `slide_index`, `shape_index` | Returns the current line color, weight, dash style, and visibility. |
| `shape` | `copy-formatting` | `presentation_session_id`, `slide_index`, `source_shape_index`, `target_shape_index` | Applies PowerPoint's native Format Painter from the source shape to the target shape. Copies appearance without replacing the target's content, position, or size. Both shapes must be on the same slide. Returns the target `shapeIndex`. |
| `shape` | `duplicate` | `presentation_session_id`, `slide_index`, `shape_index` | Creates an identical, independently editable copy of a shape on the same slide, immediately in front of the original in z-order. Returns the new shape's `shapeIndex`. |
| `shape` | `copy-to-slide` | `presentation_session_id`, `slide_index`, `shape_index`, `target_slide_index` | Copies a shape to another slide in the same presentation via PowerPoint's native copy/paste, producing an independently editable copy. Both slides must be in the same open presentation and `target_slide_index` must differ from `slide_index` (use `duplicate` for a same-slide copy). Uses the shared Windows clipboard: concurrent calls made through this server are serialized automatically, but this cannot prevent an unrelated application or a manual copy/paste on the same desktop session from replacing the clipboard contents in between — a best-effort check fails the operation if paste did not produce exactly one shape. Returns the new shape's `shapeIndex` on `target_slide_index`. |
| `shape` | `set-rotation` | `presentation_session_id`, `slide_index`, `shape_index`, `degrees` | Sets rotation in degrees clockwise from upright. Returns `rotation`. |
| `shape` | `get-rotation` | `presentation_session_id`, `slide_index`, `shape_index` | Returns the current rotation in degrees. |
| `shape` | `set-3d-rotation` | `presentation_session_id`, `slide_index`, `shape_index`, optional `rotation_x`/`rotation_y`/`rotation_z` | Sets one or more axes of 3D rotation. At least one axis is required; omitted axes remain unchanged. X and Y must be between -90 and 90 degrees. Z rotates the shape around its Z axis and is tracked separately from the shape's 2D `rotation`. Returns all three axes. |
| `shape` | `get-3d-rotation` | `presentation_session_id`, `slide_index`, `shape_index` | Returns `rotationX`, `rotationY` and `rotationZ` in degrees. |
| `shape` | `flip` | `presentation_session_id`, `slide_index`, `shape_index`, `direction` (`horizontal` or `vertical`) | Flips the shape in place. Returns `flipDirection`. |
| `shape` | `set-z-order` | `presentation_session_id`, `slide_index`, `shape_index`, `z_order_command` | Moves the shape's stacking position. `z_order_command` is one of `bring-to-front`, `send-to-back`, `bring-forward`, `send-backward`. Returns `zOrderCommand`. |
| `shape` | `set-shadow` | `presentation_session_id`, `slide_index`, `shape_index`, `visible`, plus optional `red`/`green`/`blue`, `transparency` (0-1), `blur`, `offset_x`, `offset_y` (points) | Turns the shape's drop shadow on/off. When `visible` is true, the optional color/formatting parameters set an "offset" style shadow — any omitted parameter uses PowerPoint's default. Returns `visible` and, when visible, `colorRgb`, `transparency`, `blur`, `offsetX`, `offsetY`. |
| `shape` | `get-shadow` | `presentation_session_id`, `slide_index`, `shape_index` | Returns `visible` and, if visible, `colorRgb`/`transparency`/`blur`/`offsetX`/`offsetY`. |
| `shape` | `set-glow` | `presentation_session_id`, `slide_index`, `shape_index`, `red`, `green`, `blue`, `radius`, `transparency` (optional, 0-1) | Applies a glow effect. A `radius` of 0 removes the glow. Returns `colorRgb`, `glowRadius`, `transparency`. |
| `shape` | `get-glow` | `presentation_session_id`, `slide_index`, `shape_index` | Returns the shape's current glow `colorRgb`, `glowRadius`, `transparency`. |
| `shape` | `set-reflection` | `presentation_session_id`, `slide_index`, `shape_index`, `visible`, plus optional `transparency` (0-1), `size` (% of shape height), `blur` | Turns a reflection effect on/off. Returns `visible` and, when visible, `transparency`, `reflectionSize`, `blur`. |
| `shape` | `get-reflection` | `presentation_session_id`, `slide_index`, `shape_index` | Returns `visible` and, if visible, `transparency`/`reflectionSize`/`blur`. |
| `shape` | `set-soft-edge` | `presentation_session_id`, `slide_index`, `shape_index`, `radius` | Sets the soft edge (feathered edge) radius in points. A `radius` of 0 removes it. Returns `softEdgeRadius`. |
| `shape` | `get-soft-edge` | `presentation_session_id`, `slide_index`, `shape_index` | Returns the shape's current soft edge `softEdgeRadius`. |
| `shape` | `set-bevel` | `presentation_session_id`, `slide_index`, `shape_index`, `bevel_type`, plus optional `depth`, `inset` (points) | Applies a 3D bevel to the shape's top edge. `bevel_type` is an `MsoBevelType` name (e.g. `msoBevelCircle`, `msoBevelSoftRound`, `msoBevelRelaxedInset`, or `msoBevelNone` to remove). Returns `bevelTypeName`, `bevelDepth`, `bevelInset`. |
| `shape` | `get-bevel` | `presentation_session_id`, `slide_index`, `shape_index` | Returns the shape's current `bevelTypeName`, `bevelDepth`, `bevelInset`. |
| `shape` | `group` | `presentation_session_id`, `slide_index`, `shape_indexes` (JSON array of 1-based indices, at least 2) | Groups multiple shapes into one. Returns the new total `shapeCount` on the slide — **not** the grouped shape's index (see NoPIA note below). |
| `shape` | `ungroup` | `presentation_session_id`, `slide_index`, `shape_index` | Splits a group back into its member shapes. Returns `ungroupedShapeCount` (members produced) and the new total `shapeCount`. |
| `shape` | `merge` | `presentation_session_id`, `slide_index`, `shape_indexes` (JSON array of unique 1-based indices, at least 2), `merge_type` | Merges shapes with PowerPoint's `MsoMergeCmd`: `msoMergeUnion`, `msoMergeCombine`, `msoMergeIntersect`, `msoMergeSubtract`, or `msoMergeFragment`. Returns `mergeTypeName`, `mergedShapeCount`, and the new total `shapeCount`. Inputs are consumed and replaced by the result; `msoMergeFragment` can produce multiple shapes. |
| `shape` | `set-name` | `presentation_session_id`, `slide_index`, `shape_index`, `name` | Sets the shape's name (as shown in PowerPoint's Selection Pane). Returns `name`. |
| `shape` | `get-name` | `presentation_session_id`, `slide_index`, `shape_index` | Returns the shape's current name. |
| `shape` | `set-alt-text` | `presentation_session_id`, `slide_index`, `shape_index`, `alt_text` | Sets the shape's alternative text (accessibility description). Returns `altText`. |
| `shape` | `get-alt-text` | `presentation_session_id`, `slide_index`, `shape_index` | Returns the shape's current alternative text. |
| `shape` | `set-hyperlink` | `presentation_session_id`, `slide_index`, `shape_index`, `address`, `screen_tip` (optional) | Sets the shape's mouse-click hyperlink to `address` (URL or file path). `screen_tip` sets hover tooltip text. Returns `hasHyperlink`, `hyperlinkAddress`, `hyperlinkScreenTip`. |
| `shape` | `get-hyperlink` | `presentation_session_id`, `slide_index`, `shape_index` | Returns `hasHyperlink` and, if present, `hyperlinkAddress`/`hyperlinkScreenTip`. |
| `shape` | `remove-hyperlink` | `presentation_session_id`, `slide_index`, `shape_index` | Removes the shape's mouse-click hyperlink, if any (no-op if none is set). Returns `hasHyperlink: false`. |

**Finding a just-grouped shape's index**: `group` does not return the new group shape's own
`shapeIndex` — reading `.Index` off a freshly-created COM group object is unreliable in this
codebase's NoPIA late-binding setup. If the shapes you grouped were the **last** shapes added to
the slide (highest indices, nothing added after them), the resulting group occupies the new,
smaller `shapeCount` as its index (since grouping N shapes always removes N-1 from the slide's
shape list). Otherwise, call `shape(action: "get-count", ...)` before and after grouping and
inspect via a follow-up read if you need to confirm which index now holds the group.

### Hyperlinks

`set-hyperlink`/`get-hyperlink`/`remove-hyperlink` manage a shape's **mouse-click** action —
clicking the shape at presentation time navigates to `address` (an absolute URL like
`"https://example.com"`, or a local file path). There is no separate mouse-hover hyperlink action,
and no text-run-level hyperlink (a whole-shape hyperlink is the only granularity this tool
surface exposes) — to make specific words within a text box clickable, put that text in its own
shape.

`address` is normalized by PowerPoint itself (e.g. `"https://example.com"` round-trips as
`"https://example.com/"` with a trailing slash) — compare against the returned `hyperlinkAddress`
rather than assuming an exact byte-for-byte match of what you passed in.

```
shape(action: "set-hyperlink", presentation_session_id: ..., slide_index: ..., shape_index: ...,
  address: "https://example.com", screen_tip: "Visit our site")
shape(action: "get-hyperlink", presentation_session_id: ..., slide_index: ..., shape_index: ...)
  → hasHyperlink: true, hyperlinkAddress: "https://example.com/", hyperlinkScreenTip: "Visit our site"
shape(action: "remove-hyperlink", presentation_session_id: ..., slide_index: ..., shape_index: ...)
  → hasHyperlink: false
```

### Linked Pictures

File links are separate from click hyperlinks. Add a linked picture with `image(action:
"add-picture", ..., link_to_file: true)`, then use `get-link-info`, `update-link`,
`set-link-auto-update`, or `break-link` on that picture's 1-based shape index. `break-link`
retains the picture as an embedded shape. Link operations on rectangles, text boxes, embedded
pictures, or other non-linked shapes return a validation error. Unexpected PowerPoint errors from
typed link members propagate to the MCP or CLI boundary.

### Dash Styles (`dash_style` for `set-line`)

Must match a real `MsoLineDashStyle` enum member name exactly: `msoLineSolid`,
`msoLineSquareDot`, `msoLineRoundDot`, `msoLineDash`, `msoLineDashDot`, `msoLineDashDotDot`,
`msoLineLongDash`, `msoLineLongDashDot`.

### Z-Order Commands (`z_order_command` for `set-z-order`)

`bring-to-front`, `send-to-back`, `bring-forward`, `send-backward`. (PowerPoint's Word-only
z-order members — bring/send relative to text — are intentionally not exposed here.)


## Auto Shape Types

`shape_type` for `add-auto-shape` must match a real `MsoAutoShapeType` enum member name exactly
(case-sensitive, `mso`-prefixed PascalCase) — this is a curated subset (not the full Office enum):

| Category | `shape_type` values |
|----------|--------------------|
| Basic | `msoShapeRectangle`, `msoShapeRoundedRectangle`, `msoShapeOval`, `msoShapeDiamond`, `msoShapeParallelogram`, `msoShapeTrapezoid`, `msoShapeIsoscelesTriangle`, `msoShapeRightTriangle`, `msoShapeHexagon`, `msoShapeOctagon`, `msoShapeRegularPentagon`, `msoShapeCross` |
| Arrows | `msoShapeRightArrow`, `msoShapeLeftArrow`, `msoShapeUpArrow`, `msoShapeDownArrow`, `msoShapeLeftRightArrow`, `msoShapeUpDownArrow` |
| Brackets/braces | `msoShapeLeftBracket`, `msoShapeRightBracket`, `msoShapeLeftBrace`, `msoShapeRightBrace` |
| Decorative/misc | `msoShapeCan`, `msoShapeCube`, `msoShapeBevel`, `msoShapeFoldedCorner`, `msoShapeSmileyFace`, `msoShapeDonut`, `msoShapeNoSymbol`, `msoShapeBlockArc`, `msoShapeHeart`, `msoShapeLightningBolt`, `msoShapeSun`, `msoShapeMoon`, `msoShapeArc`, `msoShapePlaque` |

Passing an unrecognized string returns `success: false` — double-check spelling rather than
guessing variants (e.g. star/callout shapes are not in this curated set).

For lines and connectors, `connector_type` must be one of
`msoConnectorStraight`, `msoConnectorElbow`, or `msoConnectorCurve`.

## Shape Indexing Within a Slide

`shape_index` is 1-based and reflects the **order shapes were added to that slide** (and any
built-in placeholders from the applied layout, if present). After adding several shapes, use
`shape(action: "get-count", ...)` to confirm the current total before referencing an index you
didn't just create yourself — don't assume index 1 is always the title.

```
shape(action: "add-text-box", presentation_session_id: ..., slide_index: 1, ..., text: "Title")   → shapeIndex 1 (assuming a blank slide)
shape(action: "add-rectangle", presentation_session_id: ..., slide_index: 1, ...)                  → shapeIndex 2
shape(action: "add-text-box", presentation_session_id: ..., slide_index: 1, ..., text: "Body")     → shapeIndex 3
shape(action: "get-count", presentation_session_id: ..., slide_index: 1) → 3                        → confirms the count before further edits
```

## Building a Text Box + Table/Chart Combo Slide

Tables and charts are added by their own domain tools (`table(action: "add-table", ...)`,
`chart(action: "add-chart", ...)` — see `tables.md` and `charts.md`) but they are shapes on the
slide like any other, and share the same `shape_index` numbering with rectangles and text boxes
added on that slide. Track the returned `shapeIndex` from each add call (or re-check with
`shape(action: "get-count", ...)`) so subsequent position/size/format calls target the right
shape.

## Repositioning and Resizing

Use `shape(action: "set-position", ...)` / `shape(action: "set-size", ...)` for targeted layout
fixes instead of deleting and re-adding a shape:

```
CORRECT — nudge a shape that overlaps another after visual verification
shape(action: "set-position", presentation_session_id: ..., slide_index: ..., shape_index: ..., left: 500, top: 120)

AVOID — delete and recreate to move a shape
shape(action: "delete", presentation_session_id: ..., slide_index: ..., shape_index: ...)
shape(action: "add-rectangle", presentation_session_id: ..., slide_index: ..., left: 500, top: 120, width: ..., height: ...)
```

Deleting and recreating loses the shape's text content and any formatting already applied to it —
prefer targeted `set-position`/`set-size` (see `anti-patterns.md`).
