Skip to content
MCP: visual, assets, batch

Documentation

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.

Arg Type Required Description
screenId string yes
viewport object no { w, h } (or { width, height }), positive integers. Defaults to the folder’s viewport preset whose name contains “desktop”, else the first preset
mode string no "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)
fullPage boolean no Default true, so tall screens aren’t clipped
scale number no 0.25–1; shrinks the payload for layout checks
path locator no Capture only this node: @id or path array (not with mode: "compare"). "@root" or [] captures the whole screen
theme string no Named 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
diff boolean no Compare against the previous same-params capture (not with mode: "compare" or path)
resetBaseline boolean no With 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; give those frames a taller h with update_frame, or fit: "content" so they follow the screen. Frames that already follow their screen are left out of the list.

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.

Arg Type Required Description
snippetId string yes
args object no Args for the snippet’s params
extraClassName string no Layered onto the body root, as at instantiation
viewport object no { w, h }; default 480 by 640
mode string no "light" (default), "dark", or "compare"
scale number no 0.25–1
theme string no Named 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.

Arg Type Required Description
screenId string yes
source object yes What to diff against: a live page { url, auth?, settleTimeoutMs?, cache? }, or a stored capture { captureId }
viewport object no { w, h }; defaults to the desktop preset. Ignored for a stored capture, which uses the viewport it was captured at
mode string no "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
fullPage boolean no Default true
scale number no 0.25–1; default 0.5 to keep payloads small. Snapped to 1, 0.5, or 0.25 for a stored capture
theme string no Named theme for the Velloo side; defaults to the hosting board’s pin, else the folder default
image boolean no Include 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.

Field Type Description
url string Required. Live URL, e.g. http://localhost:3000/pricing
auth.storageStatePath string Path to a Playwright storage-state JSON (logged-in cookies + localStorage), either absolute or relative to the design folder
auth.cookies array [{ name, value, url?, domain?, path? }] seeded before navigating (each needs url or domain + path)
auth.localStorage object String entries seeded before any page script runs (e.g. a JWT)
settleTimeoutMs integer Max wait for network quiet before capturing; default 8000. Raise it for data-heavy pages that paint a spinner first
scroll boolean A full-page capture scrolls the page end to end first, so scroll-reveal sections and lazy images are in it. false skips that. scrollTruncated in the result reports a page that kept growing
hide string[] Up to 20 CSS selectors to take out of the page before it is captured: a consent banner, a chat launcher. hidden in the result counts what each selector removed; an invalid selector is an error
cache.freeze boolean Capture the page once and diff later calls against that frozen reference, essential for dynamic pages (feeds, dashboards) whose content drifts between loads
cache.ttlMs integer How long a frozen capture stays valid; default 300000 (5 minutes)
cache.refresh boolean Re-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 (for those regions: the design node and the page element in the same box, the node’s own classes as they stand, and the computed properties that disagree, design versus page), fixInPlace (a reminder that a correction is one update_props for all the nodes named, and to make the same fix in the app’s page file where one exists), textDiff (the copy the page and the design disagree on: changed, missing, and extra), 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. For a faithful recreation, fix topMismatches in order and keep improving the whole page; the init handoff uses 0.9 as a minimum review bar, not a stopping target. Inspect the image as well as the score.

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.

A page that has a password field because it is the page you asked for is not an auth wall. A sign-in, sign-up, or password-reset address counts, and so does any page whose design has a password field of its own.

Captures wait for visible loading states to settle and freeze animations and transitions for a stable reference. A target page error is reported as unverified; fix the app or choose an authenticated capture before tuning the design.

The wait has one exception. When the design is itself a loading state, with an aria-busy region or a “Loading…” label of its own, a page that is still busy is the page that was asked for. It is captured as it stands, not waited out, and not reported as an unverified, still-loading capture. That is how the loading state of a flow step is compared with its implementation.

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.

Arg Type Required Description
url string no Page 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.

Arg Type Required Description
captureId string yes From list_captures
full boolean no Return 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).

store_host_files

HTML designs: copy the app’s stylesheets, plus the images and fonts referenced by CSS and design nodes, into assets/host/. The preview, render, export, and publish paths use these stored copies without fetching the running app.

Arg Type Required Description
from "source" or { captureId } yes Read app files on disk, or a captured page’s MHTML archive
stylesheets string[] no App stylesheet URLs in cascade order; default captured links, else the design’s current list

This also updates hostApp.stylesheets. Paths name the app’s served files, such as /static/site.css, even if the source lives in another directory. Run again after CSS changes or after adding nodes that reference new app assets. A capture supplies built CSS unavailable on disk. Missing copies appear as hostStyles in screenshot and comparison results. See the HTML workflow.

Returns { stylesheets, stored, warnings }. A non-HTML design or unknown capture returns an error.

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.

Arg Type Required Description
unusedOnly boolean no Only 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.

Arg Type Required Description
filename string yes e.g. "hero-grain.svg". Must have an allowed extension
data string yes Base64-encoded file contents; non-empty, max 5 MB decoded
overwrite boolean no Default 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.

Arg Type Required Description
paths string[] yes Absolute or relative file paths and/or globs, e.g. ["../gen/*.png", "/abs/logo.svg"]
baseDir string no Base directory for relative paths and globs; default: the server’s working directory
overwrite boolean no Default 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.

Arg Type Required Description
prompt string yes 1–2000 characters. Describe subject, style, lighting, and mood
intent string yes What the artwork is for; picks the model. One of photo, illustration, graphic, texture, icon, vector, mark, edit, cutout, upscale
aspect string no 1: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
count integer no Variants to generate, 1–4; default 1. Each one is charged
reference string[] no Up 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
filename string no Stem 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.

Arg Type Required Description
calls array yes 1–100 entries of { tool, args }; each args is that tool’s standalone argument object, validated by the same schema
atomic boolean no Default true

On the guided façade, batch also accepts { operation, args } (or { operation, arguments }) entries and a single call without a calls array. The native schema remains { calls: [{ tool, args }], atomic? }.

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? }] }, with any screen or snippet in a value summarized as a nodeCount, 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, connect_frames, add_note, update_note, remove_note. Build trees with compose outside the batch. Inside a batch, update_frame and connect_frames need their boardId.

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