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 four tools. Native operations stay registered behind them, so every call is validated against the same schema it would have on the full surface.
read_velloo and call_velloo
Run one native operation. read_velloo takes the operations that only read: the design, a render, a comparison, the code it emits. call_velloo takes every operation that changes the design. Which tool takes an operation follows its readOnlyHint, and read_velloo is annotated read-only itself, so a client can tell a read from a change before the call. In Claude Code, an allow rule for mcp__velloo__read_velloo lets an agent read without asking while every change still asks.
| Arg | Type | Required | Description |
|---|---|---|---|
operation |
string | yes | A native operation name. Each tool’s schema enumerates the operations it takes |
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. An operation sent to the other tool is refused with the call that takes it, such as read_velloo { operation: "get_screen", arguments }. operation_schema can be called through either.
run_velloo_plan
Run up to eight native operations in order, in one round-trip. A plan can mix reads and changes.
| 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> } when a call failed and stopOnError is on (the result is then an error). completed counts the calls that succeeded. 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, with failed listing { index, operation, error } for each call that failed.
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 fails loudly rather than returning a success that did nothing.
The guided façade is strict about meaning, not spelling. An argument name with only one possible reading is taken as meant, and the result says how it was read so the next call uses the documented name: screen for screenId, or type for find_nodes’ ref. A guess between two accepted names is still rejected. A few common shapes are accepted too, such as one arrow sent flat to connect_frames or a single edit sent to update_props as { path, props }. Each operation’s page notes the ones it reads.
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 list_designs runs through read_velloo and switch_design 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
- Once per design that has an app to match, set it up first: import the theme, check the preview entry, run
check_components, and propose the design language. - Discover the provider, theme, existing work, and target screen.
- Read an outline, or the screen as JSX, 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 drops on* handlers and says so. The arrows of a flow are the same: a spec for the agent to implement, with nothing emitted for them. 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.