Velloo documentation
Markdown for LLMs ↗MCP overview
How the guided and full Velloo MCP surfaces expose the same design operations, how nodes are addressed, and a map to the deeper native-operation reference.
MCP connects an AI coding agent to the same local state as the browser canvas. Reads, mutations, captures, annotations, and code emission become visible in one shared design session.
Two surfaces, one operation catalogue
Velloo registers one catalogue of native operations: list_components, compose, update_props, screenshot, emit_code, and the rest. The surface decides how that catalogue reaches the client.
velloo mcp # guided (default)
velloo mcp --surface full # advertise every native operation as its own tool
VELLOO_MCP_SURFACE=full is the environment-variable equivalent; the --surface flag wins when both are set. An unknown surface name fails at startup. The surface changes how schemas enter the client’s context, not what Velloo can do.
The guided façade
The guided surface advertises exactly three tools. Native operations stay registered behind them, so every call is validated against the same schema it would have on the full surface.
call_velloo
Run one native operation.
| Arg | Type | Required | Description |
|---|---|---|---|
operation | string | yes | A native operation name. The schema enumerates every operation available to the session |
arguments | object | yes | The native operation’s arguments, exactly as that operation declares them |
{ "operation": "get_screen", "arguments": { "screenId": "home", "mode": "outline" } }
The result is the native operation’s own result.
run_velloo_plan
Run up to eight native operations in order, in one round-trip.
| Arg | Type | Required | Description |
|---|---|---|---|
calls | array | yes | 1–8 entries of { operation, arguments } |
stopOnError | boolean | no | Stop at the first failed call. Default true |
{
"calls": [
{ "operation": "list_boards", "arguments": {} },
{ "operation": "get_screen", "arguments": { "screenId": "home", "mode": "outline" } }
]
}
The result opens with a summary: { "kind": "PlanCompleted", "completed": <n> }, or { "kind": "PlanFailed", "failedAt": <index>, "completed": <index + 1> } when a call failed and stopOnError is on (the result is then an error). Each call follows as a header { index, operation, isError } and that call’s own result content. With stopOnError: false every call runs and the summary is PlanCompleted; check each header’s isError.
A plan is sequential, not atomic: calls that succeeded before a failure stay written. For all-or-nothing mutation groups, use batch.
operation_schema
Return the exact contract for one native operation.
| Arg | Type | Required | Description |
|---|---|---|---|
operation | string | yes | A native operation name |
Returns { operation, description, inputSchema, annotations }. inputSchema is JSON Schema for the arguments, and annotations carries the MCP read-only and destructive hints.
Failed calls carry their schema
When a façade call fails, the result includes the fix. Arguments that don’t validate return { kind: "InvalidOperationArguments", operation, issues, description, inputSchema, annotations }. A call that validates but fails inside the operation returns the operation’s own error, followed by an OperationSchemaHelp entry with the same description, inputSchema, and annotations. An operation name that doesn’t exist returns UnknownOperation.
Strict arguments
Every operation’s top-level arguments object is strict: a key the operation doesn’t declare is rejected with the valid keys, never silently dropped. A misspelled includeTree, or a top-level path on update_props (which takes patches), fails loudly rather than returning a success that did nothing.
Multiple designs
When a checkout has several designs, the session instructions name the current one. Two native operations manage that context:
| Operation | Arguments | Behavior |
|---|---|---|
list_designs | none | Lists each design’s name and path, marks the current design, and summarizes its library, boards, and screens |
switch_design | name | Switches a stdio session to another design without restarting it |
switch_design is not available over HTTP and cannot run inside run_velloo_plan; reconnect with velloo mcp --http <name> instead. If startup resolution is ambiguous, Velloo opens the first design by name and tells the agent to confirm before it mutates anything. On the guided surface these operations run through call_velloo; the full surface advertises them directly.
Path addressing
Operations that act on one node take a locator in path, parentPath, fromPath, or toParent:
@idreference:"@hero-cta". Resolves to the node whose$idishero-cta. It must match^@[a-zA-Z][a-zA-Z0-9_-]*$. Ids survive sibling inserts, moves, and deletes, so prefer them for anything you will revisit.- Index path:
[1, 2]is the third child of the second child of the root;[]is the root itself. Index paths go stale when siblings change; re-locate withfind_nodes, which returns current paths.
Give a node an id when you create it (the vellooId attribute in compose JSX) or retrofit one with set_node_id. Ids are unique per screen.
Snippet instances are opaque to screen locators: @id resolution never descends into a snippet body, and an index path cannot walk through an instance. To reach a node inside one instance’s body, pass the instance’s locator plus an innerPath to update_snippet_instance or inspect; innerPath is "@id" (preferred), a dotted index path such as "0.2", or "" for the body root. For every instance at once, use update_snippet’s innerPatch.
An unresolved @id returns IdNotFound; an out-of-range index path returns InvalidPath.
Editing a snippet body
A snippet body can be edited like a screen tree by passing the virtual screen id snippet:<snippetId>. The write is persisted to the snippet, and every screen that uses it re-renders.
These operations accept snippet:<id>: update_props, move_node, remove_node, set_node_id, update_snippet_instance (for a snippet nested in the body), and find_nodes.
compose does not: it resolves real screens only and returns ScreenNotFound for a snippet: id. To add structure to a body, or to insert $param and $if nodes, send a new body through update_snippet’s tree. To patch one body node’s props without resending the tree, use update_snippet’s innerPatch. Full reference: Snippets.
Advisory prop warnings
Some successful writes return a propWarnings array of strings next to the normal result. Warnings never fail or roll back the write; they point at likely mistakes so the agent can correct them in the next call:
update_propsandcomposecheck component props against the component’s descriptor: a prop name the component doesn’t declare (with the closest known name), a value outside the prop’s enum, a boolean or number prop given another type, or anIconname that matches no lucide icon.add_screenruns the same checks over the tree it created.add_snippetandupdate_snippetflag anIconwhosenameis fed by a$paramor$if:emit_codelowers a dynamic icon name to one static glyph, so a per-instance icon should be anodeparam.update_snippetalso flags existing instances stranded by aparamschange (a now-missing required arg or a now-unknown one), naming the screen and path to fix withupdate_snippet_instance.
Design-quality checks (invalid classes, undefined CSS variables, colors that won’t theme-flip, components that threw while rendering) arrive separately as diagnostics.
Operation families
A safe working sequence
- Discover the provider, theme, existing work, and target screen.
- Read an outline before requesting a full tree.
- Compose coherent subtrees; give nodes you plan to revisit a
vellooId. - Read
propWarningsanddiagnosticson each result and correct them immediately. - Verify through the renderer and annotations.
- Emit only after the user chooses a direction.
run_velloo_plan is useful when those steps contain a known sequence of operations. Keep plans understandable and use the smallest relevant reads; a canvas session is easier to review when each change still has a clear reason.
Static by construction
Velloo represents interface composition and visual states. Click handlers, navigation, data loading, and form submission do not become application behavior inside the canvas; compose rejects on* handlers outright. The agent implements those concerns in the host app after design review. Live extensions can render selected host components for fidelity, but remain part of a design preview.