Malla runs a Model Context Protocol (MCP) server, so AI agents such as Claude can do everything you can do in the web app: edit pages and styles, manage the CMS, tables and forms, upload assets, run server workflows, publish and more. This page lists how to connect, what each tool does and the limits that apply.
Every document edit goes through the same operations as the editor. It is validated the same way, shows up live on the canvas for everyone who has the page open, and you can undo it in your editor.
Connect a client
The server speaks MCP over streamable HTTP at one endpoint:
POST https://<your MCP host>/mcp
Authorization: Bearer mk_…
There is no public MCP address yet: use the MCP host of your Malla installation, for example https://mcp.example.com/mcp. Each request is answered with JSON; the server never opens a stream, so GET /mcp answers 405.
In Claude Code:
claude mcp add --transport http malla https://mcp.example.com/mcp \
--header "Authorization: Bearer mk_your_key"
In clients that read a JSON config:
{
"mcpServers": {
"malla": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"headers": { "Authorization": "Bearer mk_your_key" }
}
}
}
The tutorial Build a page with an AI agent over MCP walks through a full session.
API keys and scopes
An agent signs in with a workspace API key. To create one, open a page in the editor, press Ask AI, then the key icon (API keys). Type a name, check Write if the agent should make changes, and press Create. The key starts with mk_ and is shown only once: press Copy and store it in your agent's secret settings.
| Scope | Lets the agent |
|---|---|
| Read (always on) | Call every read tool and every action that only reads (list, get…). |
| Write (Write checked) | Everything Read allows (a write key can always read), plus document edits and every action that changes something. |
A key without Write gets This API key is read-only (or the api error read_only_key) when it tries to change something.
- Only workspace owners and admins can create, list and revoke keys.
- A key belongs to one workspace. Sites of other workspaces answer “not found”, even if you are a member there.
- An agent can create keys with the
api_keystool, but only with scopes its own key has. A read key can never create a write key. - Press Revoke in API keys to stop a key at once.
Roles
The agent can do what your role allows, on each site, exactly as in the web app. See Team and roles.
| Your role on the site | Through MCP |
|---|---|
| Viewer | Every read tool and resource. |
| Content editor | Reads, CMS content, and text edits of plain text nodes on pages. Any other page or style change is refused, by the MCP server and again by the live page. |
| Editor | Every document edit, pages, CMS, tables, forms, assets, server workflows, publish to staging. |
| Admin, owner | Everything above, plus integrations, custom domains, members, production publishes, code export, billing and API keys. |
Tools
Tools come in six families. Ids (sites, pages, nodes, classes) always come from a read tool first: start with list_sites, then list_pages, get_site and get_page.
Read
| Tool | Returns |
|---|---|
list_sites | The sites of the key's workspace you can open. |
list_pages | The pages and components of a site, with their path, kind and SEO. |
get_site | The live site document: page records, classes, design tokens, breakpoints, site variables and formulas. |
get_page | The live document of a page or component: nodes, styles, props, interactions, variables, queries and workflows. |
get_site_styles | Just the classes, tokens and breakpoints of a site. |
High-level edits
The same tools as the AI panel. Page tools take siteId and pageId; site tools take siteId.
| Tool | Does |
|---|---|
insert_subtree | Add a section or element tree, reusing your classes. |
set_styles | Set or clear styles of a node per breakpoint and state. |
set_props | Change text, links, tags and classes, or bind them to expressions. |
create_class | Create a reusable class. |
create_query | Add a page query. |
create_workflow | Add a client workflow with a trigger and steps. |
set_page_seo | Write the SEO title and description of a page. |
set_alt_text | Describe an image. |
Editor commands
editor_command runs a command from the editor's menus and shortcuts on a page, with the same cleanup (for example, deleting a node also removes the interactions that target it). Input: { siteId, pageId, command, input }. Nodes are addressed by id.
| Command | Input |
|---|---|
delete | ids |
duplicate | ids |
cut | ids; returns the clipboard payload for paste |
paste | payload, optional parent, index |
moveUp, moveDown | ids (siblings) |
wrapInFrame | ids (siblings), optional frameId |
wrapInLink | ids (siblings), optional linkId |
unwrapLink | id of a link block |
toggleHidden | ids, optional hidden |
toggleLocked | ids, optional locked |
insertNode | type, optional parent, index, props, styles, name, preset: "linkBlock" |
insertInstance | componentId, optional parent, index, styles, name |
createComponent | id of the node, optional name; the component page is created for you, and deleted again if the command then fails |
detachInstance | id of an instance; replaces it by a plain copy of its component |
nudge | ids, optional dx, dy (pixels), breakpoint |
The tool's input schema lists each command's input shape. A command with nothing to do (for example, deleting only locked nodes) is refused with an error instead of doing nothing silently.
Raw operations
For full control, send the document operations themselves. Each call is one batch of up to 500 operations, applied in order in one transaction. The whole batch is checked against the live document first: if one operation is invalid, nothing is applied and the error names its index.
page_ops(siteId,pageId,ops):setProp,setStyle,setMeta,insert,insertSubtree,move,delete,setInteraction,setVariable,setQuery,setWorkflow,setStyleBinding.site_ops(siteId,ops):setClass,renameClass,setClassStyle,setColorToken,setFontToken,setSpacingToken,setBreakpoints,setSiteVariable,setFormula,addPage,removePage,setPageSeo,setPage.
Each operation has a t field with its type; the tool's input schema describes every field. See The page document for the model behind them.
Refactors
These change the site document and every page that uses the token or class, in one go.
| Tool | Input | Does |
|---|---|---|
token_usage | siteId, kind, key | Count the references to a token across classes, tokens and pages. |
rename_token | siteId, kind, from, to | Rename a token and rewrite every var(--<kind>-<from>). |
remove_token | siteId, kind, key | Delete a token; each reference is replaced by its value. |
delete_class | siteId, classId | Delete a class and its combo classes and remove them from every node. |
kind is color, font or space.
Domain tools
Everything that is not a document edit is one tool per area, with an action argument. The arguments are { action, <ids>, query?, body? }: ids such as siteId or collectionId are top-level, query holds filters and paging, body holds what you create or change. workspaceId defaults to the key's workspace. Each action is one call of the HTTP API, with the same roles, plan limits and checks.
| Tool | Actions |
|---|---|
sites | list, create, get, update, delete |
workspace | send_invite |
pages | list, create, get, update, delete |
cms | list_collections, create_collection, get_collection, update_collection, delete_collection, list_items, create_item, get_item, update_item, delete_item, publish_item, unpublish_item, archive_item, schedule_item, list_versions, restore_version |
tables | list, create, update, delete, list_rows, insert_row, update_row, delete_row |
visitors | list, update, delete, get_roles, set_roles |
forms | list, list_recipients, get, update, list_submissions, export_csv, delete_submission |
assets | list, upload, read, delete |
integrations | list_plugins, list, create, update, delete, test |
plugins | catalog_list, catalog_get, catalog_upload, catalog_submit, catalog_withdraw, list_installed, install, update_installed, uninstall |
server_workflows | list, create, get, update, delete, test, call, list_runs, get_run |
publish | publish, history, rollback, export |
domains | list, add, refresh, remove |
templates | list, save_site, create_section, get, bundle, delete, use |
members | list, add, remove |
billing | plans, get_workspace, set_workspace_plan, portal, get_site, set_site_plan |
api_keys | list, create, revoke |
ai | run (the editor AI assistant over a selection; prefer the page tools), expression (write, fix or explain one expression from a scope shape; answers a checked proposal, not applied) |
run | query (run a data query as the editor previews it) |
Notes on a few actions:
pagescreate,updateanddeletealso update the editor's page list, like the Pages panel. A component is a page withkind: "component"and no path.pagesupdatewithseo: nullclears the SEO settings;accesssets the visitor access rule of a page ({ rule: { $expr }, redirect? }, ornullto remove it).pagesupdatewith adocreplaces the stored document and is refused while the page is open live; edit open pages with the tools above.cmsupdate_itemandserver_workflowsupdatetake an optionalversion: if someone changed the record since, the call answers 409 instead of overwriting their work.billingset_workspace_planandset_site_plananswer a checkout URL when payment is needed. Open it in a browser.workspacesend_inviteanswers 501 while invite emails are not set up on the server.
Resources
The server also offers the live documents as MCP resources, as JSON:
| URI | Content |
|---|---|
malla://site/{siteId} | The live site document, the same as get_site. |
malla://page/{siteId}/{pageId} | The live page or component document, the same as get_page. |
resources/list lists every site the key can open and its pages. Subscriptions are not supported: read a resource again to see changes.
Undo
Changes an agent makes with your key reach your open editors as your own undoable steps. Press ⌘Z to take one back. Each change the agent sends is its own step and never merges with your typing. Nobody else can undo it, and your undo never overwrites a change someone made after it.
The agent itself has no undo tool: undo is part of your editor session. To revert a change from the agent, ask it to make the opposite change.
Errors
A tool that fails returns a result with isError: true and a message, so the agent can read it and try again.
- Document tools say what went wrong and, for batches, which operation: for example
ops[3]: …. - Domain tools also return
structuredContent.errorwith the api'sstatus,code,messageanddetails. Common codes:not_found(404, also for sites you cannot open),forbidden(403, role too low),read_only_key(403),plan_limit(402),version_conflict(409, the record changed since you read it),live_document(409, the page is open live),asset_in_use(409),payload_too_large(413).
Problems with the request itself are JSON-RPC errors:
| Code | HTTP | Meaning |
|---|---|---|
-32001 | 401 | Missing, malformed, unknown or revoked API key. |
-32700 | 400 | The body is not valid JSON. |
-32600 | 200 or 413 | Not a JSON-RPC request, or the body is over 32 MiB. |
-32601 | 200 | Unknown method. |
-32602 | 200 | Missing or invalid parameters, or an unknown resource URI. |
-32002 | 200 | Resource not found. |
Limits
| What | Limit |
|---|---|
| Request body | 32 MiB per JSON-RPC request (room for a 20 MB file as base64). |
| Raw operations | 500 per page_ops or site_ops call. |
| Asset upload | 20 MB. Send base64 (or a data: URL) or a public https url, plus a filename with its extension. URLs are fetched by the server under the same rules as integrations: https only, no private or local addresses. |
| Binary reads | assets.read and publish.export return base64 up to 5 MB. Larger files: download them in the web app. |
| CSV export | forms.export_csv returns up to 1 MB of text, then says it was cut; page through the rest with list_submissions. |
| Workflow test runs | server_workflows.test waits for the run to end, up to waitMs (default 30 s, max 120 s). If it is still running, you get its latest state; poll with get_run. |
| AI runs | ai.run waits for the assistant up to 120 s. |
Plan limits, quotas and AI usage are the same as in the web app. See Plans and billing.
What is not available
A few api routes have no MCP tool on purpose:
- Account and session routes: sign up, log in and out, change password, email verification, password reset and accepting an invite. They manage a person's browser account, so an API key is refused there. Do these in the web app.
- The current user (
/auth/me) and the editor's realtime token: a key already acts as you, and MCP writes to the live rooms directly. - The Stripe webhook: only Stripe calls it.
- The live step stream of a workflow run: it is a WebSocket.
server_workflows.testandget_runpoll the run instead. - Replacing a stored page document (
PUT /pages/:id/doc): edit documents with the page tools. - Editor-only commands: selection, zoom, panels, preview, copy, and undo or redo have no effect on a document.