Skip to content

Velloo documentation

Markdown for LLMs ↗

MCP: snippets

add_snippet, update_snippet, remove_snippet, and update_snippet_instance, plus typed params, $param and $if control forms, placing snippets by tag with compose, per-instance overrides, and body editing.

Snippets are named reusable subtrees with typed parameters. A snippet lives in snippets/<id>.json; a screen holds a $snippet instance node that passes args for the declared params. Agents place instances by the snippet’s PascalCase tag in compose JSX. Concepts: Snippets.

Params and control forms

params is an array of { name, type, default?, optional?, enum?, min?, max?, step?, description? }. A param with no default and no optional: true is required; list_components (full mode) and get_snippet surface this as a derived required flag. An optional param may be omitted with no default: it resolves to nothing, so an omitted node slot renders and emits nothing and an omitted prop is left off.

TypeMeaning
stringText input
numberNumeric input; honors min / max / step in the inspector
booleanCheckbox; pair with $if for branching
iconLucide icon picker (string-valued). emit_code bakes one glyph into the component, so an icon that varies per instance belongs in a node param
colorColor swatch (string-valued)
enumSelect from the enum array, which must be non-empty
nodeSubtree slot: one node, or an array rendered as siblings, passed by the instance; useful for icon blocks, overlays, slot composition

A default must match the declared type (and, for enum, be one of the values); a mismatch is rejected when the snippet is written.

Inside the body, two control forms consume params:

  • $param placeholder: { "$param": "name" } substitutes its argument value at render time. Placement depends on the param type: a node param fills a child slot (put the placeholder directly in a children array); a scalar param fills a prop value (e.g. { "$ref": "Heading", "props": { "children": { "$param": "title" } } }). A scalar $param placed directly in a children array renders as nothing; that’s an authoring error.
  • $if branch: { "$if": "name", "then": …, "else": … } picks a branch by the truthiness of the named arg; add "eq": value to branch on strict equality instead. Declare truthiness params as boolean; an empty string is falsy and the string "false" is truthy.

add_snippet

Create a reusable subtree.

ArgTypeRequiredDescription
namestringyes
idstringnoDefault: derived from name
paramsarraynoTyped param declarations; default []
treenodeyesBody; may contain $param placeholders and $if branches

Returns { snippetId, snippet }, plus propWarnings and diagnostics when there is something to fix. An id already in use returns SnippetIdConflict. A body that references itself, directly or transitively, returns SnippetCycle.

Always call render_snippet after add_snippet: $param wiring bugs and $if truthy-coercion mistakes are silent at definition time and only surface at instantiation.

update_snippet

Update a snippet’s metadata or body. Sparse patch: pass only what changes. The id stays stable, and every screen using the snippet re-renders.

ArgTypeRequiredDescription
snippetIdstringyes
patchobjectyes{ name?, params?, tree?, innerPatch? }

innerPatch is { innerPath, propPatch }: patch one body node’s props in place without resending the whole tree. innerPath is "@id" (preferred), a dotted index path such as "0.2", or "" for the body root; propPatch keys merge and null removes a key. A path that doesn’t resolve to a component in the body returns InvalidPath.

It is the definition-level member of the prop-patch trio:

OperationScope
update_propsA plain screen node
update_snippet_instanceOne instance’s args, extra classes, or body node
update_snippet with innerPatchThe shared definition: every instance at once

Pass tree only for a full body replacement; a replacement that would create a cycle returns SnippetCycle. Returns { snippet }. When params changes, propWarnings name any existing instance the change strands (a now-missing required arg or a now-unknown one) so it can be fixed with update_snippet_instance.

remove_snippet

Delete a snippet.

ArgTypeRequiredDescription
snippetIdstringyes

Returns { removedId }. Refuses with SnippetInUse while anything still instantiates it. The error payload splits the referencers so the agent can clean up first: screenIds lists screens holding an instance (clear them with remove_node), and snippetIds lists other snippets whose body embeds this one (clear them with update_snippet).

Placing a snippet

There is no separate instantiate operation. Place an instance with compose using the tag list_components reports for the snippet, its id or name in PascalCase (nav-item becomes <NavItem />):

{
  "screenId": "dashboard",
  "mode": "append",
  "parentPath": "@sidebar",
  "jsx": "<NavItem vellooId=\"nav-settings\" label=\"Settings\" icon=\"Settings\" className=\"mt-auto\" />"
}
  • Attributes are args. Each must name a declared param, and every required param must be present; otherwise the call returns BadRequest with the offending line and column.
  • className, unless the snippet declares a className param, becomes the instance’s extra classes, appended to the body root.
  • Text children, or one element child for a node-typed param, fill a param named children.
  • vellooId gives the instance a stable $id.

The instance is opaque from outside; its interior is not addressable with screen locators. Per-instance interior overrides are applied after placement with update_snippet_instance, which can stamp a shared snippet on many screens with a different override on each.

update_snippet_instance

Edit one instance without touching the shared definition. The three edits compose in one call.

ArgTypeRequiredDescription
screenIdstringyesScreen id, or snippet:<id> for an instance nested in a snippet body
pathlocatoryesThe instance
argPatchobjectnoMerges into the instance’s args; null removes a key
extraClassNamestring or nullnoReplaces the instance’s extra classes; null or an empty string clears
innerPathstringwith propPatchBody node: "@id" (preferred; survives body restructures), a dotted index path ("0.2" = third child of first child), or "" for the body root
propPatchobjectwith innerPathMerges into the instance’s $overrides for that body node; null removes a key, and an empty result clears the override

Pass at least one of argPatch, extraClassName, or innerPath + propPatch; innerPath and propPatch must come together. Violations return BadRequest.

{
  "screenId": "dashboard",
  "path": "@nav-settings",
  "argPatch": { "label": "Preferences" },
  "innerPath": "@nav-link",
  "propPatch": { "className": "bg-accent" }
}

Returns { path, applied }, where applied lists which sides ran ("args", "override"), plus diagnostics when there is something to fix. A locator that isn’t a snippet instance, or an innerPath that doesn’t resolve to a component in the snippet body, returns InvalidPath.

Overrides persist on the instance and are applied after param substitution at render time. emit_code inlines instances that carry overrides instead of emitting the shared component.

Previewing

render_snippet renders a snippet in isolation (no host screen) and returns a PNG; full reference on the visual page.

Editing a snippet body

update_props, move_node, remove_node, set_node_id, update_snippet_instance, and find_nodes accept screenId: "snippet:<snippetId>" and act on the snippet’s body with the same locator semantics and error model; the change persists to the snippet and every screen using it re-renders.

compose does not accept snippet: ids. To add structure to a body (including $param and $if nodes, or another snippet nested inside), send a new body with update_snippet’s tree. Full treatment in the MCP overview.