Developers

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.

The in-app Workflow Agent and an MCP client are two doors to the same room. Nodes an MCP client creates carry its name as provenance on the canvas — Claude Code (vmotif) — so a team can see which work came from where.

Install#

One endpoint, Streamable HTTP, OAuth on first use. Add it to whichever client you work in:

https://vmotif.com/api/mcp

Claude Code

Terminal
claude mcp add --transport http vmotif https://vmotif.com/api/mcp

Then, inside a Claude Code session, run /mcp and choose vmotifAuthenticate. 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

Terminal
codex mcp add --url https://vmotif.com/api/mcp --oauth-resource https://vmotif.com/api/mcp vmotif
codex mcp login vmotif

Codex needs the OAuth resource bound explicitly when the server is added, then a separate login.

Cursor, Windsurf and other mcpServers clients

mcp.json
{
  "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

.vscode/mcp.json
{
  "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.

Clients initialise their MCP servers when a session starts. After adding or re-authenticating 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 with 401. Agents act as a signed-in user, not as a key.
  • Standard scopes only. Tokens carry openid, email and profile. 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/mcp and www.vmotif.com/api/mcp both 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 returns 401 with a WWW-Authenticate header 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_usage is the pre-flight: it reports the remaining budget in micro-dollars (1,000,000 = $1), or null for 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:

Error result
{
  "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#

  1. Read before you write

    read_canvas first. It returns node ids, positions and the DESIGN.md in force; update_canvas needs the ids, and auto-placement in add_canvas_nodes stacks below what is there.
  2. Let the canvas carry the state

    Prefer add_canvas_nodes + run_workflow over bare generate_* 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.
  3. Poll, don't wait

    Generation and run tools return pending immediately. Poll get_generation / get_workflow_run; each result appears in results as its output settles, so progress can be reported before the run completes.
  4. Hand back the link

    Every canvas tool returns url. 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

Free

The 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

Free

The 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 wallet

Start 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 wallet

Start 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 wallet

Start 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

Free

Status, 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

Free

Convert, 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 confirmation

Place 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

Free

Every 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

Free

Favourited canvases, most recently updated first.

limitinteger
1–100, default 50.
offsetinteger
Default 0.

Returns Same shape as list_projects.

read_canvas

Free

The 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

Free

A 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

Free

The maintained prebuilt workflows, with stable ids and graph sizes.

No parameters.

Returns { data: { id, name, description, nodeCount, edgeCount }[] }

add_premade_workflow

Free

Append 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

Free

Add 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

Free

One 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

Free

A 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

Free

Lightweight summaries of saved workflows.

limitinteger
1–100, default 50.
offsetinteger
Default 0.

Returns { data: Workflow[], pagination }

run_workflow

Spends wallet

Run a saved graph headlessly. Executes an immutable snapshot taken at enqueue; completed outputs are written back onto the canvas.

workflow_idrequireduuid
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, workflowId, status: "pending", progress: { total, completed }, results: [] }

get_workflow_run

Free

Progress, per-output results with costs, and failure details for a run you started.

workflow_idrequireduuid
run_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 wallet

Run 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 wallet

Run 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 }