Call `create_presentation` once for a new deck. Do not list the library first. Do not upload a stub.

`kind` is `presentation` (slides) or `dashboard` (one scrollable page). It is not inferred from the HTML. See [Slides and dashboards](/docs/html-conventions/).

Never put image or font bytes in MCP tool arguments.

## Chat clients (Claude.ai, no disk)

Send `files` with one self-contained `index.html`. Put CSS and JS in that HTML. Use https image URLs or small data URIs. Do not build a zip in the browser. `add_files` with `url` can pull a remote asset.

```
create_presentation({ "title": "Q1 Review", "kind": "presentation", "files": [{ "path": "index.html", "content": "<!doctype html>..." }] })
```

Then give the user `viewUrl`. If `missingAssets` is not empty, fetch those URLs with `add_files` or tell the user the deck is incomplete.

## Local clients (Cursor, Claude Code, files on disk)

Call `create_presentation` with title and kind only. Do not paste `index.html` or binaries into the tool.

```
create_presentation({ "title": "Q1 Review", "kind": "dashboard" })
```

Read `uploadUrl`. Prefer one zip of the folder (the server unwraps a single top-level folder). Use a zip when any path has spaces or non-ASCII characters (`curl -T` does not encode the URL).

```bash
cd <dir> && zip -r /tmp/deck.zip . -x '.git/*' '.cursor/*' '.agents/*' 'node_modules/*' 'skills-lock.json' '*.zip' '*.code-workspace'
curl -sS -T /tmp/deck.zip -H 'Content-Type: application/zip' -w '\n%{http_code}\n' "<uploadUrl>"
```

Or upload `index.html` first, then each remaining ASCII path:

```bash
curl -sS -T index.html "<uploadUrl>index.html"
curl -sS -T "assets/hero.webp" "<uploadUrl>assets/hero.webp"
```

`curl -T` to `uploadUrl` (trailing slash) is fine: the server unpacks a zip even if curl appends `deck.zip` to the path.

Read `missingAssets` from each JSON body. Repeat until the list is empty. If `uploadUrl` expired, call `create_upload_link` and continue. Then call `get_presentation_files` once and give the user `viewUrl`.

> **Warning:** Do not tell the user the upload is done while `missingAssets` is non-empty or `warning` is set. Do not flatten a zip (`zip -j`). Keep relative paths.

## New deck vs revision

`create_presentation` makes a new deck. `add_revision` / a zip to `create_upload_link` is a new version of the same deck. History, share links, and comments stay.

Use a revision when the user named the deck, pasted a Prezzly URL or id, or said update / next revision. Use a new presentation when they asked for another deck or a separate copy. If you are not sure, ask.

### Local edit

1. Call `create_upload_link` with the presentation id.
2. Zip what changed. A text-only tweak can be a zip of `index.html`. Previously uploaded assets still referenced by the HTML are reused (`reusedAssets`).
3. `curl -T` the zip to `uploadUrl`.
4. Call `get_presentation_files`. When `missingAssets` is empty, give `viewUrl`.

A single PUT of `index.html` to an existing deck is rejected (400). A zip to `uploadUrl` is a new revision.

### Chat edit

`add_revision` with `files` containing the real `index.html`. Assets still referenced are reused. Never send a placeholder to obtain `uploadUrl`. Use `create_upload_link` for that.

`add_files` appends small text or a remote `https` URL. Existing paths are rejected; zip a new revision to replace them.

`restore_revision` rolls back. Revisions are history.

## Upload responses

| Status | Body signal | What to do |
| --- | --- | --- |
| 200 | `missingAssets` non-empty | Upload the listed paths. Not done yet. |
| 200 | `missingAssets: []` | Call `get_presentation_files`, then give `viewUrl`. |
| 200 | `warning` set | Act on the warning before finishing. |
| 401 | `create_upload_link` hint | Token expired. Call `create_upload_link`, retry. |
| 402 | `code: plan_limit` | Storage or upload cap. Tell the user; do not retry. |
| 409 | Upload `index.html` or a zip first | Empty deck. Upload HTML or a zip before assets. |
| 409 | `files` + `missingAssets` listed | Path exists with different bytes. Replace via a zip / `add_revision`. |
| 400 | `index.html` cannot be added this way | Replace HTML with a zip (local) or `add_revision` files (chat). |

Re-`curl -T` of the exact same bytes is safe: it returns 200, not 409.

Add `-w '\n%{http_code}\n'` so you see the status. `curl -sS` prints the JSON body on 4xx, so read the body, not just the exit code.

## Do not

- Upload a stub, placeholder, or empty shell and promise to revise later
- Put image, font, zip, or a local `index.html` in tool arguments on a local client
- Call `list_presentations` to guess which deck to update, or before a first upload
- Filter the folder by `html`/`css`/`js` and ignore images
- Give `viewUrl` without `get_presentation_files` after the last upload

If `create_upload_link` is missing from the tool list, reload the Prezzly MCP server. See [Troubleshooting](/docs/troubleshooting/).