MCP server
Vmotif is a Model Context Protocol server. Point an agent at one URL and it can do what you do on the canvas — open a project, add and wire nodes, generate images, code and video, run whole workflows and read the results back — signed in as you, spending your wallet, leaving every result on a canvas you can open.
What agents can do#
22 tools, in five groups. 6 of them can spend the GPU wallet (they start generations or cloud-browser jobs); the rest read or edit the canvas for free.
- Read
- Budget and plan, the live model catalogue with prices, every canvas and workflow the account can open, and the full graph of any of them — nodes, edges, brand assets, the DESIGN.md in force.
- Build
- Create canvases (empty or seeded with a prebuilt workflow), add any node type the toolbar offers, connect and rearrange, patch prompts and settings, rename — all as atomic edits the user sees land live.
- Generate
- Images, code, SVG, full HTML screens and video as headless jobs; free image transforms; and whole saved workflows, with per-output results and costs.
- Drive the browser nodes
- Record a page's animation into frames and CSS context, or crawl a site into a redesign starting point.
The result of a generation is never just a URL in a terminal: every completed job is placed on a canvas, and the tool responses carry the canvas link. An agent working from the command line and a person working in the browser are looking at the same graph.
Install#
One endpoint, Streamable HTTP, OAuth on first use. Add it to whichever client you work in:
https://vmotif.com/api/mcpClaude Code
claude mcp add --transport http vmotif https://vmotif.com/api/mcpThen, inside a Claude Code session, run /mcp and choose vmotif → Authenticate. A browser tab opens the sign-in and consent page; approve it and the tools appear. A step-by-step session is on the Claude Code walkthrough.
Codex
codex mcp add --url https://vmotif.com/api/mcp --oauth-resource https://vmotif.com/api/mcp vmotif
codex mcp login vmotifCodex needs the OAuth resource bound explicitly when the server is added, then a separate login.
Cursor, Windsurf and other mcpServers clients
{
"mcpServers": {
"vmotif": {
"url": "https://vmotif.com/api/mcp"
}
}
}Any client that reads an mcpServers block with a url entry speaks Streamable HTTP and will run the OAuth flow when it first connects. In Cursor this is ~/.cursor/mcp.json or a project’s .cursor/mcp.json.
VS Code
{
"servers": {
"vmotif": {
"type": "http",
"url": "https://vmotif.com/api/mcp"
}
}
}Claude.ai and Claude Desktop
Settings → Connectors → Add custom connector, paste the endpoint URL, and sign in when prompted.
vmotif, open a new session before judging whether the tools are there.Authentication#
The server is OAuth 2.1 only, with Vmotif’s own sign-in as the authorization server. The first connection opens a browser tab: you sign in (Microsoft, Google or email) and approve the client on a consent page. The client receives a token bound to this server; the server verifies it on every request and re-checks the client against the list of approved clients each time, so a client removed from that list stops working immediately rather than at token expiry.
- API keys do not work here. A
vmk_live_key is a REST credential for/api/v1; the MCP server rejects it with401. Agents act as a signed-in user, not as a key. - Standard scopes only. Tokens carry
openid,emailandprofile. Permission to generate is not a scope; it is decided per call from your plan, your workspace membership and who owns the canvas. - Two spellings, one server.
vmotif.com/api/mcpandwww.vmotif.com/api/mcpboth serve the endpoint directly and both are accepted as the token audience, so a client configured with either keeps working. Publish the apex form. - Discovery is standard: the protected-resource metadata is at
/.well-known/oauth-protected-resource/api/mcp, and an unauthenticated request returns401with aWWW-Authenticateheader pointing at it.
Budget, workspaces and limits#
Spending tools draw on the same GPU wallet as the canvas, at the same prices, and appear in the same usage history. Pass workspace_id to bill a Team workspace’s pooled wallet instead; the caller must be a member. When an agent omits it and the account has no personal plan, the server falls back to the user’s default workspace, so a Team member’s agent works without configuration.
get_usageis the pre-flight: it reports the remaining budget in micro-dollars (1,000,000 = $1), ornullfor an unlimited pool — which an agent should read as “keep going”, not “empty”.- There is no hourly cap on generations. The wallet bounds spend and your plan’s parallel slots pace load — a burst simply queues. Image transforms carry a generous abuse limit of their own; a limited call returns an error result carrying
retryAfterSeconds. - A generation the model cannot run — wrong ratio for the model, a video duration it does not offer — fails at validation, before anything is charged, with the reason in the result.
Errors#
There are no HTTP status codes at the tool level. A failed call is an ordinary tool result with isError: true, a plain-language message in content, and the same payload in structuredContent. The messages are meant to be shown:
{
"isError": true,
"content": [{ "type": "text", "text": "Workspace AI usage budget reached for the current period." }],
"structuredContent": { "error": "Workspace AI usage budget reached for the current period." }
}Ids you do not own return not found rather than forbidden — nothing about other accounts is disclosed. Unexpected failures collapse to a fixed message such as Failed to generate image. — the detail is logged server-side, not returned.
Patterns that work#
Read before you write
read_canvasfirst. It returns node ids, positions and the DESIGN.md in force;update_canvasneeds the ids, and auto-placement inadd_canvas_nodesstacks below what is there.Let the canvas carry the state
Preferadd_canvas_nodes+run_workflowover baregenerate_*calls when the result should stay useful: the prompt, model and settings are on the canvas for the next person, and re-running is one call.Poll, don't wait
Generation and run tools returnpendingimmediately. Pollget_generation/get_workflow_run; each result appears inresultsas its output settles, so progress can be reported before the run completes.Hand back the link
Every canvas tool returnsurl. Finish by giving it to the user — the canvas is where they will look at the work.
Tool reference#
Every registered tool, its parameters and what it returns. Required parameters are marked; everything else is optional. This list is checked against the server’s registrations in CI, so it cannot fall behind.
Account & catalogue
Read-only. Check the budget, list the models the picker offers.
get_usage
FreeThe plan tier and remaining GPU budget, personal or for one workspace.
workspace_iduuid- Read a workspace's pooled Team budget instead of the personal one.
Returns { tier, quota } — quota.type is token_budget, unlimited, generation_count or blocked; amounts in micro-dollars.
list_models
FreeThe models the picker offers, with category, provider, capabilities and the price actually billed.
category"image" | "text" | "video"- text includes code and SVG models.
workspace_iduuid- Bill against a Team workspace's pooled wallet instead of the personal one. The caller must be a member (any role but viewer) of an active Team workspace.
Returns { data: Model[] }
Generate
Start a headless job and poll it. Each completed generation is placed on a canvas the user can open.
generate_image
Spends walletStart an image generation.
promptrequiredstring- Up to 50,000 characters.
modelstring- An id from list_models; defaults to the picker's current image default.
images{ url, mediaType? }[]- Up to 10 reference images. https:// or data: URLs.
text_inputs{ content, label?, language? }[]- Up to 10 text or code snippets fed to the model as context.
aspect_ratio"1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3"- Requested ratio; each model supports a subset.
workspace_iduuid- Bill against a Team workspace's pooled wallet instead of the personal one. The caller must be a member (any role but viewer) of an active Team workspace.
Returns { id, status: "pending" } — poll with get_generation.
generate_code
Spends walletStart a code, markup, SVG, JSON or full-screen HTML generation.
promptrequiredstring- Up to 50,000 characters.
modelstring- An id from list_models; defaults to the code model.
images{ url, mediaType? }[]- Up to 10 reference images. https:// or data: URLs.
text_inputs{ content, label?, language? }[]- Up to 10 text or code snippets fed to the model as context.
target_languagestring- typescript, css, svg, glsl, json, text… Omit and the server's own heuristics choose.
screen_htmlboolean- Generate one self-contained HTML page instead of a snippet.
workspace_iduuid- Bill against a Team workspace's pooled wallet instead of the personal one. The caller must be a member (any role but viewer) of an active Team workspace.
Returns { id, status: "pending" }
generate_video
Spends walletStart a video generation. Duration, resolution and ratio are validated against the model's contract before anything is charged.
promptrequiredstring- Up to 50,000 characters.
modelstring- An id from list_models with category video; defaults to the video default.
images{ url, mediaType? }[]- Reference or first/last-frame images; supported counts depend on the model.
text_inputs{ content, label?, language? }[]- Up to 10 text or code snippets fed to the model as context.
aspect_ratio"1:1" … "21:9"- Nine ratios; 21:9 is Seedance only.
durationnumber- Seconds, 1–30. Most models accept only 4, 6 or 8; Seedance 2.5 up to 30.
resolutionstring- "480p", "720p" or "1080p".
sound_enabledboolean- Generate audio on models with a sound toggle.
workspace_iduuid- Bill against a Team workspace's pooled wallet instead of the personal one. The caller must be a member (any role but viewer) of an active Team workspace.
Returns { id, status: "pending" }
get_generation
FreeStatus, result, cost or failure for a generation you started over MCP. On first completion the result is placed on a new canvas and its link returned.
idrequireduuid- From generate_image, generate_code or generate_video.
Returns { id, status, result, error, projectAssociation: { projectId, url, nodeIds } }
transform_image
FreeConvert, compress to a byte target, or trim the edges of an image you own. Synchronous and free; its own rate limit.
sourcerequiredstring- A storage path or URL of an image the caller owns — a generation's result URL qualifies.
oprequired"convert" | "compress" | "trim"format"png" | "jpeg" | "webp" | "avif"- Required for convert; compress defaults to webp.
qualitynumber- 0–1 for convert, default 0.9.
target_bytesinteger- Required for compress: 10 KB – 10 MB.
max_dimensioninteger- Bound the longest edge, up to 8192. Never enlarges.
Returns { url, path, width, height, bytes, format, withinTarget, trimmed }
add_generation_to_project
FreeNeeds confirmationPlace every image from a completed generation onto a chosen canvas. The one tool that insists on explicit user confirmation.
generation_idrequireduuid- A completed MCP image generation.
project_idrequireduuid- The target canvas, chosen by the user from list_projects.
confirmed_by_userrequiredtrue- Literal true, only after the user has said yes.
Returns { status, generationId, projectId, nodeIds }
Canvases
Read and edit the same graph the user sees — every node type the toolbar offers, atomically.
list_projects
FreeEvery canvas the account can open — owned and workspace — with its URL.
limitinteger- 1–100, default 50.
offsetinteger- Default 0.
favorites_onlyboolean
Returns { data: Project[], pagination: { total, has_more, next_offset } }
get_favorite_projects
FreeFavourited canvases, most recently updated first.
limitinteger- 1–100, default 50.
offsetinteger- Default 0.
Returns Same shape as list_projects.
read_canvas
FreeThe whole graph — nodes with positions and data, edges, tagged brand assets as live URLs, and the DESIGN.md in force.
project_idrequireduuid
Returns { project, canvas: { nodes, edges, brandAssets, designContext } }
create_canvas
FreeA new empty canvas, or one seeded atomically with a prebuilt workflow. Nodes it creates carry the client's name as provenance.
namestring- Defaults to the workflow's name or Untitled Workflow.
workspace_iduuid- Create inside a workspace the caller belongs to.
premade_workflow_idstring- An id from list_premade_workflows.
Returns { id, name, url, nodeCount, edgeCount, createdBy }
list_premade_workflows
FreeThe maintained prebuilt workflows, with stable ids and graph sizes.
No parameters.
Returns { data: { id, name, description, nodeCount, edgeCount }[] }
add_premade_workflow
FreeAppend a prebuilt workflow to an existing canvas without disturbing what is there.
canvas_idrequireduuid- The canvas, from list_projects or create_canvas.
premade_workflow_idrequiredstring- An id from list_premade_workflows.
Returns { canvasId, nodeCount, edgeCount, nodeIds }
add_canvas_nodes
FreeAdd up to 25 nodes of any type the toolbar offers — image, prompt (with its output node created and wired), video, capture, style_guide_input, site_scrape, code, text_input, sticky_note, note, group or social_post. Omit x/y to auto-place.
canvas_idrequireduuid- The canvas, from list_projects or create_canvas.
nodesrequiredNodeSpec[]- A discriminated union on type; each variant accepts only its own fields.
Returns { canvasId, url, nodes: { id, type, x, y }[], edges }
update_canvas
FreeOne atomic edit: patch node content and settings, move or resize, delete nodes and edges, connect nodes, rename the canvas. An unknown id fails the whole update without changing anything.
canvas_idrequireduuid- The canvas, from list_projects or create_canvas.
namestring- Rename the canvas.
updates{ node_id, data }[]- Per-node patches — prompt, model, aspect_ratio, image_url, brand_asset_role, content, color, url, html, viewport… each node type accepts its own.
moves{ node_id, x, y, width?, height? }[]- Only group and capture nodes resize.
add_edges{ source_node_id, target_node_id }[]- Outputs feed prompts; no self-connections.
remove_node_idsstring[]- Attached edges go too.
remove_edge_idsstring[]
Returns { canvasId, url, movedCount, removedNodeCount, updatedNodeCount, addedEdges }
Workflows
Run a saved graph end to end and read the per-output results.
get_workflow
FreeA saved workflow's metadata and hydrated graph — the same payload as read_canvas, addressed by workflow id.
workflow_idrequireduuid
Returns { workflow, graph: { nodes, edges, brandAssets, designContext } }
list_workflows
FreeLightweight summaries of saved workflows.
limitinteger- 1–100, default 50.
offsetinteger- Default 0.
Returns { data: Workflow[], pagination }
run_workflow
Spends walletRun a saved graph headlessly. Executes an immutable snapshot taken at enqueue; completed outputs are written back onto the canvas.
workflow_idrequireduuidworkspace_iduuid- Bill against a Team workspace's pooled wallet instead of the personal one. The caller must be a member (any role but viewer) of an active Team workspace.
Returns { id, workflowId, status: "pending", progress: { total, completed }, results: [] }
get_workflow_run
FreeProgress, per-output results with costs, and failure details for a run you started.
workflow_idrequireduuidrun_idrequireduuid- From run_workflow.
Returns { id, status, progress, results: { outputNodeId, type, status, url | code, costMicros }[], error, completedAt }
Site & motion
Drive the two cloud-browser nodes: record a page's animation, or crawl a site into a redesign.
run_capture
Spends walletRun a capture node — record its URL or element in a cloud browser and write the frames and animation context back onto the node. Synchronous, 10–30 seconds.
canvas_idrequireduuid- The canvas, from list_projects or create_canvas.
node_idrequiredstring- A capture node, from read_canvas. Set its url, selector, duration and color_scheme first.
Returns { status, frameUrls, totalFrames, animationContext, pageTitle }
run_site_scrape
Spends walletRun a site scrape node — crawl the configured site and seed the redesign graph (screenshots, brand assets, baseline DESIGN.md, direction previews, per-page screen prompts) beside it. Synchronous, metered per page.
canvas_idrequireduuid- The canvas, from list_projects or create_canvas.
node_idrequiredstring- A site scrape node, from read_canvas. Set its url, mode, max_pages and paths first.
Returns { status, siteName, pageCount, pages, seededNodeCount, previousGraphReplaced }