Skip to content

Velloo documentation

Markdown for LLMs ↗

MCP: visual, assets, batch

screenshot with diff baselines, render_snippet, compare_to_url fidelity checks, browser captures, asset listing, upload, import and generation, and the atomic batch envelope.

The capture tools render through the same pipeline as the canvas (real components, the folder’s compiled Tailwind, custom CSS, live-island bundles) and screenshot via a headless browser. That browser is an optional, one-time install: run velloo browser install (add --with-deps on Linux to install the system libraries Chromium needs), or accept the offer during velloo init. When it’s missing, the capture tools return that command as their error; retry after installing, no server restart needed. The canvas, editing, and publishing never need it.

screenshot

Render a screen to PNG and run the full class, theme, and render diagnostics.

ArgTypeRequiredDescription
screenIdstringyes
viewportobjectno{ w, h } (or { width, height }), positive integers. Defaults to the folder’s viewport preset whose name contains “desktop”, else the first preset
modestringno"light", "dark", or "compare". Compare renders light and dark side-by-side in one image, the fastest signal that a design actually adapts. Omitted, it follows the scheme pinned on the frames that place this screen (light when unpinned)
fullPagebooleannoDefault true, so tall screens aren’t clipped
scalenumberno0.25–1; shrinks the payload for layout checks
pathlocatornoCapture only this node: @id or path array (not with mode: "compare"). "@root" or [] captures the whole screen
themestringnoNamed theme to render with. Omitted, it follows the theme pinned on the boards that place this screen, else the folder default. An unknown name is an error listing the known themes
diffbooleannoCompare against the previous same-params capture (not with mode: "compare" or path)
resetBaselinebooleannoWith diff, re-establish the baseline without comparing

When mode or theme is omitted and the screen’s placements disagree (one frame pinned dark and another pinned light or unpinned, or two live boards pinning different themes), the call fails and asks you to pass mode or theme explicitly. Archived boards are ignored for both.

The render uses its own viewport, not a board frame’s size. The plain (non-diff, whole-screen) result returns a text block with contentHeight (the screen’s full rendered height in CSS px), the theme and viewport it used, any diagnostics, and framesShorterThanContent: frames on live boards whose width matches the render viewport but whose fixed height clips the screen below the fold, each as { board, frame, label?, frameHeight, overflowBy }. Screenshots never change the board; resize those frames with update_frame.

Diff mode is tiered. The first call (or resetBaseline: true) establishes a baseline keyed on the render params and returns the image. Later calls return: text only on zero change; a highlight crop plus the changed regions, each mapped to the screen node under it, on small change (under 40 percent changed); the full new image on large change. Baselines are in-memory and per-session, capped at 20; a daemon restart clears them.

{ "screenId": "landing", "mode": "compare", "scale": 0.5 }

render_snippet

Render a snippet in isolation (no host screen) and return a PNG, plus diagnostics for the snippet body when there are any. Useful for iterating on snippet visuals before stamping instances.

ArgTypeRequiredDescription
snippetIdstringyes
argsobjectnoArgs for the snippet’s params
extraClassNamestringnoLayered onto the body root, as at instantiation
viewportobjectno{ w, h }; default 480 by 640
modestringno"light" (default), "dark", or "compare"
scalenumberno0.25–1
themestringnoNamed theme to render with; default "default"

compare_to_url

Code-to-design fidelity check: render the screen and capture the same page (from a live URL, or from a stored browser capture) at the same viewport, then pixel-diff. See the porting guide.

ArgTypeRequiredDescription
screenIdstringyes
sourceobjectyesWhat to diff against: a live page { url, auth?, settleTimeoutMs?, cache? }, or a stored capture { captureId }
viewportobjectno{ w, h }; defaults to the desktop preset. Ignored for a stored capture, which uses the viewport it was captured at
modestringno"light" (default) or "dark". Dark renders the Velloo side dark and best-effort drives a live page dark: prefers-color-scheme, localStorage.theme, and .dark / data-theme="dark" on the html element. An app with a bespoke theme toggle may not flip; eyeball the side-by-side
fullPagebooleannoDefault true
scalenumberno0.25–1; default 0.5 to keep payloads small. Snapped to 1, 0.5, or 0.25 for a stored capture
themestringnoNamed theme for the Velloo side; defaults to the hosting board’s pin, else the folder default
imagebooleannoInclude the side-by-side PNG (page left, Velloo right); default true. false returns metrics only

A live source takes these fields. Only http: and https: URLs are allowed; localhost is the intended target.

