Skip to content

Velloo documentation

Markdown for LLMs ↗

MCP: lifecycle

Create, update, and remove screens, boards, frames, board notes, annotations, and extensions.

Lifecycle operations manage the resources a design folder is made of: screens, boards, frames, notes, annotations, and extensions. On the default guided surface, invoke an operation through call_velloo after checking its operation_schema; on the full surface the native names below are advertised directly. Every operation’s arguments are a strict object on either surface: an argument name the schema doesn’t declare is rejected with the valid keys rather than silently dropped.

Screens

add_screen

Create a new screen. Empty by default. Does not place the screen on a board; that’s a separate, intentional add_frame step.

ArgTypeRequiredDescription
namestringyes
idstringnoOmit to auto-suffix a unique id derived from the name
fromScreenIdstringnoClone an existing screen’s tree
treenodenoSupply a tree explicitly

A route-scan init already scaffolds one placeholder screen per detected route (id = route slug). Don’t add_screen for those; it returns ScreenIdConflict with a hint. Rebuild the existing screen instead with compose mode "replace".

update_screen

Sparse metadata patch: only name is patchable today; the screen id stays stable.

ArgTypeRequiredDescription
screenIdstringyes
patchobjectyes{ name? }

remove_screen

Delete a screen from disk. Refuses to remove the last screen (LastScreen). Any frames referencing the screen on any board are removed alongside it; the result is { removedScreenId, removedFrames: [{ boardId, frameIds[] }] }. Undoable from the canvas.

ArgTypeRequiredDescription
screenIdstringyes

Boards

Boards are the canvases of a design folder; one folder has many. Each board owns its own frames and persists as boards/<id>.json. A folder with zero boards is a supported state.

add_board

Create a new empty board. Returns { boardId, board }.

ArgTypeRequiredDescription
namestringyesMax 80 characters
idstringnoDerived from name if omitted (auto-suffixed on collision); an explicit id that already exists returns BoardIdConflict
groupstringnoSidebar group to file the board under: an existing group’s id or name (matched case-insensitively), or a new name, which creates the group

update_board

Sparse patch on a board’s metadata. Returns { board }.

ArgTypeRequiredDescription
boardIdstringyes
patchobjectyes{ name?, theme?, archived?, group? }, see below

Patch fields:

  • name: max 80 characters.
  • theme names a theme (the stem of theme/<name>.json) the board’s frames render with, the per-board look; null clears back to the folder default.
  • archived, when true, files the board away: hidden from the sidebar, from list_boards (unless includeArchived), and from a default publish, but kept and still editable. false unarchives it.
  • group moves the board to a sidebar group by existing id or name, or a new name (which creates one); null files it back under Ungrouped.
{ "boardId": "explorations", "patch": { "archived": true } }

remove_board

Delete a board and its notes. There is no last-board rule; removing the only board is allowed.

The delete cascades. Screens that no other board places are deleted with the board; screens also framed on another board are kept. Snippets that only those deleted screens reached are deleted too; a snippet that was already unreferenced before the call is left alone. The result reports all of it: { removedBoardId, removedScreenIds, removedSnippetIds }.

This is permanent. To put a board away reversibly, use update_board with patch: { archived: true } instead.

ArgTypeRequiredDescription
boardIdstringyes

reorder_boards

Set the left-sidebar display order of boards. Persists to config.boardOrder; the canvas drag-and-drop calls this too. Returns the persisted { order }.

ArgTypeRequiredDescription
orderstring[]yesBoard ids in the desired order. Unknown ids are ignored; omitted boards keep their current slots

Frames

Frames are placements of screens on a chosen board. Multiple frames of the same screen always render the same tree at different sizes; that is the sync model. Different layouts per breakpoint are separate screens.

A frame’s group is the id of a frame region on its board. Regions themselves are created, renamed, and recolored on the canvas; there is no MCP operation for them. Agents only set or clear a frame’s group through add_frame and update_frame. (This is unrelated to a board’s sidebar group, set via add_board / update_board.)

