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
| Client | How |
|---|---|
| Claude Code | claude mcp add --transport http maketa https://maketa.pro/maketa/api/mcp — the browser opens a Maketa sign-in, no key to copy |
| Claude web & desktop | Settings → Connectors → Add custom connector → paste the URL |
| Cursor | Add to Cursor, or Settings → Tools & Integrations → New MCP Server with the URL |
| VS Code | Add to VS Code (Copilot Chat, agent mode) |
| ChatGPT | Settings → 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.
| Tool | What it does |
|---|---|
maketa_list_boards | List the boards (mockup files) the caller can access. Always the first call. |
maketa_list_screens | Screens 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_screen | One screen exactly as stored: its slots and the flat nodes map. |
maketa_get_tree | The 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_screen | New 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_node | Add 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_node | Patch 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_node | Reparent a node or reorder it among its siblings. In v2 this is "move": a node has no coordinates. |
maketa_remove_node | Delete a node with its whole subtree; references to it in actions and tab items are dropped for you. |
maketa_branch_screen | Branch a screen into an editable copy — the only way to redesign a screen that came from code. |
maketa_push_code_screen | Upsert a screen from source code by a stable key; every push is a new version ("commit from code"). |
maketa_list_branches | Branches of a screen. |
maketa_get_parent | The 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.
| Type | What it is | content |
|---|---|---|
stack | Container. Cards, lists, carousels, grids, headers and dividers are all built from it — they are palette presets, not types | — |
text | A label | text (≤5000), size 8–200, weight 100–900 in hundreds, color, align, lines (clamp), emoji |
image | A picture or its placeholder | src (data:image/… only), fit cover|contain|fill, ratio (aspect ratio instead of a height) |
button | A button — a type, not a rectangle with a label on it | text, icon, kind filled|tonal|text, size, weight, color, align |
input | A text field | placeholder, value, search, kind text|password|multiline |
choice | Radio buttons or checkboxes as one group | items [{text, on}], multi, active |
tabs | A tab bar or an in-screen switcher as one node | items [{text, icon, link (screen id), shows (node id), action}], active, variant bottom|pill|underline|segmented, color, muted, activeText |
item | A list row: icon, title, subtitle, trailing element | slots: {lead, title, sub, trail} — these are roles over nodes that already exist, not extra children |
screen | The 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:
actionis 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), withhidden: truemarking 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: 10on one node, not ten copies. A tab bar is onetabsnode, never five rectangles; a button isbutton; a text field isinput. 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"}'
| Credential | Scope | Lifetime |
|---|---|---|
| OAuth access token | all boards of the signed-in user | 1 hour (refresh token: 30 days) |
mk_pk_… project key | one board | until revoked |
mk_sb_… sandbox key | sandbox boards created with that key | 14 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.
| Response | Meaning |
|---|---|
409 conflict | the server has a newer revision — re-read /load, merge, save with the new baseRev |
404 not_found | a board with this id never existed |
410 gone | the board was deleted (tombstone) — do not retry |
401 bad_key / key_expired | unknown or revoked key / sandbox key older than 14 days — register a new one |
403 quota_boards | 6th board on a sandbox key |
429 quota_saves / too_many | 201st 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 —
POSTonly,application/jsonresponses, no sessions. The server echoes the protocol version the client sent ininitialize(2024-11-05by default).
Honest limitations
- The editor UI is in Russian. Tool names, argument schemas and error codes are English; tool descriptions returned by
tools/listand the human-readablehintin error responses are currently Russian. This page is the English reference. - Product documentation is in Russian apart from this page.
llms.txtandopenapi.jsonhave 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