FieldTypeDescription
urlstringRequired. Live URL, e.g. http://localhost:3000/pricing
auth.storageStatePathstringPath to a Playwright storage-state JSON (logged-in cookies + localStorage), either absolute or relative to the design folder
auth.cookiesarray[{ name, value, url?, domain?, path? }] seeded before navigating (each needs url or domain + path)
auth.localStorageobjectString entries seeded before any page script runs (e.g. a JWT)
settleTimeoutMsintegerMax wait for network quiet before capturing; default 8000. Raise it for data-heavy pages that paint a spinner first
cache.freezebooleanCapture the page once and diff later calls against that frozen reference, essential for dynamic pages (feeds, dashboards) whose content drifts between loads
cache.ttlMsintegerHow long a frozen capture stays valid; default 300000 (5 minutes)
cache.refreshbooleanRe-capture now and replace the frozen reference; use after changing the target app. Implies freeze

Frozen captures are in-memory and per-session, and a capture flagged unverified is never frozen.

A stored source is { captureId } from list_captures. It was taken in a real browser the user drove, so it is already past any login and identical on every call, the durable way to verify a page behind auth. auth, settleTimeoutMs, and cache don’t apply to it. A theme-only capture has no page render to diff against and is refused.

{
  "screenId": "pricing",
  "source": {
    "url": "http://localhost:3000/pricing",
    "auth": { "storageStatePath": ".auth/state.json" },
    "cache": { "freeze": true }
  },
  "viewport": { "w": 1280, "h": 800 },
  "image": false
}

Returns { similarity, changedRatio, heightDelta, contentHeight, regions } with each diff region mapped to the screen node under it, plus the side-by-side PNG. Depending on the run it also carries contentSimilarity (similarity over the overlapping height, when the heights differ), alignedSimilarity (the score when a one-pixel offset is forgiven, present when that scores better — a raw pixel diff gives no credit for being close, and a single rounded spacing value cascades down a whole column), topMismatches (the worst regions ranked in text), styleDiff (the resolved computed properties that disagree in those regions, design versus page), framesShorterThanContent, a note interpreting a height-dominated or very low score, capture (for a stored source), urlCache (hit or miss, when cache is active), and diagnostics. A faithful structural port usually lands 0.85+; fix topMismatches in order and don’t chase 1.0: fonts and image assets legitimately differ.

When the capture isn’t your page, the result carries unverified: true and a warning, and the similarity is meaningless; do not iterate against it. Causes: redirected: { requested, final } or authWall: true (the URL bounced to a login page; pass source.auth, or use a stored capture) or pageError (the target app is throwing a dev error overlay or rendered blank; fix the dev server first). A data-heavy page that paints a loading spinner can read as blank; raise settleTimeoutMs. An unreachable URL fails with a hint to check that the app’s dev server is running.

Browser captures

Captures are pages recorded from a real browser (the page PNG, a DOM extract with computed styles, the page’s CSS custom properties, and its image assets), stored per design folder. They come from start_capture_session or the velloo capture CLI command.

start_capture_session

Open a real, visible browser window the user drives (to log in, or reach a staging page) and capture pages from its toolbar. Returns immediately with { sessionId, url, status: "open", note }; it does not wait for the user. Tell the user to log in, hit Capture page on each page you need, then Done, and poll list_captures rather than calling this again.

ArgTypeRequiredDescription
urlstringnoPage to open the browser on (http: or https: only). The session is scoped to this origin

The window needs a headed browser: an installed Google Chrome is used directly; otherwise run velloo browser install --full (the full ~300MB Chromium; the default install is a headless shell with no window). The session start appears in the canvas activity feed.

list_captures

List stored captures for this folder, newest first, plus the state of any capture session this daemon opened. Takes no arguments.

Returns { captures, sessions, note? }. Each capture is { captureId, url, title, capturedAt, viewport, themeOnly, nodeCount, assetCount }. sessions[] reports whether the user is still in the browser (status, currentUrl). The note tells you what to ask the user when a session is open or produced nothing.

get_capture

Read one stored capture.

ArgTypeRequiredDescription
captureIdstringyesFrom list_captures
fullbooleannoReturn every extracted node with computed styles (large). Default false

By default the result is a digest: outline lines for the page’s structural spine and repeated blocks (your component candidates), each with its rect as [w×h @x,y] and a HIDDEN or OFFSCREEN marker for nodes that lay out but can’t be seen. It also carries themeCss (the page’s custom properties as import_theme-ready CSS) with fonts and tokenCount, the downloaded assets, and files with absolute paths. Open page.png: it is the authoritative reference; the outline is a digest of it. Feed themeCss to import_theme before composing, and verify with compare_to_url { screenId, source: { captureId } } (the result spells out that call as verifyWith).

Assets

