# MCP server
`deckcraft-cli mcp` runs a Model Context Protocol server on stdio. By default it drives its own
headless engine (no window); with `--connect PORT` it drives a running DeckCraft started with
`--control PORT`, so an agent and a person can work on the same deck. `--sample` opens the sample
deck.
```json
{
"mcpServers": {
"deckcraft": { "command": "deckcraft-cli", "args": ["mcp", "--connect", "7990"] }
}
}
```
## Confining file access
```sh
deckcraft-cli mcp --automation-read-root /work/project --automation-write-root /work/project/out
```
```json
{"mcpServers": {"deckcraft": {"command": "deckcraft-cli",
"args": ["mcp", "--automation-read-root", "/work/project", "--automation-write-root", "/work/project/out"]}}}
```
The two flags (VectorCraft, PhotoCraft and the other Craft apps take the same ones, so one client
config works across the suite) keep the agent to the folders you give it:
- Every file a command reads must lie inside the **read root**: `open_presentation` and
`file.open`, and `insert_picture`, `insert.picture`, `picture.change`, `insert.audio` and
`insert.video` with a `path`. Pictures, audio and video are copied into the deck when they are
inserted, so playing them later reads nothing from disk.
- Every file a command writes must lie inside the **write root**: `save_presentation`, `export`
(every format, and every slide of an image export), `file.save`, `file.saveAs`, `file.export`,
and `render_slide` with a `path`, plus the `.saving` temporary file a save goes through.
- Read and write access are separate. Give the same folder twice for both; a root left out grants
none of its access (with only `--automation-read-root`, nothing can be written to disk). Without
either flag nothing changes.
- A path is made absolute against the server's working directory, then its links are followed: a
file (or folder) link that leads out of a root is outside it. For a file that doesn't exist yet,
its nearest existing folder's links are followed and the rest must be plain names, so `..` below a
folder that doesn't exist is refused, as is a link that leads nowhere (writing through it would
create its target). The result is compared with the root by whole folder names: `/work2/x` is not
inside `/work`. On Windows case is ignored, UNC paths (`\\server\share\...`) are resolved like
drive-letter ones, and device names (`CON`, `NUL`, `COM1`...) are refused.
- A refused path is an ordinary tool error the agent can read and correct, for example
`automation path rejected: outside the write root /work/project/out: /etc/x.pptx` or
`automation filesystem access is not granted: write authority is absent: /work/project/out/x.pptx`.
- The roots must be existing folders; a missing one stops the server with an error. Each flag is
given once (`--flag
` or `--flag=`).
The flags confine the in-process server, so `--connect` together with them is an error: the server
would otherwise talk to an app that isn't confined. DeckCraft's desktop app has no equivalent flags
yet, so a running app (`deckcraft --control`) is not confined. `--sample` is the operator's own
choice and is opened before the roots apply.
What the roots do not cover: commands that take no path still read and change the open deck, and
`inspect_document` and `inspect_slide` return it. The system font scan and the app's own recovery
folder are fixed paths, not agent input, and are not checked. Anything that reads or writes a path
goes through one place in the engine (`io::read_file` and `io::write_file`); a test keeps the rest of the
engine from touching the file system on its own.
The checks hold for every thread of the server process. A path is checked, then opened: another
program that swaps a link in between can still win that race, so the roots keep an agent to the
files it was given rather than guarding against other software on the computer.
## Tools
| Tool | What it does |
|---|---|
| `list_commands` | Every command (id, label, menu, shortcut, params doc), optionally filtered |
| `run_command` | Run one command: `{command, params}` — every menu item, ribbon button and gesture is a command |
| `batch` | Run several commands in order and stop at the first error |
| `new_presentation`, `open_presentation`, `save_presentation`, `export` | Files: native `.deckcraft`, PowerPoint `.pptx`, PDF (slides, notes pages, handouts), PNG/JPEG, outline, animated GIF (size, seconds per slide, loop, slides) |
| `inspect_document`, `inspect_slide` | Structure of the deck / one slide (shapes, text, placeholders, animations) |
| `render_slide` | A slide as an image the model can look at |
| `add_slide`, `go_to_slide` | Slides by layout |
| `add_shape`, `add_text_box`, `set_text` | Shapes and text |
| `insert_picture`, `insert_table`, `insert_chart` | Content |
| `select`, `pointer`, `select_tool`, `key`, `type_text` | The same selection, mouse and keyboard paths as the UI |
| `screenshot` | The whole window, UI included (connected mode) |
| `call_app` | Any [control protocol](control-protocol.md) method (connected mode) |
A tool refuses an argument that is not in its schema (`insert_chart: unknown argument "data" (expected: categories, series, title, type)`). Keys inside `run_command`'s `params` are that command's parameters.
Useful commands for building decks: `slide.new {layout, title, body}`, `shape.insert {preset,
rect, text}`, `shape.connect {from, to, preset}`, `shape.freeform {points, closed, smooth}`,
`shape.merge {op, ids}`, `format.*`, `design.theme`, `transition.set`, `animation.set`,
`chart.*`, `table.*`, `file.export {path, layout}` (PDF) or `{path, width, secondsPerSlide, loop}`
(GIF). `deckcraft-cli describe COMMAND` prints any
command's parameters.