Core tools
Generated MCP reference for core tools.
This page is generated from mcp-catalog.json. Do not edit it by hand.
Source: paper3 local extraction on 2026-05-29. Generated: 2026-05-29T11:34:45.044Z.
create_node
Create node
Creates a typed node and inserts it under parentId. Use this for primitives. For complex layouts prefer write_html.
When type === 'form', ALWAYS read get_guide({topic:'forms'}) BEFORE you start adding inputs/buttons. Forms have subtle rules around submit-button auto-detection, the consent checkbox requirement, fieldName uniqueness, submitActions payload shape, and visibleIf / multi-step semantics. Skipping the guide produces forms that look fine in the canvas but fail at runtime.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
parentId | string | Yes | |
type | string | Yes | |
props | object | No | |
index | number | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"parentId": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"frame",
"form",
"text",
"button",
"input",
"rect",
"image"
]
},
"props": {
"type": "object",
"properties": {
"text": {
"type": "string"
},
"label": {
"type": "string"
},
"placeholder": {
"type": "string"
},
"src": {
"type": "string"
},
"styles": {
"type": "object",
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
}
]
}
}
},
"additionalProperties": false
},
"index": {
"type": "number"
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"required": [
"parentId",
"type"
],
"additionalProperties": false
}delete_node
Delete node (one or many)
Removes one or more nodes from their parents. Pass nodeId for a single delete, or nodeIds: [...] to delete several in one call (the model-side framing overhead drops linearly).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
nodeId | string | No | |
nodeIds | array | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"nodeId": {
"type": "string"
},
"nodeIds": {
"type": "array",
"items": {
"type": "string"
}
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"additionalProperties": false
}edit_html
Edit HTML
Surgically edits a section by replacing an exact HTML substring. Mirrors Claude Code's Edit tool: pass nodeId, the exact old_html that currently exists in that section, and the new_html replacement. The string match must be unique unless replace_all:true. Internally we render the section's HTML, perform the substring replacement, re-parse the result, and atomically swap the section's children (and its outer styles, when editing a frame/rect). WORKFLOW: 1) read(\{nodeId\}) to see what's there, 2) edit_html with a precise old_html substring you copied from that read. The exact-match requirement guarantees you read first — synthetic guesses will not match. Use this for targeted edits (a heading, a CTA, swapping an image src) without rewriting the whole section. For full rewrites, delete_node + write_html on the parent is cleaner. Limitations: edit_html cannot be used on leaf nodes (text/button/image/input) — use set_text_content or set_node_field instead. After edit, the new HTML must parse to exactly one root element.
INPUT SHAPES:
• \{nodeId, old_html, new_html, replace_all?\} — single edit.
• \{edits: [\{nodeId, old_html, new_html, replace_all?\}, ...]\} — many edits applied SEQUENTIALLY (in array order). Saves N round-trips when you have a list of independent string swaps queued up (e.g. brand rename across 8 sections). Each edit re-reads the target node fresh, so two edits on the SAME nodeId will see each other's output — order them with that in mind. Returns \{applied: [\{nodeId, occurrencesReplaced\}, …], failed: [\{nodeId, error\}, …]\}. Failures don't roll back earlier successes — the agent decides whether to retry or accept partial progress.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
nodeId | string | No | |
old_html | string | No | |
new_html | string | No | |
replace_all | boolean | No | |
edits | array | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"nodeId": {
"type": "string"
},
"old_html": {
"type": "string"
},
"new_html": {
"type": "string"
},
"replace_all": {
"type": "boolean"
},
"edits": {
"type": "array",
"items": {
"type": "object",
"properties": {
"nodeId": {
"type": "string"
},
"old_html": {
"type": "string"
},
"new_html": {
"type": "string"
},
"replace_all": {
"type": "boolean"
}
},
"required": [
"nodeId",
"old_html",
"new_html"
],
"additionalProperties": false
}
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"additionalProperties": false
}get_basic_info
Get document basic info
Returns top-level info: site id, name, version, settings, and the list of pages (each with its artboards).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"additionalProperties": false
}get_guide
Get guide
Returns paper3's design and operations doctrine for a given topic. Always start a session with get_guide({ topic: 'design-quality' }) — the prompt structure of your work matters more than the model. Available topics: paper3-mcp-instructions, design-quality, design-system, site-classes, responsive, interactivity, navigation-menus, image-generation, screenshot-loop, collections, animations, forms
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
topic | string | No |
Input schema
{
"type": "object",
"properties": {
"topic": {
"type": "string",
"enum": [
"paper3-mcp-instructions",
"design-quality",
"design-system",
"site-classes",
"responsive",
"interactivity",
"navigation-menus",
"image-generation",
"screenshot-loop",
"collections",
"animations",
"forms"
]
}
},
"additionalProperties": false
}get_screenshot
Get screenshot
Captures a real JPEG screenshot of ANY node — an artboard for the full page, a section for one band of the layout, a single button to inspect a hover-state visual, anything addressable by id. Render goes through headless Chromium with the node's resolved CSS + design tokens, so the screenshot reflects exactly what the user sees on the canvas. The image returns inline so you can see it directly.
When to scope it down: prefer the smallest meaningful subtree (a section, a card) over the whole artboard whenever you only care about ONE area — the screenshot is sharper at section scope and you stop sending megapixels of unrelated content to the vision model. Pass the artboard id only when you need the full-page composition.
Optional viewportWidth overrides the layout width the section is rendered at; defaults to the artboard's authored width (artboards) or 1280 px (subtrees). Useful for spot-checking a responsive layout at a specific breakpoint without rebuilding the artboard.
Tip: before screenshotting an ARTBOARD, set its height to 'fit-content' via set_node_field, otherwise the bottom of the page will be cropped. Subtrees naturally size to their content so no setup is needed.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
nodeId | string | Yes | |
viewportWidth | number | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"nodeId": {
"type": "string"
},
"viewportWidth": {
"type": "number"
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"required": [
"nodeId"
],
"additionalProperties": false
}outline
Outline a tree (flat list, no styles)
Cheap structural overview of the tree. Returns [\{id, type, name, depth, parentId, childCount\}] for everything under rootId (or the whole doc when omitted), capped at depth levels (default 2). Use this BEFORE read when you don't know which nodeId you want — scan the outline, identify the section, then read only that subtree.
Args: rootId? (anchor at a subtree, omit for full doc), depth? (1-10, default 2), maxNodes? (truncation cap, default 2000).
Replaces get_compact_tree + get_tree_summary — one tool, one cache fingerprint.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
rootId | string | No | |
depth | number | No | |
maxNodes | number | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"rootId": {
"type": "string"
},
"depth": {
"type": "number"
},
"maxNodes": {
"type": "number"
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"additionalProperties": false
}read
Read a node (HTML + inline styles + all data attrs)
Canonical reader for paper3's tree. Returns the node's HTML with everything authored stamped inline:
• inline-CSS for resting styles
• data-pid for identity
• data-tablet="…" / data-mobile="…" for breakpoint divergences (styles + text + src + alt)
• data-state-hover / -focus / -active for interactive-state styles
• behaviors / animations / classStyles serialised as data-* attrs
Round-trips BYTE-STABLE through edit_html — pass any substring you saw here as old_html and it will match. Same Read+Edit pattern as Claude Code.
INPUT SHAPES:
• \{\} — whole document.
• \{nodeId\} — one subtree.
• \{nodeIds: [a, b, c]\} — many subtrees in one call, returned as \{nodes: \{a: \{code, …\}, b: \{…\}, …\}, missing: [...]\}. Use whenever you need to inspect 3+ sections — saves N tool-call round-trips.
Options: compact: true strips inline styles (~8× smaller, structural reconnaissance only — DO NOT feed compact output back to edit_html, the substring won't match). maxChars caps each node's HTML (default 50000); on overflow you get the head + truncated: true + a hint pointing at search_nodes / a deeper nodeId.
Workflow for surgical edits: 1) read(\{nodeId\}) → 2) edit_html(\{nodeId, old_html, new_html\}) with the exact substring copied from step 1.
Replaces the legacy get_html, get_jsx, get_node_info, get_breakpoint_overrides — one fingerprint, snapshot-cache dedups identical reads in the same turn, history trim supersedes older reads of the same node automatically.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
nodeId | string | No | |
nodeIds | array | No | |
compact | boolean | No | |
maxChars | number | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"nodeId": {
"type": "string"
},
"nodeIds": {
"type": "array",
"items": {
"type": "string"
}
},
"compact": {
"type": "boolean"
},
"maxChars": {
"type": "number"
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"additionalProperties": false
}read_styles
Read authored styles on a node (per breakpoint + state)
Surgical style reader. Returns just the styles you ask for, NOT a full HTML dump. Use BEFORE update_styles / update_state_styles / set_breakpoint_override so you know what's already there — same Read-and-Edit pattern as read + edit_html but for the structured style layer.
INPUT SHAPES:
• \{nodeId\} — one node.
• \{nodeIds: [a, b, c]\} — many nodes in one call, returned as \{nodes: \{a: \{...\}, b: \{...\}\}, missing: [...]\}. Use whenever 3+ nodes need a styles check (e.g. comparing all .btn-primary buttons before a bulk update).
Filters (apply across all nodes in the batch):
• breakpoint? — 'all' (default) | 'desktop' | 'tablet' | 'mobile'.
• states? — array of 'default' | 'hover' | 'focus' | 'active'. Default: all four.
• properties? — array of CSS property names (camelCase) to filter. Returns ONLY those keys (drastic token reduction — "give me color + backgroundColor for the CTAs" = a handful of fields per node instead of 30).
• classes? — true to include all class-state overlays, an array of class names to limit, or undefined to omit.
Returns sparse: missing pseudo-states are omitted, properties absent from a breakpoint cascade from the level above. tablet/mobile carry ONLY the overrides authored at that level — empty objects mean the desktop value passes through unchanged.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
nodeId | string | No | |
nodeIds | array | No | |
breakpoint | string | No | |
states | array | No | |
properties | array | No | |
classes | union | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"nodeId": {
"type": "string"
},
"nodeIds": {
"type": "array",
"items": {
"type": "string"
}
},
"breakpoint": {
"type": "string",
"enum": [
"all",
"desktop",
"tablet",
"mobile"
]
},
"states": {
"type": "array",
"items": {
"type": "string",
"enum": [
"default",
"hover",
"focus",
"active"
]
}
},
"properties": {
"type": "array",
"items": {
"type": "string"
}
},
"classes": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"additionalProperties": false
}rename_node
Rename node (one or many)
Renames a node (cosmetic; affects layers panel and codegen component name).
INPUT SHAPES (pick whichever fits — the model-side framing overhead drops linearly with the number of tool calls):
• \{ nodeId, ...patch \} — one node, one patch.
• \{ nodeIds: [id1, id2, …], ...patch \} — same patch fanned out to many nodes (the 'multi-select' shape — use whenever 3+ nodes get the same change).
• \{ updates: [\{ nodeId, ...patch \}, …] \} — different patches per node, batched in one call (use whenever 3+ nodes get different changes; far cheaper than N separate calls).
Missing nodes are skipped silently. The result is \{ ok: true, applied, skipped \}.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
nodeId | string | No | |
nodeIds | array | No | |
updates | array | No | |
name | string | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"nodeId": {
"type": "string"
},
"nodeIds": {
"type": "array",
"items": {
"type": "string"
}
},
"updates": {
"type": "array",
"items": {
"type": "object",
"properties": {
"nodeId": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"nodeId",
"name"
],
"additionalProperties": false
}
},
"name": {
"type": "string"
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"additionalProperties": false
}replace_text
Bulk find-and-replace on text + button nodes
Substitutes find with replace across text + button nodes in one call. Case-sensitive by default (caseInsensitive: true to relax). Returns the list of changed nodeIds with per-node scope (page:<title> or component:<name>).
Coverage: pages + page freeItems + component masters by default. A match inside a master cascades to every instance on every page — set_component_override per-instance text is NOT touched.
Scope (mutually exclusive, optional):
• nodeIds: [...] — only these nodes.
• rootId — only nodes under this subtree.
• surface? — 'all' (default) | 'pages' (skip masters) | 'components' (only masters). Ignored when nodeIds / rootId is set.
• neither — whole document.
Tip: call search_nodes(\{query: find\}) first to preview which nodes will be touched.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
find | string | Yes | |
replace | string | Yes | |
caseInsensitive | boolean | No | |
nodeIds | array | No | |
rootId | string | No | |
surface | string | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"find": {
"type": "string"
},
"replace": {
"type": "string"
},
"caseInsensitive": {
"type": "boolean"
},
"nodeIds": {
"type": "array",
"items": {
"type": "string"
}
},
"rootId": {
"type": "string"
},
"surface": {
"type": "string",
"enum": [
"all",
"pages",
"components"
]
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"required": [
"find",
"replace"
],
"additionalProperties": false
}search_nodes
Search nodes by text / type / class (grep for the doc)
Find nodes by content + optional filters. Returns short hits ({nodeId, type, name, snippet, matchedField, parentPath, scope}) instead of dumping the tree. Use BEFORE read when you know what you're looking for verbally (e.g. 'where do we say tarifs?', 'find the Buy buttons', 'which images miss alt').
Coverage: by default scans page artboards, page freeItems, AND the components library (every master's root). Hits inside a component master surface with scope:"component:<name>" — editing them via set_text_content cascades to every instance on every page. Per-instance text overrides (set_component_override) are NOT searched.
Args:
• query — substring (case-insensitive) or regex when mode="regex". Optional if you only want filters.
• mode? — 'literal' (default) | 'regex'.
• nodeTypes? — only return matching types (e.g. ['text','button']).
• classContains? — substring match on a node's classStyles keys (e.g. 'btn-primary').
• scope? — 'all' (default) | 'pages' (skip component masters) | 'components' (only masters).
• rootId? — anchor the search at a single subtree (overrides scope).
• limit? — cap, default 50, max 500.
Returns \{ hits, totalReturned, truncated?, hint? \}. Match precedence per node: text|label|placeholder|alt → layer name.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | No | |
mode | string | No | |
nodeTypes | array | No | |
classContains | string | No | |
scope | string | No | |
rootId | string | No | |
limit | number | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"query": {
"type": "string"
},
"mode": {
"type": "string",
"enum": [
"literal",
"regex"
]
},
"nodeTypes": {
"type": "array",
"items": {
"type": "string",
"enum": [
"artboard",
"frame",
"form",
"text",
"image",
"button",
"input",
"rect",
"raw",
"componentInstance",
"repeater"
]
}
},
"classContains": {
"type": "string"
},
"scope": {
"type": "string",
"enum": [
"all",
"pages",
"components"
]
},
"rootId": {
"type": "string"
},
"limit": {
"type": "number"
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"additionalProperties": false
}set_node_field
Set node field
Sets a non-style attribute on a node. Available fields:
• text node: tag — one of p, h1, h2, h3, h4, h5, h6, span, div, blockquote, pre, code, em, strong, small, cite, label, legend, figcaption.
• image node: src, alt.
• input node: inputType (text, email, password, number, tel, url, search, date, time, datetime-local, month, week, color, range, checkbox, radio, file, hidden, textarea), placeholder, fieldName (HTML name, required for forms), value (initial value / textarea content), checked (checkbox/radio), required (boolean), rows (textarea height), visibleIf (fieldName, name=v, name!=v — runtime hides until truthy), helpText (caption under the field), validation ({ min, max, minLength, maxLength, pattern, errorMessage }).
• button node: submitSendingLabel (text the runtime shows while a submit is in flight, e.g. 'Sending…'). Only meaningful when the button is inside a form.
• form node: submitActions (FormSubmitAction[] — see get_guide({topic:'forms'})), successMessage, redirectAfterUrl, redirectNewTab (boolean — open redirect in a new tab), redirectParams (RedirectParam[] — query-string params appended to the redirect URL; each entry is { id, key, source: 'field' | 'literal', fieldName | value }; empty/missing means no params), dismissedConsentWarning (boolean), requireCaptcha (boolean — turn on Cloudflare Turnstile).
• frame node (when child of a form): formStep — 1-indexed step ordinal for multi-step wizards.
• any node: anchor — sets the HTML id attribute so an <a href="#anchor"> can target it (sanitised to [A-Za-z0-9_-]). Pair with scrollLink behaviours or anchor links inside menus.
• artboard: width, height, background. Width/height accept a number (px) or 'fit-content'. ALWAYS set artboard height to 'fit-content' before screenshotting or shipping.
• menu node: menuId, layout (horizontal | vertical).
For form-specific fields, ALWAYS read get_guide({topic:'forms'}) first — submission actions carry a structured payload that breaks subtly when set incorrectly.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
nodeId | string | No | |
nodeIds | array | No | |
updates | array | No | |
field | string | No | |
value | union | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"nodeId": {
"type": "string"
},
"nodeIds": {
"type": "array",
"items": {
"type": "string"
}
},
"updates": {
"type": "array",
"items": {
"type": "object",
"properties": {
"nodeId": {
"type": "string"
},
"field": {
"type": "string",
"enum": [
"tag",
"src",
"alt",
"placeholder",
"inputType",
"fieldName",
"value",
"checked",
"required",
"rows",
"anchor",
"width",
"height",
"background",
"layout",
"submitActions",
"successMessage",
"redirectAfterUrl",
"redirectNewTab",
"redirectParams",
"dismissedConsentWarning",
"requireCaptcha",
"submitSendingLabel",
"visibleIf",
"helpText",
"validation",
"formStep",
"classNames"
]
},
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
},
{
"type": "object",
"additionalProperties": {}
},
{
"type": "array",
"items": {}
}
]
}
},
"required": [
"nodeId",
"field",
"value"
],
"additionalProperties": false
}
},
"field": {
"type": "string",
"enum": [
"tag",
"src",
"alt",
"placeholder",
"inputType",
"fieldName",
"value",
"checked",
"required",
"rows",
"anchor",
"width",
"height",
"background",
"layout",
"submitActions",
"successMessage",
"redirectAfterUrl",
"redirectNewTab",
"redirectParams",
"dismissedConsentWarning",
"requireCaptcha",
"submitSendingLabel",
"visibleIf",
"helpText",
"validation",
"formStep",
"classNames"
]
},
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
},
{
"type": "object",
"additionalProperties": {}
},
{
"type": "array",
"items": {}
}
]
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"additionalProperties": false
}set_text_content
Set text content (one or many)
Sets the text on a text node, or the label on a button node.
INPUT SHAPES (pick whichever fits — the model-side framing overhead drops linearly with the number of tool calls):
• \{ nodeId, ...patch \} — one node, one patch.
• \{ nodeIds: [id1, id2, …], ...patch \} — same patch fanned out to many nodes (the 'multi-select' shape — use whenever 3+ nodes get the same change).
• \{ updates: [\{ nodeId, ...patch \}, …] \} — different patches per node, batched in one call (use whenever 3+ nodes get different changes; far cheaper than N separate calls).
Missing nodes are skipped silently. The result is \{ ok: true, applied, skipped \}.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
nodeId | string | No | |
nodeIds | array | No | |
updates | array | No | |
text | string | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"nodeId": {
"type": "string"
},
"nodeIds": {
"type": "array",
"items": {
"type": "string"
}
},
"updates": {
"type": "array",
"items": {
"type": "object",
"properties": {
"nodeId": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"nodeId",
"text"
],
"additionalProperties": false
}
},
"text": {
"type": "string"
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"additionalProperties": false
}update_styles
Update styles (one node, many nodes, or per-node varying)
Patch CSS styles on a node IN THE MASTER (DESKTOP) TREE. Read first — call read_styles(\{nodeId\}) (or read_styles(\{nodeId, properties:['color','...']\}) for a tight payload) so you know what's already authored before overwriting. Empty string or null removes the property. Keys are camelCase (e.g. backgroundColor, paddingLeft). For colors and fonts, prefer design-token references — \{ color: 'var(--color-primary)', fontFamily: 'var(--font-display)' \} — over literal values, so token edits cascade across the site. Run get_design_system once if you don't know the token ids. RESPONSIVE: this writes to the desktop master and cascades to tablet/mobile. To diverge a single property at a smaller breakpoint (e.g. fontSize:32px on mobile only), use set_breakpoint_override instead — DO NOT call write_html on a tablet/mobile artboard, it duplicates the tree and breaks cascade. STATES: this writes the resting/default state. For hover/focus/active state styling — anything you'd write :hover \{ ... \} for in CSS — use update_state_styles instead; inline styles can't target pseudo-classes.
INPUT SHAPES (pick whichever fits — the model-side framing overhead drops linearly with the number of tool calls):
• \{ nodeId, ...patch \} — one node, one patch.
• \{ nodeIds: [id1, id2, …], ...patch \} — same patch fanned out to many nodes (the 'multi-select' shape — use whenever 3+ nodes get the same change).
• \{ updates: [\{ nodeId, ...patch \}, …] \} — different patches per node, batched in one call (use whenever 3+ nodes get different changes; far cheaper than N separate calls).
Missing nodes are skipped silently. The result is \{ ok: true, applied, skipped \}.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
nodeId | string | No | |
nodeIds | array | No | |
updates | array | No | |
styles | object | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"nodeId": {
"type": "string"
},
"nodeIds": {
"type": "array",
"items": {
"type": "string"
}
},
"updates": {
"type": "array",
"items": {
"type": "object",
"properties": {
"nodeId": {
"type": "string"
},
"styles": {
"type": "object",
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
}
]
}
}
},
"required": [
"nodeId",
"styles"
],
"additionalProperties": false
}
},
"styles": {
"type": "object",
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
}
]
}
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"additionalProperties": false
}wait
Wait (sleep)
Sleep for N seconds and return. Use this AFTER queueing a background generate_image job when you want to give the gateway time to finish before continuing — much more efficient than spamming list_media in a loop, which both wastes calls AND can't deliver the completion notification (the inbox only drains on the NEXT tool response). Typical usage: queue 1–3 images, then wait(\{seconds: 30\}), then proceed; the completion [Background] blocks will appear at the top of the tool response that follows your wait. Max 60 seconds per call — if you need to wait longer, call wait again.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
seconds | number | Yes | |
reason | string | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"seconds": {
"type": "number"
},
"reason": {
"type": "string"
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"required": [
"seconds"
],
"additionalProperties": false
}write_html
Write HTML
Parses HTML+inline-CSS and inserts it as one or more nodes under parentId. The recommended way to compose richer layouts. DESIGN TOKENS — for every color and font in the inline CSS USE TOKENS: write style="color: var(--color-primary); font-family: var(--font-display);" instead of literal hexes. Token edits cascade across the site instantly. Call get_design_system once per session to see which ids exist (defaults: primary/secondary/accent/neutral/surface + display/body). RESPONSIVE — author tablet + mobile divergences inline on the SAME element via these data attributes; write_html will fan them out as breakpoint overrides on the page's tablet / mobile artboards in one pass (no follow-up set_breakpoint_override call needed):
data-tablet="font-size:32px; padding:24px 16px" → tablet style overrides (CSS string)
data-mobile="font-size:24px; padding:16px" → mobile style overrides
data-tablet-text="Compact heading" → swap text at tablet (text nodes only)
data-mobile-text="Mobile copy" → swap text at mobile
data-mobile-src=".../portrait.jpg" data-mobile-alt → swap image src/alt at mobile
VISIBILITY — to hide a node on a breakpoint, fold display: none into the matching data-{bp} CSS string (e.g. data-mobile="display:none"). Bring it back with display: revert (or block/flex/etc.) on the wider breakpoint. The structured hidden flag is reserved for the user-facing right-click 'Hide' menu — agents should not write it.
Only carry the keys that actually diverge — everything else cascades from the wider breakpoint (mobile ← tablet ← desktop). On a PAGE, the page must already have tablet / mobile artboards (call add_artboard_breakpoint first if missing — write_html will surface a responsiveSkipped field listing the breakpoints it couldn't author). On a COMPONENT, no extra setup is needed: the overrides land directly on the component's own breakpointOverrides and fan out to every instance on every page automatically.
REPEATERS — to fan a card out across N collection items, wrap it in <div data-paper-type="repeater" data-collection-id="<id>" data-limit="3" data-sort-field="publishedAt" data-sort-direction="desc">…template…</div>. Inside the wrapper, use \{\{<fieldSlug>\}\} for field values (e.g. \{\{title\}\}, \{\{coverImage\}\}), \{\{@url\}\} for the canonical item URL, \{\{@slug\}\} / \{\{@id\}\} for item meta. The same \{\{token\}\} syntax also works on single-template pages bound via set_page_collection_binding(\{ mode: 'single' \}) — the router resolves the matched item automatically. Read get_guide(\{ topic: 'collections' \}) for the full flow.
FORMS — a literal <form>…</form> in your HTML is recognised by the parser and emitted as a real FormNode (NOT a plain Frame). That's what wires up submission persistence, the GDPR consent check, submitActions (email / webhook / auto-responder), the honeypot, and Turnstile. Every <input> / <textarea> / <select> inside must carry a name (we map it to fieldName); any <button> inside auto-codegens to type="submit". When the form collects identifying data (email / name / phone / address), include a <input type="checkbox" name="consent" required /> paired with a <label> carrying the consent copy — this is non-optional under GDPR. Before authoring the first form in a session, ALWAYS call get_guide(\{ topic: 'forms' \}) for the full consent + submitActions contract.
DISCLOSURES — a literal <details><summary>…</summary>…body…</details> is lowered automatically onto paper3's toggleClass behaviour + classStyles ("is-open") layer: outer Disclosure frame → clickable Summary frame → collapsible Body frame with a max-height transition. Just write the natural HTML — no need to hand-roll behaviors: [\{ kind: 'toggleClass', … \}] and class layers yourself. The <details open> attribute starts the body expanded. Use this for FAQs, accordions, settings sections; if you need a custom open/close animation, edit the Body frame's transition / classStyles afterwards.
LOGOS / ICONS / VECTOR MARKS — DO NOT hand-write <svg> markup for a brand mark, app icon, favicon, section icon, or any decorative vector. Call generate_svg first — Quiver's Arrow models produce production-grade SVG that's compact, on-brand, and ready to embed. Then drop the returned svg string verbatim into the HTML you pass here (re-pointing fill/stroke to design tokens so the mark recolours with the palette). Hand-rolled SVG paths consistently underperform: imprecise geometry, no perceptual balance, missing fills, and they balloon the prompt with hundreds of coordinate tokens. The single exception: trivial geometric primitives (a circle bullet, a checkmark in an existing icon set you're matching) — for anything more, generate it.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
parentId | string | Yes | |
html | string | Yes | |
index | number | No | |
_site | string | No | Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set. |
Input schema
{
"type": "object",
"properties": {
"parentId": {
"type": "string"
},
"html": {
"type": "string"
},
"index": {
"type": "number"
},
"_site": {
"type": "string",
"description": "Optional target site formatted as <workspaceSlug>/<siteSlug>. Required when the workspace has multiple sites and no site header is set.",
"pattern": "^[^/]+/[^/]+$"
}
},
"required": [
"parentId",
"html"
],
"additionalProperties": false
}