upload_asset, import_assets, and generate_asset write into the folder’s assets/ directory, served at /assets/<name> for an Image src. Only image and font types are accepted: .png, .jpg, .jpeg, .gif, .webp, .avif, .svg, .ico, .bmp, .woff, .woff2, .ttf, .otf. Filenames are reduced to a safe basename (no path traversal, no leading dots), and SVGs have active content stripped before they are stored.

list_assets

List the files in assets/. Check here before spending on art you may already have.

ArgTypeRequiredDescription
unusedOnlybooleannoOnly assets that no screen or snippet references

Returns { assets: [{ url, bytes, usedBy, generated? }] }. generated carries the prompt, intent, and generatedAt for anything generate_asset produced.

upload_asset

Write base64 data into assets/ and get the URL for an Image src. Author SVG art directly and upload it rather than falling back to stock placeholders. For files already on disk, prefer import_assets.

ArgTypeRequiredDescription
filenamestringyese.g. "hero-grain.svg". Must have an allowed extension
datastringyesBase64-encoded file contents; non-empty, max 5 MB decoded
overwritebooleannoDefault true; false fails when the file exists

Returns { assetPath, url, bytes }.

import_assets

Bulk-import existing image, SVG, or font files into assets/ by path, with no base64. Ideal for pulling in many files at once.

ArgTypeRequiredDescription
pathsstring[]yesAbsolute or relative file paths and/or globs, e.g. ["../gen/*.png", "/abs/logo.svg"]
baseDirstringnoBase directory for relative paths and globs; default: the server’s working directory
overwritebooleannoDefault true

Max 5 MB each. Missing, empty, oversized, disallowed-type, or otherwise failing files are reported per entry; one bad file never fails the call. Returns { imported, matched, results } with assetPath, url, and bytes per successful file and an error per failed one.

generate_asset

Generate images or SVGs from a prompt into assets/, through Velloo Cloud. This is the one paid path in the MCP server: it requires signing in with velloo login, and every generated variant is charged against the account’s credit balance. Author the art yourself with upload_asset when you can, check list_assets first, iterate at count: 1, and relay the returned cost and balance to the user.

ArgTypeRequiredDescription
promptstringyes1–2000 characters. Describe subject, style, lighting, and mood
intentstringyesWhat the artwork is for; picks the model. One of photo, illustration, graphic, texture, icon, vector, mark, edit, cutout, upscale
aspectstringno1:1, 4:3, 3:4, 16:9, 9:16, 3:2, 2:3. Defaults per intent; ignored by edit, cutout, and upscale, which follow the source
countintegernoVariants to generate, 1–4; default 1. Each one is charged
referencestring[]noUp to 3 existing assets as folder paths ("assets/hero.png"), each png/jpg/gif/webp/avif up to 4 MB. Required by edit, cutout, and upscale; optional style or subject guidance for others
filenamestringnoStem for assets/<filename>.<png or svg>; default: the generation id. With count > 1 the variants become <filename>-1, <filename>-2, …

Returns { assets, intent, chargedMicros, balanceMicros, cost } plus the first file’s assetPath, url, and kind mirrored at the top level. Each stored file carries width/height (raster) or its SVG content. The prompt is recorded as provenance beside the asset, which list_assets reports. Failures come back as a message the agent can act on: signed out (run velloo login), out of credits (ask the user to top up), rate limited (wait a minute), bad arguments, or generation unavailable on the server. Bad-argument and unreachable-server failures state that nothing was generated or charged.

Batch

batch

Run a sequence of mutation calls in one round-trip.

ArgTypeRequiredDescription
callsarrayyes1–100 entries of { tool, args }; each args is that tool’s standalone argument object, validated by the same schema
atomicbooleannoDefault true

Atomic by default: on the first error, every touched resource rolls back to its pre-batch state (disk, memory, and undo history; created screens/boards/snippets are deleted), and the result reports rolledBack: true with the failing call. Pass atomic: false to run until the first error and keep the completed work. The result is { completed, total, rolledBack, results: [{ tool, ok, value?, error? }] }, plus diagnostics for the screens and snippets the batch touched.

A batch with two or more remove_node / move_node calls on the same screen addressed by numeric paths is refused up front, because each removal shifts later sibling indices. Address those nodes by @id instead.

Supported tools: add_screen, remove_screen, add_board, add_frame, remove_frame, update_props, remove_node, move_node, set_node_id, add_snippet, update_snippet, remove_snippet, update_snippet_instance, update_frame, add_note. Build trees with compose outside the batch.

{
  "calls": [
    { "tool": "add_screen", "args": { "name": "Pricing", "id": "pricing" } },
    { "tool": "add_frame", "args": { "boardId": "main", "screenId": "pricing", "w": 1440, "h": 900 } }
  ]
}