Velloo documentation
Markdown for LLMs ↗MCP: discovery
Read operations. List and fetch screens, boards and their flows, the component and snippet catalogue, canvas fidelity, the preview entry, the component sweep, the theme, annotations, notes, and comment threads, and query a screen tree with find_nodes.
Discovery operations are reads. With one exception they never mutate the design folder, and they are the recommended first calls of any session: list_components (index mode), get_theme, and list_boards, plus component_status before claiming an app component renders exactly. In a folder whose app has components of its own, preview_status comes first of all.
The exception is set_preview_entry, documented here beside the preview_status check it reruns rather than on the mutations page.
list_screens
List every screen in the design folder.
| Arg | Type | Required | Description |
|---|---|---|---|
includeTree |
boolean | no | Embed each screen’s full tree: one round-trip instead of list_screens plus N get_screen calls. Default false |
Returns { screens: [{ id, name, tree? }] }.
get_screen
Return the JSON for a single screen.
| Arg | Type | Required | Description |
|---|---|---|---|
screenId |
string | yes | |
mode |
string | no | "full" (default) returns the complete screen. "outline" returns { id, name, tree } where each node is stripped to ref (or snippet, or param), $id when present, a classSnippet of its className capped at 40 characters, and children. "jsx" returns the tree as the JSX compose reads |
path |
locator | no | "jsx" only: the subtree to print. Default: the whole tree |
Use mode: "outline" to scan a large screen before drilling in with inspect or @id locators. An unknown id returns ScreenNotFound.
mode: "jsx" is the read half of read, edit, and send back. It returns { screenId, path?, jsx }; edit the JSX and return it with compose in mode: "replace", passing the same path to replace only that subtree. Composing the printed JSX unchanged yields the same nodes.
{ "screenId": "home", "mode": "jsx", "path": "@pricing" }
A run of look-alike siblings prints as one .map over the values that differ, so a list reads as a list and one template edits every item. The fold is checked on every read: where the folded text would not compose back to the same nodes, the tree prints sibling by sibling instead. What has no JSX form (an instance’s $overrides, a node’s $emitAs) is listed in notInJsx; replacing the JSX drops those, so change them with their own operations. A path that resolves to nothing returns InvalidPath.
list_boards
List the folder’s boards in sidebar order.
| Arg | Type | Required | Description |
|---|---|---|---|
includeFrames |
boolean | no | Embed each board’s frames and groups lists in one round-trip. Default false |
includeArchived |
boolean | no | Also list archived boards. Default false; archived boards are hidden |
Returns { boards: [{ id, name, frameCount, group?, archivedAt?, frames?, groups? }] }. group is the name of the sidebar group the board is filed under (absent means ungrouped); archivedAt appears only on archived boards.
get_board
Return one board’s full JSON: frames (placements of screens at chosen sizes), groups, and its flow arrows.
| Arg | Type | Required | Description |
|---|---|---|---|
boardId |
string | yes | |
mode |
string | no | "full" (default) returns the board. "flow" returns the flow spec alone |
A board that has arrows carries them as stored in edges: [{ from, to, label?, state?, labelAt? }] (labelAt is canvas layout: where someone slid the label along its arrow), plus flow: the board read as a flow. A board with no arrows has neither field. mode: "flow" returns the same spec with boardId and name, and without the frames.
{
"steps": [
{
"frame": "signup-1", "screen": "signup", "name": "Sign up", "route": "/signup",
"states": [
{ "state": "loading", "frame": "signup-sending-1", "screen": "signup-sending", "when": "Submit" },
{ "state": "error", "frame": "signup-taken-1", "screen": "signup-taken", "when": "Email already taken", "after": "signup-sending-1" }
]
},
{ "frame": "verify-1", "screen": "verify", "name": "Check your inbox" }
],
"transitions": [{ "from": "signup-sending-1", "to": "verify-1", "when": "Account created" }],
"starts": ["signup-1"],
"ends": ["verify-1"],
"note": "A state is its step in another condition, not another page: build each step as one route and one component that shows its states conditionally. Only a transition changes the route."
}
| Field | Meaning |
|---|---|
steps |
Each step an arrow touches, in board order: its default frame, that frame’s screen, a name (the frame’s label, else the screen’s name), a route when the screen has one, and its states |
steps[].states |
The step’s other frames: { state, frame, screen, when?, after? }. when is the arrow’s label. after names the state’s frame this one follows, when it doesn’t follow the default |
transitions |
Arrows that move on: { from, to, when? }, ends as drawn |
starts |
The steps the flow is entered at. Steps that lead round to each other are read as one loop, so a way back doesn’t hide the start |
ends |
The frames nothing leaves, other than loading and error states. A flow can end at a state, and a frame nobody led on from appears here too |
unconnected |
Frames no arrow touches, unless their screen is already in the flow at another size. Present only when there are some |
gaps |
[{ step, missing, why }]. One rule today: a step with a loading state and no error state. Present only when there are some |
note |
Present when any step has states: a state is its step in another condition, built on the step’s one route and one component. Only a transition changes the route |
Frame ids are what connect_frames takes, and each frame carries the screen that emit_code takes. An unknown id returns BoardNotFound.
list_components
List the tag namespace that compose resolves: the app’s own components, library components, registered extensions, and snippets.
| Arg | Type | Required | Description |
|---|---|---|---|
filter |
string | no | Case-insensitive substring match on ids (for snippets, the PascalCase tag) |
mode |
string | no | "index" (default), "summary", or "full" |
kind |
string | no | "repo", "library", "extension", or "snippet" narrows the result |
unusedOnly |
boolean | no | List only snippets no screen reaches, directly or through another snippet. Implies kind: "snippet" |
The three modes trade size for detail:
indexreturns families on shelves:{ snapshotVersion, groups: [{ group, label, families: [{ id, pieces?, designModeNotes? }] }], totals: { components, families } }. A family’s sub-components are folded intopieces(FieldlistsFieldLabel,FieldDescription,FieldError), and extensions and snippets each get a shelf of their own. Status is reported only as exceptions:unavailableInDesignandnotInstalledInApplist the ids that differ from the norm. Read this first.summary:{ snapshotVersion, components: [...] }, one entry per component withid,category,source,props(names only),designModeNotes,kind,availableInDesign,installedInApp, andimportPathwhere known.full: complete descriptors with prop types, enum values, and a workingexample. A prop that enumerates a huge value set (for exampleIcon.name, roughly 4,000 lucide names) is truncated to 40 values plus anenumValuesTruncatedcount.
Repo entries are your app’s own components, shelved first and grouped by source — Repo · this app's components, Repo · @mantine/core. Each carries importPath, exportName, member for a compound part, extracted props (with an inheritedProps count when some were folded away), acceptsChildren, styleProps, preview states, parts, and provenance naming where the app uses it. When a component reads data for itself, dataSources lists the hooks — props alone won’t fill a component that reads a store. An id that would collide with a provider component is qualified (Mantine.Button), and designModeNotes says why. In index mode a repo note reports each app’s preview-entry state, so check it before trusting the shelves.
The catalog also says what each component is:
- What it is for. The doc comment above a component leads its
designModeNotes: the one line its author wrote about when to use it. - Icons are counted, not listed. An app that uses sixty of a pack’s icons has not chosen sixty components. In
indexmode thereponote carriesicons: [{ source, inUse, examples }]instead of a shelf entry each, and any icon the pack exports can be placed by name.kind: "repo"withmode: "summary", or afilterthat names one, still lists them. An icon or a context provider carriesrole: "icon"orrole: "provider". - Flat-named parts sit with their root. A
PanelBodyexported besidePanelfrom the same module is filed underPanel’sfamily, as a compound part would be. - One module is one component. A component the app imports as both
@/components/paneland./src/components/panelis listed once.
When the app has only pages, a router, or icons on its Repo shelves, the repo note says so, and no preview entry is needed.
Snippet entries use the snippet’s PascalCase tag as id and carry the persisted kebab-case id as snippetId. In full mode their props are the declared params, each with a derived required flag (true when the param has no default and isn’t optional). A snippet nothing reaches carries unused: true, and its designModeNotes say so in every mode.
installedInApp describes the host app only; false never blocks design. There is no install operation: emit_code returns componentsToInstall for the implementation handoff. Snippets have no separate listing either; they are the kind: "snippet" entries here, and get_snippet returns one definition.
component_status
Compile-check components for the browser canvas and report how each one actually renders, so a fidelity claim rests on a check rather than an assumption. Pass screen or ids, or neither for every component the app gives the design: the set check_components mounts.
| Arg | Type | Required | Description |
|---|---|---|---|
screen |
string | no | Screen id: check the components that screen uses and whether it mounts |
ids |
string[] | no | Component ids, as list_components returns them. Repo catalog ids are accepted |
library |
string | no | With ids: the library to check. Default: the folder’s default library. A screen names its own |
Each diagnostic carries the component id, a status, usually a note, and — when the status isn’t exact — a code and a remedy:
| Status | Meaning |
|---|---|
exact |
Rendered from the app’s own source |
adapted |
Rendered with a named canvas-safe adaptation |
unstyled |
Rendered, but its stylesheet wasn’t loaded, so it can’t be reported exact |
proxy |
Couldn’t render; the snippet named in $repo.proxy stands in |
fallback |
Drawn from its server render inside the mount instead of the browser bundle. code says why: extension (one of the folder’s extensions) or static-fallback (its source didn’t compile) |
unavailable |
Couldn’t render and has no proxy; a labelled frame stands in |
server-rendered |
The adapter has no browser bundle, so the server render is what the screen shows (antd, Chakra UI, HTML) |
unchecked |
Nothing here could establish a verdict: no bundler, or the library’s manifest failed to load. Don’t assume a fidelity |
unknown |
Not a component this library or catalog has |
An exact or adapted that no mounted frame has confirmed carries observed: false, and its note says it is a build check only: the source compiles and exports the component. Once a frame mounts, its runtime verdict replaces the build’s.
The codes are stable: missing-provider, render-threw, missing-export, compile-failed, server-only, resolve-failed, unstyled. missing-provider almost always means the preview entry needs another wrapper.
With screen, returns { library, screen, mounted, note?, diagnostics, errors }. Mounting is per component:
- Library components the bundle can’t compile are drawn from their own server render inside the mount (
fallback), so they cost their neighbours nothing. Only a screen where nothing would mount reportsmounted: false. - Repository components each fall back alone to their proxy snippet or a labelled frame, leaving the rest real. The report also merges what the mounted frame found at runtime (
render-threw,missing-provider,unstyled,missing-export) and setsruntimeChecked.
With ids, returns { library, usable, diagnostics, errors }. An id neither the library nor the repo catalog has is reported with status: "unknown" rather than as a canvas failure.
Where the server render is the whole answer (an adapter with no browser bundle, or a screen nothing on which mounts), the result also carries renderSource (bundled-library or velloo-primitives) and hostMount. hostMount.supported says whether the adapter can client-mount its own components at all; hostMount.mounted, on screen answers, whether this screen does. An adapter with a bundle answers per component instead.
Passing neither argument returns BadRequest; an unknown screen returns ScreenNotFound.
preview_status
Check the preview entry your app’s own components render inside on the canvas: the providers and global stylesheets they need. It proves the answer by mounting a real component in a headless browser rather than inspecting the file. Run it once per folder before the first screen.
| Arg | Type | Required | Description |
|---|---|---|---|
app |
string | no | config.hostApps key; the default app when omitted |
component |
string | no | Catalog id to probe. Default: the first one the app uses, preferring one with recorded props |
probe |
boolean | no | Mount a component to verify. Default true |
Returns { state, preview, recipes, appWrappers, appStylesheets, unloadedStylesheets?, components, probe?, suggestedPreviewEntry?, warnings?, next }.
stateisabsent(no entry and components need one),valid(a component mounted cleanly),incomplete(it mounts, but the entry never imports one of the app’s global stylesheets, listed inunloadedStylesheets, so the app’s own classes render as nothing), orfailing(the probe found a problem).previewdescribes what’s in force:kindisfile(yourpreview.tsx, with itspath),recipe(a built-in wrapper for a known library), ornone.appWrappersandappStylesheetsare what the app’s own entry point uses — the raw material for writing an entry.codePropsflags a wrapper prop that was an expression rather than a literal.probereports the mount:component,mounted,status,code,note,remedy, pluspreviewErrorwhen the entry itself threw and up to fiveconsoleErrors. Aninconclusivenote means the component threw without props the app normally supplies as code; the prior state is retained. Probe another component before treating the entry as verified or broken.suggestedPreviewEntryis a draft to adapt, present whenever the current entry isn’t a verified file.nextis the concrete step to take from this state.
set_preview_entry
Write the design folder’s preview entry and verify it by mounting a real component. Returns the same report as preview_status.
| Arg | Type | Required | Description |
|---|---|---|---|
source |
string | no | TSX/JSX module source, up to 100,000 bytes. Omitted, the suggested entry is written |
app |
string | no | config.hostApps key. Writes preview.<app>.tsx; omitted writes preview.tsx |
With no source, set_preview_entry {} writes the entry preview_status suggests: the app’s own entry rewritten for a canvas, with its stylesheets, its providers in their nesting order, an in-memory router in place of the real one, a query client of its own, and auth providers left out. For most apps that is the answer. When a component still needs something, such as a provider a layout adds or seeded data, read preview.tsx, extend it, and send it back as source. With no app to suggest from, omitting source returns BadRequest.
The module’s default export receives { children, colorScheme, theme, recipeTheme } and returns the providers and fixtures your components need, with the app’s global stylesheets imported at the top. Source without a default export is rejected with BadRequest, as is an unknown app.
Keep the entry free of network calls and credentials: it runs in your local browser during design, and it’s a fixture rather than a bootstrap of the real app.
check_components
Mount every component the app gives the design, at the props its call sites pass, and report which can be trusted. This is the sweep that setup runs. It covers the app’s own components and, where the library is the app’s own files (a shadcn components/ui), the library components those files back. A library file that exports only the app’s own renamed component (a badge.tsx with StatusChip and no Badge) backs no library component; its exports are checked as the app’s own. Run it once the theme is imported and preview_status says valid, and again after changing either.
| Arg | Type | Required | Description |
|---|---|---|---|
app |
string | no | config.hostApps key; the default app when omitted |
ids |
string[] | no | Component ids to check. Default: all of them, the app’s own files first |
dark |
boolean | no | Render the dark theme. Default light |
image |
boolean | no | Return the contact sheets. Default true |
Returns { mounted, checked, ok, problems, inconclusive?, notChecked?, partsNote?, sheets, next }, plus one contact-sheet image per render: twenty components to a sheet, up to 120 components a call. Pass ids to check the rest, or to see a few at full size.
-
oklists the ids that render from the app’s source. -
problemsis[{ id, status, code?, note?, remedy?, flags?, detail?, size? }]. An entry with a status such asunavailable,fallback, orunstyledcarries the mount’s owncodeandremedy, ascomponent_statusreports them. An entry withflagsrendered, but not as itself:Flag Meaning blankIt drew nothing. A dialog or drawer that is closed until opened does that by design thinIt drew a sliver variantsIdenticalTwo values of a variant prop drew the same thing -
inconclusivewent without props it needs: code its call site passes (rows={data}), or a required prop no call site gave as data. It says nothing about the preview entry. -
mountedis false, with anote, when a sheet’s components never mounted. Fix the preview entry first. -
notCheckedlists the ids past the per-call limit, andpartsNotecounts parts of other components that were not mounted alone. -
nextis the step to take from this result.
A component with a variant prop is mounted twice, differing in that prop alone. variantsIdentical on several components at once is usually one cause: a stylesheet the preview entry never loaded, or a colour the app’s Tailwind config defines and the theme lacks. Fix the cause, then re-run with ids for the ones that were wrong.
The report judges what was mounted and measured, not whether it is legible. Look at the sheets as well. An app with no components of its own returns checked: 0 and a note saying so. An unknown app or id returns BadRequest.
get_snippet
Return the full JSON for a single snippet: id, name, params (each with the derived required flag), and the body tree.
| Arg | Type | Required | Description |
|---|---|---|---|
snippetId |
string | yes | The kebab-case snippet id |
An unknown id returns SnippetNotFound.
get_theme
Return a theme’s token tree.
| Arg | Type | Required | Description |
|---|---|---|---|
theme |
string | no | Named theme to read. Default "default" |
Returns the theme’s tokens plus two derived fields: typeScale, what the default typeset computes to per type role, and customCss, the folder’s custom CSS. When a design-system document is followed, the result also includes designSystem: { path, note }. Read that file’s current prose before composing or reviewing; it carries rules tokens cannot express. Edit through the theme operations, not per-node sizes.
list_annotations
List annotations on a screen: designer-authored guidance plus any the agent pinned.
| Arg | Type | Required | Description |
|---|---|---|---|
screenId |
string | no | Omitted, every screen’s annotations are listed, each with its screenId |
Returns { annotations }. Each carries an author, a target: { locator }, a markdown body, and a resolved path, null when the targeted node has since been removed (treat dangling annotations as low-priority; the note is stale). User-authored annotations are read-only to the agent. Agent annotations are written with the operations on the Lifecycle page.
list_notes
List the markdown notes on one board, or on every board.
| Arg | Type | Required | Description |
|---|---|---|---|
boardId |
string | no | Omitted, every board’s notes are listed, each with its boardId |
Returns { notes }. Free notes live in board coordinates (the same space as frame x/y); an attached note carries attachment: { frameId, screenId, locator } naming the node it anchors to. Writable via add_note / update_note / remove_note on the Lifecycle page.
list_comment_threads
List visual feedback threads. Every open thread is a change the user expects.
| Arg | Type | Required | Description |
|---|---|---|---|
boardId |
string | no | Narrow to one board. Default: every board in the folder |
status |
string | no | "open" (default), "resolved", or "all" |
scope |
string | no | "local", "shared" (threads left on a published link), or "all" (default) |
Returns { threads }. A thread’s anchor reports its attached path, or that it is stale when the original node is gone.
get_comment_thread
Read one complete thread, including its anchor and replies.
| Arg | Type | Required | Description |
|---|---|---|---|
threadId |
string (UUID) | yes |
Returns { thread }.
update_comment_thread
Act on a thread. Pass reply, status, or both; replying with what changed and resolving in one call is the normal close-out.
| Arg | Type | Required | Description |
|---|---|---|---|
threadId |
string (UUID) | yes | |
reply |
string | no | Agent message to post, 1–4000 characters. Posted before any status change |
status |
string | no | "resolved", "open", or "deleted". Deletion is permanent; only on the user’s explicit request |
Returns { thread }. Passing neither reply nor status returns BadRequest; comment-store failures return CommentStore with a code. The full loop is in the velloo://guide/comments resource.
find_nodes
Query a screen tree for matching nodes. All filters AND together. Accepts snippet:<id> to search a snippet body.
| Arg | Type | Required | Description |
|---|---|---|---|
screenId |
string | yes | |
ref |
string | no | Exact component $ref, e.g. "Button" |
snippetId |
string | no | Exact $snippet id for snippet instances |
id |
string | no | Exact $id anchor |
classContains |
string | no | Substring of props.className |
text |
string | no | Case-insensitive substring of the node’s text |
prop |
string | no | Prop key that must be present |
propValue |
any | no | With prop: strict-equal value match |
limit |
number | no | Max matches; default 50 |
{ "screenId": "home", "ref": "Icon", "prop": "name", "propValue": "Github" }
Returns { matches: [{ path, kind, ref?, id?, className?, textPreview?, childCount }], total }. kind is component, snippet, or param, and total counts matches before limit. Use it to locate targets for the path-accepting operations without fetching and walking the whole tree.
On the guided façade, type, component, or tag is read as ref, and the result says so.
Assets and captures
list_assets lists the files in the folder’s assets/ directory, and list_captures / get_capture read page captures recorded by start_capture_session. Both families are documented with the rest of the visual tooling on Visual, assets, and batch.