Skip to content

MCP Tools

Deckyard ships a Model Context Protocol server, so an AI agent can list, read, create, edit, comment on and export presentations. It is a thin layer over the same storage and AI modules the editor uses: an agent’s write goes through the same slide-type validation and per-deck access checks as a person’s.

This page describes core Deckyard 1.34.0: 27 tools and 7 prompts. The source of truth is server/mcp/tools.js and server/mcp/prompts.js in the Deckyard repository.

The server speaks two transports. Both register the same tools.

TransportHow it runsAuthenticationUse it for
stdionpm run mcp (node server/mcp/index.js), launched by the clientNone: the process runs on your machine with direct database accessClaude Desktop, Claude Code, Cursor
Streamable HTTPPOST /mcp on a running Deckyard server, no separate processAn API key as Authorization: Bearer dk_live_...Remote agents and hosted clients

Add Deckyard to your client’s MCP configuration, pointing cwd at your Deckyard checkout:

{
"mcpServers": {
"deckyard": {
"command": "node",
"args": ["server/mcp/index.js"],
"cwd": "/path/to/deckyard",
"env": {
"DECKYARD_MCP_OWNER_EMAIL": "you@example.com"
}
}
}
}

The server reads the .env in the Deckyard root, so it uses the same storage backend and LLM vendor as your instance.

DECKYARD_MCP_OWNER_EMAIL decides who the session acts as. It:

  • filters list_presentations to decks that address owns,
  • becomes the owner of decks the session creates,
  • turns on per-deck access checks: read for reading tools, collaborator-aware write access for mutating tools, and owner-only for delete_presentation,
  • is the author of comments the session writes.

Without it, the stdio session is treated as a trusted local single-user process: no owner filter, no per-deck checks, and the comment write tools refuse to run because there is nobody to attribute a comment to. Decks created without an owner may be inaccessible in the web UI, so set it.

A stdio session has no organization of its own. It acts in the instance’s single organization and refuses to guess once there are several.

The endpoint is /mcp on the Deckyard server itself; the MCP server initializes on the first request.

MethodPathDoes
POST/mcpSend a JSON-RPC request or a batch (array) of requests
GET/mcpOpen a server-sent event stream for an existing session
DELETE/mcpClose a session
OPTIONS/mcpCORS preflight

Sending initialize opens a session and returns its id in the Mcp-Session-Id response header. Send that header on later requests to stay in the session. A POST without the header and without initialize is handled statelessly: it authenticates and answers on its own.

Terminal window
# Open a session
curl -i -X POST https://your-instance.com/mcp \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'
# Call a tool in that session
curl -X POST https://your-instance.com/mcp \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "Mcp-Session-Id: <id from the header above>" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_presentations","arguments":{}}}'
PropertyValue
Protocol version reported by initialize2024-11-05
Session idle timeout30 minutes
Maximum concurrent sessions1000 (the least recently active one is evicted)
Request body limit1 MB
Stream keepaliveevery 30 seconds
CORSAccess-Control-Allow-Origin: *; restrict it at your reverse proxy if needed

The key is validated again on every request. A session is bound to the key owner that opened it: a request with a key from a different owner gets 403. An invalid or missing key gets 401, an expired session 404.

Supported JSON-RPC methods: initialize, tools/list, tools/call, prompts/list, prompts/get, ping.

/mcp and /api/v1 accept the same API keys, and a key means the same thing on both. Each tool declares the one permission it needs, the same one its /api/v1 counterpart needs.

PermissionTools
readget_slide_types, list_presentations, get_presentation, get_presentation_url, list_themes, validate_presentation, preview_slide, preview_presentation
writecreate_presentation_from_slides, add_slide, update_slide, remove_slide, reorder_slides, duplicate_presentation, delete_presentation
aicreate_presentation, append_slides, convert_slide, iterate_presentation, compress_presentation, analyze_presentation
exportexport_presentation
comments:readlist_comments, list_recent_comments
comments:writeadd_comment, reply_to_comment, set_comment_status

Over HTTP:

  • tools/list only lists the tools the key has the permission for.
  • A call spends the key’s per-minute request bucket and daily counters, shared with /api/v1: switching transport does not reset a limit. Tools needing ai also count against the daily AI limit, export_presentation against the daily export limit. The limits per tier are in Rate Limits.
  • The request bucket is spent before the permission is checked, so a refused call costs as much as an accepted one.

Over stdio there is no key, so there is no permission or quota gate.

A refused or failed tool call is a normal JSON-RPC response whose result carries isError: true and a text item starting with Error:, for example Error: API key lacks required permission: write. It is not an HTTP error status. An unknown tool name or method is a JSON-RPC error with code -32601.