add_frame

Place a screen on a specific board at a chosen size and position. Returns { frame }.

ArgTypeRequiredDescription
boardIdstringyes
screenIdstringyes
xnumbernoUsed only when y is also given; otherwise the frame is placed 80px right of the rightmost existing frame, at y: 0
ynumbernoPaired with x
wintegeryesWidth in px
hintegeryesHeight in px
labelstringno
groupstringnoFrame-region id on the same board
idstringnoDefaults to <screenId>-<n>; an existing id returns FrameIdConflict

update_frame

Move, resize, relabel, regroup, or pin the color scheme of frames on one board. One entry per frame in patches, applied in one atomic write (single persist, broadcast, and undo entry); length 1 for a single frame. Returns { frames }, the board’s full frame list. An unknown frame id fails the whole call with FrameNotFound.

ArgTypeRequiredDescription
boardIdstringyes
patchesarrayyes[{ frameId, patch }], at least one entry

Each patch is { x?, y?, w?, h?, label?, group?, scheme? }. label, group, and scheme accept null to clear; an omitted field is unchanged. scheme is "light" or "dark" and pins that frame’s render mode, a review affordance over the screen’s one shared tree, not a separate design variant. screenshot follows the pin when you omit mode.

{
  "boardId": "main",
  "patches": [
    { "frameId": "pricing-1", "patch": { "h": 2400 } },
    { "frameId": "pricing-2", "patch": { "scheme": "dark", "label": "Pricing · dark" } }
  ]
}

remove_frame

Remove a frame placement from a board. The underlying screen is untouched.

ArgTypeRequiredDescription
boardIdstringyes
frameIdstringyes

Canvas notes

Board-level sticky notes. A note is either free-floating in board coordinates (the same space as frame x/y) or attached to a node inside a frame, in which case the canvas anchors it to that node with a connector and auto-places it beside the frame until someone drags it. Use them for guidance that belongs next to frames: tour steps, review remarks, handoff context. list_notes is on the discovery page.

add_note

Returns { note }.

ArgTypeRequiredDescription
boardIdstringyes
xnumberunless attachmentBoard coordinates; place beside frames, not on them
ynumberunless attachment
widthnumbernoPixel width; default 240
bodystringyesMarkdown-lite
attachmentobjectno{ frameId, screenId, locator }: anchor the note to a node instead of free board space

A note with neither attachment nor both x and y is rejected. For an attachment, frameId must be a frame on this board, that frame must show screenId (otherwise InvalidPath), and locator (@id or a path array) must resolve in that screen.

{
  "boardId": "main",
  "body": "Swap this for the real pricing table before handoff.",
  "attachment": { "frameId": "pricing-1", "screenId": "pricing", "locator": "@plans" }
}

update_note

Returns { note }; an unknown noteId returns CanvasNoteNotFound.

ArgTypeRequiredDescription
boardIdstringyes
noteIdstringyes
patchobjectyes{ x?, y?, width?, body? }

remove_note

Returns { removedId }.

ArgTypeRequiredDescription
boardIdstringyes
noteIdstringyes

Annotations

Annotations are markdown pinned to a specific node; the canvas draws a connector to that node in every frame showing it. Authorship matters: designer-authored annotations are the designer’s channel to the agent: read them via list_annotations, act on them, never edit or delete them. Agent-authored annotations (questions for the designer, review remarks) are created, edited, and removed with the three operations below.

add_annotation

Pin agent-authored markdown to a node. Created with author: "agent" and position: "auto". Returns { annotation }. A node holds one annotation; pinning a second to the same node returns AnnotationConflict with the existingId.

ArgTypeRequiredDescription
screenIdstringyes
pathlocatoryes@id or path array of the target node
bodystringyesMarkdown
collapsedbooleanno

update_annotation

Edit one of your own annotations. Pass body, collapsed, or both; neither is a BadRequest. Refuses user-authored annotations; answer those with your own annotation instead. Returns { annotation }.

