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.
| Arg | Type | Required | Description |
|---|---|---|---|
name | string | yes | |
id | string | no | Omit to auto-suffix a unique id derived from the name |
fromScreenId | string | no | Clone an existing screen’s tree |
tree | node | no | Supply 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.
| Arg | Type | Required | Description |
|---|---|---|---|
screenId | string | yes | |
patch | object | yes | { 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.
| Arg | Type | Required | Description |
|---|---|---|---|
screenId | string | yes |
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 }.
| Arg | Type | Required | Description |
|---|---|---|---|
name | string | yes | Max 80 characters |
id | string | no | Derived from name if omitted (auto-suffixed on collision); an explicit id that already exists returns BoardIdConflict |
group | string | no | Sidebar 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 }.
| Arg | Type | Required | Description |
|---|---|---|---|
boardId | string | yes | |
patch | object | yes | { name?, theme?, archived?, group? }, see below |
Patch fields:
name: max 80 characters.themenames a theme (the stem oftheme/<name>.json) the board’s frames render with, the per-board look;nullclears back to the folder default.archived, whentrue, files the board away: hidden from the sidebar, fromlist_boards(unlessincludeArchived), and from a default publish, but kept and still editable.falseunarchives it.groupmoves the board to a sidebar group by existing id or name, or a new name (which creates one);nullfiles 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.
| Arg | Type | Required | Description |
|---|---|---|---|
boardId | string | yes |
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 }.
| Arg | Type | Required | Description |
|---|---|---|---|
order | string[] | yes | Board 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 }.
| Arg | Type | Required | Description |
|---|---|---|---|
boardId | string | yes | |
screenId | string | yes | |
x | number | no | Used only when y is also given; otherwise the frame is placed 80px right of the rightmost existing frame, at y: 0 |
y | number | no | Paired with x |
w | integer | yes | Width in px |
h | integer | yes | Height in px |
label | string | no | |
group | string | no | Frame-region id on the same board |
id | string | no | Defaults 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.
| Arg | Type | Required | Description |
|---|---|---|---|
boardId | string | yes | |
patches | array | yes | [{ 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.
| Arg | Type | Required | Description |
|---|---|---|---|
boardId | string | yes | |
frameId | string | yes |
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 }.
| Arg | Type | Required | Description |
|---|---|---|---|
boardId | string | yes | |
x | number | unless attachment | Board coordinates; place beside frames, not on them |
y | number | unless attachment | |
width | number | no | Pixel width; default 240 |
body | string | yes | Markdown-lite |
attachment | object | no | { 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.
| Arg | Type | Required | Description |
|---|---|---|---|
boardId | string | yes | |
noteId | string | yes | |
patch | object | yes | { x?, y?, width?, body? } |
remove_note
Returns { removedId }.
| Arg | Type | Required | Description |
|---|---|---|---|
boardId | string | yes | |
noteId | string | yes |
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.
| Arg | Type | Required | Description |
|---|---|---|---|
screenId | string | yes | |
path | locator | yes | @id or path array of the target node |
body | string | yes | Markdown |
collapsed | boolean | no |
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 }.
| Arg | Type | Required | Description |
|---|---|---|---|
screenId | string | yes | |
annotationId | string | yes | |
body | string | no | Replacement markdown |
collapsed | boolean or null | no | null clears the override |
remove_annotation
Remove an agent-authored annotation. Refuses user-authored ones. Returns { removedId }; an unknown id returns AnnotationNotFound.
| Arg | Type | Required | Description |
|---|---|---|---|
screenId | string | yes | |
annotationId | string | yes |
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.
| Arg | Type | Required | Description |
|---|---|---|---|
id | string | yes | Component id; shadows a library component with the same id |
importPath | string | yes | Emitted verbatim by codegen; use the alias your app actually uses (@/components/data-table, @acme/charts) |
props | array | yes | Prop schema (shape above) |
category | string | no | "ui" (default) or "typography" |
description | string | no | |
render | string | no | "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 |
app | string | no | Monorepos 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 |
fit | string | no | Live-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.
| Arg | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
patch | object | yes | { 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 }.
| Arg | Type | Required | Description |
|---|---|---|---|
id | string | yes |
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.