While the instance is in maintenance mode, every tool without the read-only flag is refused with a message saying when to retry. Tools marked read-only below keep working; tools/list marks them with the annotation readOnlyHint: true.

  • Presentation ids. Every tool that takes presentationId also accepts id as an alias.
  • Slide positions are 0-based indexes (slideIndex, position, fromIndex, toIndex).
  • Return values are JSON objects serialized as text in the result’s content, except the two preview tools, which return an HTML document as text.
  • URLs in results (editUrl, presentUrl, downloadUrl) are only included when the instance has APP_URL or DOMAIN configured.
  • vendor on the AI tools overrides the configured LLM vendor for that call, e.g. "openai" or "anthropic".
  • lang takes a deck language code such as nl, en-GB or de.

The R/O column marks read-only tools, which stay available during maintenance.

ToolPermissionR/OParametersReturns
get_slide_typesreadyescategory (structural, content, all; default all), lang (language of the example content)types keyed by type, count, exampleLang, globalOptions
list_themesreadyesnonethemes: id, label, brandColors, hasBackgroundImages

get_slide_types answers for the session’s organization: core types, file-based custom types, and the organization’s own published slide types (keyed custom-<slug>), minus the types the organization disabled. Types marked deprecated or opted out of AI use are left out. Each entry carries its canonical typeId, a schema derived from the type’s field definitions, and an example content object you can copy. documented: false means the type has no usage guidance yet. An optional usage string holds rules the organization set for filling that type; agents should follow it. globalOptions lists fields any slide may carry, such as a background image or logo.

ToolPermissionR/OParametersReturns
list_presentationsreadyeslimit (default 50), ownership (owned, shared, all; default owned)presentations (id, title, theme, createdAt, updatedAt, slideCount, permission on shared decks, editUrl), total, ownerFilter, ownership
get_presentationreadyesid or presentationIdid, title, theme, lang, slides (index, id, type, content, notes), slideCount
get_presentation_urlreadyesid or presentationIdid, title, editUrl, presentUrl; a note instead of the URLs when no base URL is configured
validate_presentationreadyesid or presentationIdslideCount, warningCount, warnings (per slide: slideIndex, type, title, warnings), isValid

Shared decks (ownership: "shared" or "all") need the database storage backend.

ToolPermissionR/OParametersReturns
create_presentationainocontent (required: source text), title, theme (default default), lang (detected when omitted), speaker, ownerEmail, vendorid, title, theme, slideCount, slides (index, type, title), editUrl, presentUrl
create_presentation_from_slideswritenotitle (required), slides (required, 1-50 items of { type, content, notes? }), theme (default default), lang, ownerEmail, validation (strict or fix; default strict), auto_prepend_title (default false)As create_presentation, plus lang, and appliedFixes when validation is fix
duplicate_presentationwritenopresentationId (required)id, title, slideCount, editUrl of the copy

create_presentation runs the AI generator over free text. create_presentation_from_slides writes the slides you pass without an AI pass: use it when the agent already knows the slide types and content.

Validation in create_presentation_from_slides checks exactly what each slide type declares:

  • strict stops at the first problem and writes nothing; the error message says which slide and field failed. A key the type does not declare is refused as an unknown field.
  • fix applies automatic fixes (truncating, padding, switching layout) and lists them in appliedFixes.

auto_prepend_title: true adds an empty title slide, using title and the theme’s default title slide type, when the first slide is not already one.

ownerEmail on both create tools overrides the session owner as the new deck’s owner.

ToolPermissionR/OParametersReturns
add_slidewritenopresentationId, type, content (all required), position (appends when omitted)added, slideId, position, type, totalSlides
update_slidewritenopresentationId, slideIndex, content (all required), typeupdated, slideIndex, type, content
remove_slidewritenopresentationId, slideIndex (both required)removed, slideIndex, removedType, removedTitle, totalSlides
reorder_slideswritenopresentationId, fromIndex, toIndex (all required)moved, slide (type, title), from, to
convert_slideainopresentationId, slideIndex, targetType (all required), vendorconverted, slideIndex, fromType, toType, content

update_slide merges content into the slide’s existing content and validates the result. Passing type converts the slide first, but only between the pairs the editor converts: content and image-text (both ways), image to image-text, list to content, and title and chapter title (both ways). Other pairs are refused; use convert_slide, which lets the AI restructure the content for any target type.

New slides from add_slide get the type’s defaults for fields you leave out.

ToolPermissionR/OParametersReturns
append_slidesainopresentationId, content (both required), vendorappended, insertedAt, totalSlides, newSlides (type, title)
iterate_presentationainopresentationId, command (both required), vendorapplied, targetSlideIndex, summary, modifications (slideIndex, action, reasoning), totalSlides
compress_presentationainopresentationId (required), apply (default false), intensity (moderate or aggressive; default moderate), vendorapplied, merges, removals, recommendations, slidesAfter when applied
analyze_presentationaiyespresentationId (required), vendorslideCount, suggestionCount, suggestions (slideIndex, category, body, proposedSlide)
  • append_slides generates content slides from new text and inserts them before any closing slides (payoff, end, follow-invite) at the end of the deck.
  • iterate_presentation takes an instruction in plain language, such as “make slide 3 punchier” or “split the KPI slide”, and saves the result.
  • compress_presentation only previews its merges and removals unless apply is true; a preview needs read access, applying needs write access.
  • analyze_presentation suggests improvements and changes nothing.
