# Prezzly documentation Full English docs dump for agents. HTML versions: https://prezzly.ai/docs/ # Prezzly for agents Connect Cursor, Claude Code, or Claude.ai to Prezzly over MCP, install the presentations skill, and publish HTML slides or a dashboard as a live view link. URL: https://prezzly.ai/docs/ Prezzly hosts HTML presentations and dashboards. Your agent writes the file; Prezzly publishes a view link, keeps revisions, and runs presenter mode. The MCP endpoint to paste into a client is: `https://mcp.prezzly.ai` That is the Streamable HTTP endpoint. ## Quickstart 1. [Connect an MCP client](/docs/mcp/connect/). Cursor, Claude, and other clients start OAuth (or accept a Bearer key from [Connections](https://app.prezzly.ai/connections)). 2. [Install the Prezzly skill](/docs/skills/install/) so the agent uploads files correctly instead of pasting image contents into tool calls. 3. Ask the agent to publish the deck. It should call `create_presentation` once and give you a live [view link](/docs/mcp/upload/). Do not ask the agent to list your library first. Do not upload a placeholder and promise a revision later. ## What Prezzly MCP can do After you connect, the agent can: - Publish a new slide deck or a scrollable dashboard - Upload a local folder with `uploadUrl` and `curl` (images stay on disk) - Revise an existing deck without changing the share link - Manage folders, share links, collaborators, speaker notes, and audience questions - Read your templates and follow a template `SKILL.md` See the [tool reference](/docs/mcp/tools/) for every call. ## Slides vs dashboard `kind` is chosen at upload time. It is not inferred from the HTML. - `presentation`: 16:9 slides. Each slide is `.slide`; exactly one also has `.active`. - `dashboard`: one vertically scrollable page with section markers. Markup details live in [Slides and dashboards](/docs/html-conventions/). ## Guides - [Connect an MCP client](/docs/mcp/connect/) - Cursor, Claude.ai, Claude Code, VS Code, and others - [Install the Prezzly skill](/docs/skills/install/) - `npx skills add tagpilot/prezzly-skills` - [Publish and update a deck](/docs/mcp/upload/) - chat `files` vs local `uploadUrl` - [Slides and dashboards](/docs/html-conventions/) - HTML the runtime expects - [Tool reference](/docs/mcp/tools/) - 32 tools plus prompts - [Troubleshooting](/docs/troubleshooting/) - 401, 402, 409, missing tools For the product story, see [MCP for presentations in Claude and Cursor](/blog/mcp-for-presentations-claude-cursor/). --- # Connect an MCP client Add Prezzly MCP in Cursor, Claude.ai, Claude Code, VS Code, Windsurf, or any Streamable HTTP client. Use OAuth or a Bearer key from Connections. URL: https://prezzly.ai/docs/mcp/connect/ Paste this URL into any MCP client that speaks Streamable HTTP: `https://mcp.prezzly.ai` Prezzly authenticates with `Authorization: Bearer `. Prefer OAuth when the client supports it. If it asks for a key, generate one under [Connections](https://app.prezzly.ai/connections). > **Note:** After you connect, [install the Prezzly skill](/docs/skills/install/) so the agent uploads files locally (`uploadUrl` + `curl`) instead of pasting image contents into tool calls. ## Cursor Add this to `.cursor/mcp.json` in the project (or your user MCP config). The `url` field is enough: ```json { "mcpServers": { "prezzly": { "url": "https://mcp.prezzly.ai" } } } ``` Reload MCP in Cursor Settings. Cursor starts OAuth and asks you to sign in to Prezzly. Then tell the agent to upload the project folder. See [Publish and update a deck](/docs/mcp/upload/). ## Claude.ai Settings → Connectors → Add custom connector. Paste `https://mcp.prezzly.ai`. Claude starts OAuth and asks you to sign in to Prezzly. Ask Claude to build or upload the deck. In the browser it has no disk, so it should call `create_presentation` with a self-contained `index.html` in `files`. ## Claude Code ```bash claude mcp add --transport http prezzly https://mcp.prezzly.ai ``` Claude Code starts OAuth when the client supports it. Otherwise paste a Bearer key from Connections. ## Claude Desktop Add a custom connector with `https://mcp.prezzly.ai`, or put the same JSON as Cursor into the Desktop MCP config. Prefer OAuth. A Bearer key from Connections also works. ## VS Code (Copilot) In `.vscode/mcp.json` or your user MCP settings: ```json { "servers": { "prezzly": { "url": "https://mcp.prezzly.ai", "type": "http" } } } ``` Field names vary by VS Code build. If the editor asks for a header, set `Authorization` to `Bearer ` from Connections. ## Windsurf, Cline, and Zed Any client that accepts a Streamable HTTP MCP URL can use `https://mcp.prezzly.ai`. Use OAuth when the client starts it. Otherwise add: ```http Authorization: Bearer ``` ## ChatGPT and other hosts If the host lets you add a custom MCP or connector URL, paste `https://mcp.prezzly.ai`. Chat clients without a disk should send HTML in `files`, not a zip. See [Publish and update a deck](/docs/mcp/upload/). ## OAuth vs API key | Method | When to use | | --- | --- | | OAuth | Cursor, Claude.ai, and any client that opens a Prezzly sign-in window | | Bearer API key | Desktop or CLI clients that only accept a token | Create and revoke keys on [Connections](https://app.prezzly.ai/connections). A 401 with a `create_upload_link` hint means the upload token expired, not that you lost the connection. Mint a new upload URL and continue. If tools look stale (no `create_upload_link`), reload the MCP server in the client. Do not fall back to pasting HTML into `add_revision`. --- # Install the Prezzly skill Install prezzly-presentations with npx skills add tagpilot/prezzly-skills so Cursor, Claude Code, and Codex follow the Prezzly upload contract. URL: https://prezzly.ai/docs/skills/install/ The MCP server tells the agent *that* it can publish. The skill tells it *how*: slides vs dashboard, new deck vs revision, inventory of local files, and `uploadUrl` + `curl` for binaries. Without the skill, a local agent often base64-encodes images into tool arguments. That is slow and hits size limits. With the skill, it zips the folder and `curl -T`s it. The published repo is [`tagpilot/prezzly-skills`](https://github.com/tagpilot/prezzly-skills). Source of truth is the Prezzly monorepo; deploys copy that tree 1:1. ## Install with the skills CLI Requires Node.js 18+ and an agent that supports the [skills CLI](https://github.com/vercel-labs/skills) (Cursor, Claude Code, Codex, and others). ```bash npx skills add tagpilot/prezzly-skills ``` List skills in the repo: ```bash npx skills add tagpilot/prezzly-skills --list ``` Install only `prezzly-presentations`: ```bash npx skills add tagpilot/prezzly-skills --skill prezzly-presentations ``` Scope the install to one agent or to your user profile when the CLI supports it: ```bash npx skills add tagpilot/prezzly-skills --agent cursor npx skills add tagpilot/prezzly-skills -g ``` `--agent` values commonly include `cursor`, `claude-code`, and `codex`. `-g` / `--global` writes into the user-level skills directory instead of the project. The CLI writes `skills-lock.json` in the project so the next install can pin the same revision. ## Where the skill lands After install you should see `SKILL.md` in one of these trees (the CLI picks the one your agent reads): | Agent | Typical path | | --- | --- | | Cursor | `.cursor/skills/prezzly-presentations/SKILL.md` or `.agents/skills/prezzly-presentations/SKILL.md` | | Claude Code | `.claude/skills/prezzly-presentations/SKILL.md` | | Codex and others | `.agents/skills/prezzly-presentations/SKILL.md` | Reload the agent window if it does not pick the skill up immediately. ## Manual install If you cannot run the CLI, copy [`SKILL.md`](https://github.com/tagpilot/prezzly-skills/blob/master/skills/prezzly-presentations/SKILL.md) into the path your agent already reads (see the table). Keep the folder name `prezzly-presentations`. ## Update Re-run the same `npx skills add` command. The lockfile records `source`, `ref`, and a content hash. After a Prezzly deploy that changes the skill, update the project so the agent does not follow a stale upload recipe. ## Skill vs MCP instructions | You have | What the agent knows | | --- | --- | | MCP only | Tool names, server instructions, `viewUrl` / `uploadUrl` | | Skill only | HTML conventions and the upload playbook, but no live tools | | Both | The intended setup: connect MCP, then install the skill | The skill does not replace [connecting an MCP client](/docs/mcp/connect/). After both are in place, [publish the deck](/docs/mcp/upload/). --- # Publish and update a deck Publish with create_presentation: chat clients send files, local clients curl a zip to uploadUrl until missingAssets is empty. Revisions use create_upload_link. URL: https://prezzly.ai/docs/mcp/upload/ 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": "..." }] }) ``` 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 && 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' "" ``` Or upload `index.html` first, then each remaining ASCII path: ```bash curl -sS -T index.html "index.html" curl -sS -T "assets/hero.webp" "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/). --- # Slides and dashboards Prezzly HTML conventions: .slide plus one .active for 16:9 decks, section markers for dashboards, speaker notes, sandbox limits, and optional runtime script. URL: https://prezzly.ai/docs/html-conventions/ `kind` on `create_presentation` chooses the runtime. It is not inferred from the HTML. Content runs in a sandboxed iframe: no `localStorage`, no cookies, no top-level navigation. ## Slides (`kind=presentation`) Each slide is an element with class `slide`. Exactly one also has class `active`. Hide the rest with CSS: ```css .slide { display: none; } .slide.active { display: block; } ``` Design each slide to fill a 16:9 frame (rendered at 1280×720 and scaled). Keep content inside that frame so it stays centered. ```html

