Maketa · MCP server

Maketa MCP Server: give your AI agent a real design surface

Maketa is a screen-mockup editor with a machine-readable scene model. Its MCP server lets an AI agent create screens, build and rearrange the screen tree, and wire up tap-through navigation — then hand a human a link that opens the result in a browser, clickable like a real app.

The point is not "AI generates a picture of a UI". A screen is a tree of typed nodes — stacks, text, images, buttons, inputs, a bottom tab bar — that a person can afterwards rearrange with a mouse. Nodes carry no coordinates of their own: a parent decides where each child sits and how much room it gets, the way Flutter, Compose and SwiftUI do it. Nothing is baked into a bitmap, and the same model is edited by humans in the browser, by Flutter and web apps pushing their real screens, and by agents through MCP or REST.

Connect in one line:

claude mcp add --transport http maketa https://maketa.pro/maketa/api/mcp

Remote server, Streamable HTTP, nothing to install. Endpoint: https://maketa.pro/maketa/api/mcp. Everything on this page is free; the product itself is maketa.pro (interface in Russian — see limitations).

Connect

ClientHow
Claude Codeclaude mcp add --transport http maketa https://maketa.pro/maketa/api/mcp — the browser opens a Maketa sign-in, no key to copy
Claude web & desktopSettings → Connectors → Add custom connector → paste the URL
CursorAdd to Cursor, or Settings → Tools & Integrations → New MCP Server with the URL
VS CodeAdd to VS Code (Copilot Chat, agent mode)
ChatGPTSettings → Connectors → developer mode → Create → paste the URL. Paid plans only
Cline and any other client{"mcpServers":{"maketa":{"type":"streamableHttp","url":"https://maketa.pro/maketa/api/mcp"}}}

After sign-in the agent sees only the boards of that account. The first call is always maketa_list_boards.

Tools

Thirteen tools, scoped per user — an agent only ever sees boards its account owns or collaborates on.

ToolWhat it does
maketa_list_boardsList the boards (mockup files) the caller can access. Always the first call.
maketa_list_screensScreens of a board with a summary: kind (code/design), lock state, node count, slots, branches, version. model tells you whether it is a tree (v2) or a legacy flat screen (v1).
maketa_get_screenOne screen exactly as stored: its slots and the flat nodes map.
maketa_get_treeThe screen as a nested tree per slot — cheaper and clearer to read than the flat map. Also returns detached: nodes attached to nothing.
maketa_create_screenNew design screen: name, device preset, size, background. It comes with a root stack in the body slot — its id is returned as bodyId, put your first nodes there.
maketa_add_nodeAdd a node to the tree: the node itself plus parentId (inside a container) or slot (top/body/bottom/over), with an optional index.
maketa_update_nodePatch a node: name, w, h, layout, look, content, action, hidden, repeat. layout, look and content merge field by field, so {"content":{"text":"New"}} keeps size and colour; "action": null clears the action.
maketa_move_nodeReparent a node or reorder it among its siblings. In v2 this is "move": a node has no coordinates.
maketa_remove_nodeDelete a node with its whole subtree; references to it in actions and tab items are dropped for you.
maketa_branch_screenBranch a screen into an editable copy — the only way to redesign a screen that came from code.
maketa_push_code_screenUpsert a screen from source code by a stable key; every push is a new version ("commit from code").
maketa_list_branchesBranches of a screen.
maketa_get_parentThe original a branch came from — navigate back to the code screen.

Read-only subset: list_boards, list_screens, get_screen, get_tree, list_branches, get_parent. Everything else writes to the user's boards. The *_element tools are gone: a node is not dragged to x/y, it is moved to another parent. Tool errors come back as isError: true with a human-readable message — for tree problems it names the offending node.

Code ↔ design branching

Screens pushed from source code (kind: "code") are locked. An agent that wants to redesign one calls maketa_branch_screen and edits the branch — a proposal that lives next to the original. When the code moves on and a new version is pushed, existing branches are flagged baseStale, so nobody silently reviews an outdated design.

Example prompts

  • "Show me the screens of my Maketa board and describe the checkout one."
  • "Add a primary button at the bottom of the login screen and link it to the home screen."
  • "Turn the menu screen into a scrollable list of five identical dish cards." (one node with repeat: 5, not five copies)
  • "Branch the profile screen that came from code and propose a cleaner layout."

The scene model