ToolPermissionR/OParametersReturns
preview_slidereadyespresentationId, slideIndex (both required)A self-contained HTML document of one slide
preview_presentationreadyespresentationId (required), slideRange (e.g. "0-4"; all slides when omitted)A self-contained HTML document with the slides as a gallery
export_presentationexportyespresentationId, format (both required), langid, title, format, downloadUrl, note

The previews include their CSS and images, so a client can show them as an artifact. They need read, not export: they produce no file.

export_presentation does not render anything itself. It returns a URL on the instance that renders the file when opened in a browser signed in to Deckyard.

formatFile
pdfServer-rendered PDF
pptxPowerPoint
htmlSelf-contained HTML
jsonThe deck source
png-zipOne PNG per slide, zipped

lang selects one language version of a multilingual deck.

ToolPermissionR/OParametersReturns
list_commentscomments:readyespresentationId (required), status (open, resolved, dismissed, all; default all), slideId, since (ISO 8601), includeReplies (default false)presentationId, presentationTitle, comments, total
list_recent_commentscomments:readyesownership (owned, shared, all; default all), authorEmail, status, since, limit (default 50, max 200)comments, total, ownership, ownerFilter
add_commentcomments:writenopresentationId, body (both required; max 5000 characters), slideIdok, comment
reply_to_commentcomments:writenopresentationId, commentId, body (all required)ok, comment
set_comment_statuscomments:writenopresentationId, commentId, status (resolved, open, dismissed; all required)ok, comment
  • Comments are newest first. By default replies are nested under their comment; includeReplies: true returns them as separate rows.
  • Each comment carries the slide as it is now (index, type, title, or marked deleted), a snapshot of the slide from when the comment was written, and an editUrl that opens the editor on that slide.
  • list_recent_comments covers all decks the session can reach and, with the file storage backend, returns nothing.
  • add_comment with a slideId anchors the comment to that slide and stores a snapshot of it.
  • Replying to a reply adds to the same thread: threads are one level deep.
  • set_comment_status allows the same transitions as the app (open to resolved, open to dismissed, resolved to open) and only for the presentation’s owner or its original creator, who keeps that right after the deck changes owner.
  • The three write tools need the database storage backend and an acting user: an API key’s owner, or DECKYARD_MCP_OWNER_EMAIL over stdio. Comments written by an agent notify and update open editors like any other comment.

The matching REST endpoints are in Public API: Comments.

ToolPermissionR/OParametersReturns
delete_presentationwritenopresentationId, confirm (both required)With confirm: true: deleted, id. Otherwise deleted: false with the deck’s title, slideCount and a message

The deck is moved to trash. Only its owner can delete it; confirm: false is a dry run that tells the agent what it is about to delete.

Prompts are parameterized workflows a client shows in its prompt menu (the / menu in Claude Desktop). The client collects the arguments, the server returns the instruction, and the model then drives the tools itself.

PromptDoesRequired argumentsOptional arguments
create-presentationGenerate a deck from text, notes or a documentcontentlanguage, speaker
create-from-structured-dataBuild a deck from pre-structured slides, without AI rewritingtitle, datalanguage
improve-presentationAnalyze a deck and apply improvementspresentationIdfocus
refine-slideChange one slide with a plain-language instructionpresentationId, instruction
compress-presentationMake a deck shorter by merging or removing slidespresentationIdintensity
add-contentAdd slides to a deck from additional textpresentationId, content
deck-overviewSummarize a deck’s slides, theme and validation, or list deckspresentationId

A fork adds its own tools without editing core: a custom/mcp-tools.js that default-exports a registrar is loaded by both transports.

custom/mcp-tools.js
export default function registerCustomTools(server, ctx) {
server.tool(
'my_tool',
'What the tool does, written for the agent',
{
type: 'object',
properties: { presentationId: { type: 'string' } },
required: ['presentationId'],
},
async ({ presentationId }, context) => {
// ctx.storageScopeOf(context) is the scope every storage call takes
return { url: ctx.presentationUrl(presentationId, 'present') };
},
{ permission: 'read', readOnly: true },
);
}

ctx provides repoRoot, defaultOwnerEmail, getOwner(context), storageScopeOf(context), getAppBaseUrl() and presentationUrl(id, mode).

Two defaults fail closed:

  • Declare permission. A tool without one is hidden from, and refused to, every API key. Over stdio it still works, which makes the omission easy to miss.
  • Declare readOnly: true for a tool that changes nothing, or it is refused during maintenance.

Registering an existing name replaces that tool, which is how a fork wraps a core tool. The replacement keeps the original’s permission and readOnly unless it restates them, and removing a permission this way throws.