ArgTypeRequiredDescription
screenIdstringyes
annotationIdstringyes
bodystringnoReplacement markdown
collapsedboolean or nullnonull clears the override

remove_annotation

Remove an agent-authored annotation. Refuses user-authored ones. Returns { removedId }; an unknown id returns AnnotationNotFound.

ArgTypeRequiredDescription
screenIdstringyes
annotationIdstringyes

Extensions

Extensions declare user-owned custom components (a bespoke DataTable, a brand Hero, a real chart) that aren’t part of any library’s registry. The canvas renders them as labelled placeholders (or client-mounts the real component with render: "live"); emit_code emits a real import from importPath. Concepts: Extensions.

Each prop entry is { name, type, optional, control, defaultValue?, enumValues? } where optional is a boolean, defaultValue a string, and control one of boolean, number, string, color, enum, icon. control: "enum" requires a non-empty enumValues. A malformed entry returns InvalidExtensionProp naming the prop.

add_extension

Register a custom component so it can appear in screen trees. Returns { id, extension }, plus shadowedLibraryComponent when the id matches a library component and liveResolveWarning when a render: "live" import path can’t be resolved from the host app (the canvas shows the placeholder until it’s fixed). An id that is already registered returns ExtensionIdConflict.

ArgTypeRequiredDescription
idstringyesComponent id; shadows a library component with the same id
importPathstringyesEmitted verbatim by codegen; use the alias your app actually uses (@/components/data-table, @acme/charts)
propsarrayyesProp schema (shape above)
categorystringno"ui" (default) or "typography"
descriptionstringno
renderstringno"static" (default, placeholder card) or "live": bundle the real component from the host app and client-mount it in the canvas; falls back to the placeholder on bundle/render failure
appstringnoMonorepos only: which host app the component lives in, a key of config.hostApps (e.g. "web", "admin"). The live island bundles from that app’s root and node_modules. Omit for the default host app; an unknown key is a BadRequest listing the known ones
fitstringnoLive-island sizing: "aspect-video" (default, locks a 16:9 box) or "content" (a fixed-height chart or an absolute-inset overlay drives its own height)
{
  "id": "RevenueChart",
  "importPath": "@/components/revenue-chart",
  "props": [
    { "name": "range", "type": "\"7d\" | \"30d\"", "optional": true, "control": "enum", "enumValues": ["7d", "30d"] }
  ],
  "render": "live",
  "fit": "content"
}

Don’t use extensions for compositions of existing components (use snippets) or for variations of an existing component (use props or class overrides).

update_extension

Patch an existing extension: add or remove props, change the importPath after a refactor, switch render or fit, move it to another host app. Sparse: unlisted fields keep their values. To rename, remove_extension then add_extension; renaming would break tree references. Returns { id, extension } (plus liveResolveWarning when applicable); an unknown id returns ExtensionNotFound.

ArgTypeRequiredDescription
idstringyes
patchobjectyes{ importPath?, props?, category?, description?, render?, app?, fit? }, same value rules as add_extension; props replaces the whole prop list

remove_extension

Remove an extension. Refuses with ExtensionInUse while any screen or snippet tree still references it; the error carries the offending references: [{ screenId, path }] so the agent can remove or replace them first. Returns { id }.

ArgTypeRequiredDescription
idstringyes

Folder format upgrade

upgrade_design_folder

Not part of the normal catalogue. When velloo mcp (stdio) starts against a design folder whose on-disk format is older than the installed velloo, the design tools can’t load. Instead of exiting, the server serves a minimal session: its instructions explain the mismatch, and upgrade_design_folder is the only tool. It takes no arguments and runs the same in-place migration as velloo upgrade <design>, returning the applied steps and rewritten files. Reconnect the MCP server afterwards to load the design tools.

A folder written by a newer velloo can’t be migrated backwards, so that session carries only the notice: update the velloo CLI, then reconnect.