One JSON document per board, schema maketa.board.v2. A board holds screens; a screen is a tree of nodes, not a flat list of shapes. A node has no coordinates of its own: the parent decides where it sits and how much room it gets. The rule is lifted verbatim from the platforms (Flutter's "Understanding constraints", the same in Compose and SwiftUI):

Constraints go down. Sizes go up. The parent sets the position.

Free placement still exists — it is the over layout, where a child is pinned to one of nine anchor points with a dx/dy offset.

A screen has four slots: top (header, does not scroll), body (the scrollable content, takes the remaining height), bottom (tab bar, does not scroll) and over (a layer on top — floating buttons, hints). The nodes themselves live in a flat nodes map; the tree inside it is expressed by children arrays, because a node is addressed by id from actions, from the structure panel and from MCP.

Shared node fields: id, type, name, children (containers only), w, h, layout, look, content, action, hidden, repeat — plus at, dx, dy inside an over container. There is deliberately no x, y, margin, flex or zIndex: spacing is the parent's gap, order is the position in children.

Size on each axis is one of three values: "hug" (fit the content — the default, the field can be omitted), "fill" (take all the room the parent has left) or a number of pixels.

TypeWhat it iscontent
stackContainer. Cards, lists, carousels, grids, headers and dividers are all built from it — they are palette presets, not types—
textA labeltext (≤5000), size 8–200, weight 100–900 in hundreds, color, align, lines (clamp), emoji
imageA picture or its placeholdersrc (data:image/… only), fit cover|contain|fill, ratio (aspect ratio instead of a height)
buttonA button — a type, not a rectangle with a label on ittext, icon, kind filled|tonal|text, size, weight, color, align
inputA text fieldplaceholder, value, search, kind text|password|multiline
choiceRadio buttons or checkboxes as one groupitems [{text, on}], multi, active
tabsA tab bar or an in-screen switcher as one nodeitems [{text, icon, link (screen id), shows (node id), action}], active, variant bottom|pill|underline|segmented, color, muted, activeText
itemA list row: icon, title, subtitle, trailing elementslots: {lead, title, sub, trail} — these are roles over nodes that already exist, not extra children
screenThe screen inside the device frame—

Layout (containers only): dir down|right|over|wrap; pad (a number or [top,right,bottom,left]); gap; main start|center|end|between|around|evenly; cross start|center|end|stretch; scroll; cols (with wrap); visible.

Look: bg, border, borderW 0–12, radius (number or [tl,tr,br,bl]), shadow none|soft|card, opacity.

Three rules worth knowing before your first write:

  • action is what makes a mockup clickable. {"t":"goto","screen":"s_cart","mode":"push|modal|dialog|replace"} opens another screen; {"t":"show","show":["n_b"],"hide":["n_a"]} swaps blocks on the same screen (tabs, form steps, expanding sections), with hidden: true marking a block that waits for a tap. A mockup without actions is a set of pictures.
  • A carousel's card width is set by visible, never in pixels — how many cards fit in the frame (2, 2.5, 3). A half means "the edge of the next card shows", which is what tells a human the row scrolls. It is the only phrasing that survives a change of device.
  • Ten identical rows are repeat: 10 on one node, not ten copies. A tab bar is one tabs node, never five rectangles; a button is button; a text field is input. Agents get this wrong constantly.
{
  "v": 2,
  "screens": [{
    "id": "s_home", "name": "Home",
    "device": "iphone-15", "w": 393, "h": 852, "bg": "#FFFFFF",
    "safe": true, "kind": "design", "parentId": null, "branches": [],
    "slots": { "top": "n_hdr", "body": "n_body", "bottom": "n_nav" },
    "nodes": {
      "n_hdr":  {"id":"n_hdr","type":"stack","name":"Header","w":"fill","h":56,
                 "look":{"bg":"#FFFFFF"},
                 "layout":{"dir":"right","pad":[0,16,0,16],"gap":8,"cross":"center"},
                 "children":["n_ttl"]},
      "n_ttl":  {"id":"n_ttl","type":"text","content":{"text":"Menu","size":22,"weight":700}},
      "n_body": {"id":"n_body","type":"stack","w":"fill","h":"fill",
                 "layout":{"dir":"down","pad":16,"gap":12,"scroll":true},
                 "children":["n_card"]},
      "n_card": {"id":"n_card","type":"stack","name":"Dish card","w":"fill",
                 "repeat":5,                    // five identical cards, one node
                 "look":{"bg":"#F7F8FA","radius":14,"shadow":"soft"},
                 "layout":{"dir":"down","pad":12,"gap":4},
                 "children":["n_name"],
                 "action":{"t":"goto","screen":"s_dish","mode":"modal"}},
      "n_name": {"id":"n_name","type":"text","content":{"text":"Margherita","size":16,"weight":600}},
      "n_nav":  {"id":"n_nav","type":"tabs","w":"fill","h":82,
                 "content":{"variant":"bottom","active":0,
                            "items":[{"text":"Menu","icon":"🍕"},
                                     {"text":"Cart","icon":"🛒","link":"s_cart"}]}}
    }
  }]
}

Tree integrity is checked on write and a violation is a refusal naming the node, not a silent repair: unknown_node (a dangling reference), node_two_parents, tree_cycle, too_deep (deeper than 12), bad_node_type, unknown_screen. Out-of-range values, by contrast, never fail a save — they are clamped or dropped.

Documents saved before the tree model arrive without "v": 2 and are read as the legacy flat maketa.board.v1 (objects: […]); it is kept for them only. New code always sends "v": 2.

Full reference: the Russian model page and the machine-readable OpenAPI 3.1 schema. Device presets with sizes: devices.json. The layout engine that turns a tree into rectangles is a dependency-free file: layout-engine.js.

Authentication

OAuth 2.1 — recommended for interactive clients

Authorization code with PKCE S256, public client, access token for 1 hour, refresh token for 30 days. Dynamic client registration (RFC 7591) is open — POST https://maketa.pro/maketa/api/oauth/register — so any client bootstraps itself the same way Figma's connector does, with no manual client id.

Metadata discovery: https://maketa.pro/.well-known/oauth-protected-resource, the path-suffixed …/oauth-protected-resource/maketa/api/mcp (RFC 9728 §3.1) and …/.well-known/oauth-authorization-server (RFC 8414). An unauthenticated request answers 401 with WWW-Authenticate: Bearer resource_metadata="…", so strict clients find the sign-in on their own. Access is revoked from the Maketa account page.

Bearer token — headless

Same endpoint, Authorization: Bearer mk_pk_… for a project key (issued by a board owner, permanent until revoked, scoped to one board — what CI uses to push screens from code; boardId is then required in every call) or Bearer mk_sb_… for an agent sandbox key (see below). The header x-maketa-key is accepted too.

curl -s -X POST https://maketa.pro/maketa/api/mcp \
  -H "Authorization: Bearer mk_sb_…" -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
CredentialScopeLifetime
OAuth access tokenall boards of the signed-in user1 hour (refresh token: 30 days)
mk_pk_… project keyone boarduntil revoked
mk_sb_… sandbox keysandbox boards created with that key14 days, 5 boards, 200 saves/day

For fully autonomous agents: no human, no signup

A human can draw anonymously in the browser. An agent gets a sandbox key the same way — without a person in the loop:

curl -s -X POST https://maketa.pro/maketa/api/agent/register \
  -H 'content-type: application/json' \
  -d '{"name":"my-agent","purpose":"mockup for a client"}'
# → {"key":"mk_sb_…","expiresAt":…,"quotas":{"boards":5,"savesPerDay":200},
#    "docs":"https://maketa.pro/llms.txt"}

Limits, stated plainly: 3 registrations per day per IP; the key is shown once (the server stores only a hash) and lives 14 days; 5 boards and 200 saves per day; boards survive 30 days past key expiry and then answer 410. Sandbox keys have no access to paid AI features or billing.

Then create a board, save screens and hand over a link — over MCP with the bearer token, or over plain REST:

curl -s -X POST https://maketa.pro/maketa/api/create \
  -H 'content-type: application/json' -H 'x-maketa-key: mk_sb_…' \
  -d '{"doc":{"v":2,"name":"Demo","screens":[{"id":"s_1","name":"Home","device":"iphone-15",
       "w":393,"h":852,"bg":"#FFFFFF","slots":{"body":"n_body"},"nodes":{
       "n_body":{"id":"n_body","type":"stack","w":"fill","h":"fill","layout":{"dir":"down","pad":16,"gap":12}}}}]}}'