Hello

Topic

``` Prezzly injects the runtime when it serves the deck. You do not need your own JavaScript for arrow keys. The runtime toggles `active` (keyboard, presenter view, `?slide=N`, thumbnails). A deck may move `active` with its own ArrowLeft / ArrowRight handler; that is respected. Optional and harmless: ```html ``` Do not require `prezzly.io` as the runtime host. ## Dashboards (`kind=dashboard`) Build one vertically scrollable layout, not slides. Mark major sections with `id` or `data-prezzly-section` (fallback: `h1`–`h3`) so section navigation and scroll sync work. Internal links, buttons, and modals are fine. The same sandbox rules apply. ## Speaker notes Put notes in `data-notes` on each `.slide`, or set them later with `get_notes` / `set_note` / `set_notes`. UI notes override code notes in presenter view. ## Templates `list_templates` and `get_template` return your style packs, including `SKILL.md` and a download URL. Build against that skill, then upload with `create_presentation`. Do not list templates as a prerequisite for a first upload unless the user asked for a style. ## Externally built decks Reveal.js, hand-written HTML, and other 16:9 decks work as-is if one slide element carries `active` and CSS hides the rest. Guest upload from the Prezzly app accepts the same `index.html` or ZIP. ## What the sandbox blocks Do not rely on `localStorage`, cookies, or navigating the top window. Use in-memory state. Load fonts and images from the deck files or `https` URLs. If slides do not change, check that exactly one element has both `slide` and `active`. If a dashboard has no section nav, add `id` or `data-prezzly-section` on the major blocks. See [Troubleshooting](/docs/troubleshooting/). --- # Tool reference All 32 Prezzly MCP tools for presentations, folders, sharing, notes, audience questions, and templates, plus the upload_deck and revise_deck prompts. URL: https://prezzly.ai/docs/mcp/tools/ The server also registers two prompts: `upload_deck` and `revise_deck`. Use them when the client surfaces prompts. Tool names stay stable; descriptions below match the live server. Do not call read-only list tools as a prerequisite for a first upload. ## Presentations | Tool | What it does | | --- | --- | | `create_presentation` | Publish a new deck. Local: title and kind, then `curl` to `uploadUrl`. Chat: self-contained `files`. Never put image or font bytes in arguments. | | `add_revision` | Update a named existing deck. Chat: real `index.html` in `files`. Local: `create_upload_link` + curl zip. Never send a placeholder. | | `add_files` | Append small text/svg (under 200 KB) or an `https` `url` for the server to fetch. Not for image bytes. Existing paths are rejected. | | `create_upload_link` | Mint a 30-minute `uploadUrl` for an existing deck or missing assets. | | `get_presentation` | Full owned deck: metadata, revisions, collaborators, share links. Use when you already have an id. | | `get_presentation_files` | List revision files (path and size). Defaults to current. | | `get_presentation_file` | Read one file (utf8 or base64). Truncated over 256 KB. | | `list_presentations` | Browse the library. Optional folder filter. Not a first-upload step. | | `update_presentation` | Rename and/or move to a folder. Does not upload content. | | `restore_revision` | Make an older revision live. History is kept. | | `delete_presentation` | Permanently delete the deck and its revisions. Owner only. | ## Sharing | Tool | What it does | | --- | --- | | `create_share_link` | Time-limited public view link. Default TTL 7 days. | | `revoke_share_link` | Revoke a public link by token id from `get_presentation`. | | `share_presentation` | Grant a collaborator read access by email. | | `unshare_presentation` | Revoke collaborator access by email. | ## Folders | Tool | What it does | | --- | --- | | `list_folders` | Folders you own, with item counts and share state. | | `create_folder` | Create a folder, optionally nested. | | `update_folder` | Rename and/or move under a different parent. | | `delete_folder` | Delete the folder. Files fall back to the library root. | | `share_folder` | Share the folder and everything inside by email. | | `unshare_folder` | Revoke folder access by email. | | `list_folder_access` | List collaborator emails on a folder. | ## Speaker notes | Tool | What it does | | --- | --- | | `get_notes` | Merged code and UI notes per slide index. | | `set_note` | Set UI notes for one 0-based slide. Overrides `data-notes` in presenter view. | | `set_notes` | Bulk-set UI notes. Array index maps to slide index. | ## Audience questions (pins) | Tool | What it does | | --- | --- | | `set_allow_pins` | Enable or disable audience questions on a deck. | | `list_question_sessions` | Q&A sessions with question counts. | | `list_questions` | Pins/questions, optionally scoped to a session. | | `resolve_question` | Mark a question resolved, or reopen it. | | `delete_question` | Remove a question. | ## Templates | Tool | What it does | | --- | --- | | `list_templates` | Your style packs. Optional before a build; not required for a first upload. | | `get_template` | Manifest, download URL, and inline `SKILL.md`. | | `get_template_file` | One text file from a template (max 64 KB). | ## Prompts | Prompt | What it does | | --- | --- | | `upload_deck` | Instructs the model to publish a new deck in one `create_presentation` call. | | `revise_deck` | Instructs the model to update a named deck (`add_revision` or `create_upload_link`). | Upload playbook: [Publish and update a deck](/docs/mcp/upload/). HTML: [Slides and dashboards](/docs/html-conventions/). --- # Troubleshooting Fix Prezzly MCP errors: expired upload tokens, plan limits, path conflicts, missing create_upload_link, OAuth, and decks that do not advance or scroll. URL: https://prezzly.ai/docs/troubleshooting/ Most MCP failures are an expired upload token, a stale tool catalog, or a plan limit. The connection itself is usually fine. ## The upload returned 401 `uploadUrl` lasts about 30 minutes. Call `create_upload_link` and continue `curl -T`. You do not need to sign in again. If every MCP call returns 401, the OAuth session or API key is dead. Reconnect in the client or generate a new key on [Connections](https://app.prezzly.ai/connections). ## The client has no `create_upload_link` The catalog is stale. Reload the Prezzly MCP server (Cursor: Settings → MCP → toggle the server). Do not send a placeholder through `add_revision` to obtain `uploadUrl`. ## 402 `plan_limit` Storage or per-upload size cap. Tell the user; do not retry. Free-plan defaults include 10 MB per upload and 250 MB storage. Compare plans in the app. ## 409 path exists / empty deck - "Upload `index.html` or a zip first": the deck has no HTML yet. Upload HTML or a zip before individual assets. - `files` plus `missingAssets`: that path already exists with different bytes. Replace it with a zip to `uploadUrl` or `add_revision`, not a second `add_files`. ## 400 on `index.html` A single PUT of `index.html` to an existing deck is rejected. Local clients zip `index.html` (and new binaries) to `uploadUrl`. Chat clients send the real HTML through `add_revision`. ## OAuth never starts Confirm the URL is `https://mcp.prezzly.ai`. The client must send Streamable HTTP, not stdio. If it only accepts a token, use a Bearer key from Connections. Pop-up blockers can swallow the Prezzly sign-in window. Allow the popup and try again. ## The agent stuffed images into tool arguments [Install the skill](/docs/skills/install/). On a local client, `create_presentation` takes title and kind only; binaries go through `curl -T`. Chat clients should use https URLs or `add_files` with `url`. ## Slides do not advance Exactly one element needs both `slide` and `active`. CSS must hide non-active slides. Do not add a custom ArrowLeft / ArrowRight handler unless you intend to own navigation. See [Slides and dashboards](/docs/html-conventions/). ## The dashboard has no section list Mark major blocks with `id` or `data-prezzly-section`. Bare `h1`–`h3` are a fallback. Confirm `kind` was `dashboard` at upload time. ## `missingAssets` never clears Each referenced relative path must exist on disk and be uploaded under the same path. Skip `https:`, `data:`, `#`, and `mailto:`. Spaces and non-ASCII paths need a zip, not a raw `curl -T`. ## The new revision looks too small Compare `revision.totalBytes` with the previous revision from `get_presentation`. If it shrank and that was not intended, call `restore_revision`. More status codes: [Publish and update a deck](/docs/mcp/upload/).