# → {"id":"BOARD_ID","edit":"EDIT_TOKEN","view":"VIEW_TOKEN"}

curl -s -X POST https://maketa.pro/maketa/api/save \
  -H 'content-type: application/json' -H 'x-maketa-key: mk_sb_…' \
  -d '{"id":"BOARD_ID","baseRev":0,"doc":{"v":2,"name":"Demo","screens":[{"id":"s_1","name":"Home",
       "device":"iphone-15","w":393,"h":852,"bg":"#FFFFFF","slots":{"body":"n_body"},"nodes":{
       "n_body":{"id":"n_body","type":"stack","w":"fill","h":"fill","layout":{"dir":"down","pad":16,"gap":12},"children":["n_card"]},
       "n_card":{"id":"n_card","type":"stack","w":"fill","look":{"bg":"#EEF1F6","radius":16},"layout":{"dir":"down","pad":16},"children":["n_hi"]},
       "n_hi":{"id":"n_hi","type":"text","content":{"text":"Hello!","size":24,"weight":700,"color":"#111827"}}}}]}}'
# → {"ok":true,"rev":1,…}
# → 400 {"error":"unknown_node","screen":"s_1","node":"n_body",…} if a reference dangles

# The human-facing link (read-only by default; the prototype is clickable):
#   https://maketa.pro/app/?b=BOARD_ID
# Your boards with links: GET /maketa/api/agent/boards   (header x-maketa-key)
# Cheap change check:     GET /maketa/api/rev?id=BOARD_ID → {"rev","updatedAt"}

Always send baseRev (the rev from your last /load, /save or /rev) when saving. Without it a write silently overwrites a concurrent edit; with it you get 409 {"error":"conflict","rev":N} — re-read, merge, retry. Permanent access later: the board owner issues a project key mk_pk_… from the editor.

Working end-to-end walkthrough with every endpoint: llms.txt (Russian comments, English keys).

Errors and limits

Every REST error is {error, hint}: error is a stable machine code, hint is a Russian sentence for a human. Key off error.

ResponseMeaning
409 conflictthe server has a newer revision — re-read /load, merge, save with the new baseRev
404 not_founda board with this id never existed
410 gonethe board was deleted (tombstone) — do not retry
401 bad_key / key_expiredunknown or revoked key / sandbox key older than 14 days — register a new one
403 quota_boards6th board on a sandbox key
429 quota_saves / too_many201st save today / rate limit; retryAfterMin says when
  • MCP rate limit: 600 requests per hour per key or user.
  • Board limits: 8 MB per document, 200 screens per board, 1000 nodes per screen, 5000 nodes per board, tree depth 12.
  • Transport: Streamable HTTP without SSE — POST only, application/json responses, no sessions. The server echoes the protocol version the client sent in initialize (2024-11-05 by default).

Honest limitations

  • The editor UI is in Russian. Tool names, argument schemas and error codes are English; tool descriptions returned by tools/list and the human-readable hint in error responses are currently Russian. This page is the English reference.
  • Product documentation is in Russian apart from this page. llms.txt and openapi.json have Russian prose around English keys.
  • Paid AI features (screen generation, screenshot parsing) and video rendering are billed in roubles through a Russian payment method and are not practically available outside Russia. Everything on this page — MCP, REST, boards, sharing, collaboration — is free and has no such restriction.
  • This is a mockup tool, not an app builder. It produces screens and tap-through navigation, not a running application.
  • No component variants, no design systems, no vector editing. Layout is real (stacks, wrapping grids, hug/fill sizing), but if your agent needs to operate on a production design system, it needs Figma, not this.
  • No npm package yet. The remote server is the supported way in; the local stdio server is open source — github.com/demdas/maketa-mcp (MIT), listed in the official MCP registry as pro.maketa/maketa — but not on npm.

Privacy & security

The server sees only your boards (their screen trees), your account e-mail and board ids. No payment data passes through MCP. An OAuth token grants read/write to that user's boards only; a project key is limited to one board; a sandbox key is limited to its own sandbox boards, expires in 14 days and cannot use paid features. Access can be revoked from the Maketa account page; project keys via POST /project/key/revoke.

Privacy policy: maketa.pro/privacy/ · Terms: maketa.pro/oferta/ (both in Russian).

Who makes it

Maketa is built by demda.pro, a custom software studio. The editor is free because it is how we meet the people who later need a development team. We would rather say that outright than have you wonder where the catch is.

Questions and bug reports: Telegram @demda.

Updated 2 September 2026