- Essay
- #mcp
- #figma
- #open-source
Mirra: a two-way MCP server for Figma
Mirra lets an agent read a Figma or FigJam design and render one back through the Plugin API, with no REST token and no monthly call limit.
Mirra is an open-source MCP server for Figma that works in both directions. An agent can read a design or a FigJam board, and render one back, using the Figma Plugin API. Because it talks to the plugin running inside Figma Desktop, it needs no REST token and has no monthly call limit.
The code is on GitHub under the MIT license.
Why the Plugin API
Most Figma integrations go through the REST API. That means a token to create and store, and a rate limit to plan around. Mirra skips both by running as a Figma plugin and letting the agent drive it over a local connection.
The trade-off is that Figma Desktop has to be open, with the plugin panel running. For a design tool an agent works alongside, that felt like a fair price.
Try it
npx @ttsalpha/mirra initThen restart your agent. init adds Mirra to Claude Desktop, Claude Code, Cursor, VS Code and Antigravity, and installs the plugin. It needs Figma Desktop (macOS or Windows) and Node 24+.
In Figma, open a file and run Plugins → Development → Mirra, and keep the panel open. It reads Disconnected until your agent has started Mirra. If it still does not connect, npx @ttsalpha/mirra doctor tells you why.
What the agent can do
Mirra exposes a small set of tools, each doing one thing:
| Tool | What it does |
|---|---|
mirra_render | Build a whole screen, or a FigJam board, from one declarative tree |
mirra_read | Read the selection, or a node, back as that same tree |
mirra_patch | Change nodes by key, as one undo step |
mirra_find | Locate layers by name, kind or key, and select them |
mirra_pages | List the pages, add one, or switch to one |
mirra_screenshot | Export a layer as PNG and hand the image back |
mirra_tokens | List variables and styles |
mirra_define_tokens | Create or update variables, modes, aliases and styles |
mirra_export_assets | Write layers to disk as PNG or SVG |
There is also mirra_ping to check the connection, and mirra_exec, which I will come back to.
One tree, both directions
The part I care about most is that mirra_render and mirra_read share a single format. An agent reads a layer as a tree, and can write code from exactly what it read. The same tree goes back in to build or change a design.
The format is flexbox-like. Nodes are frame, text, rect, ellipse, instance, svg and image. Sizes are a number, "fill" or "hug", and a "$name" binds a field to a variable.
{
"type": "frame",
"key": "card",
"layout": { "dir": "vertical", "gap": 12, "pad": 16 },
"fill": "#ffffff",
"radius": 12,
"children": [
{ "type": "text", "text": "Welcome back", "size": 24 },
{ "type": "rect", "w": "fill", "h": 1, "fill": "#eeeeee" }
]
}A key on a node lets mirra_patch address it later, so an agent can adjust one part of a screen without rebuilding it.
FigJam too
FigJam boards use the same idea with different nodes: section, sticky, shape, connector, table, code, link and gif. A board is one section, since FigJam has no frames, auto layout or variables. A connector joins nodes by key and routes itself.
How it works
Agent ⇄ stdio ⇄ MCP server ⇄ ws://localhost:51730 ⇄ plugin ⇄ FigmaThe agent talks MCP over stdio to the server. The server talks to the plugin over a WebSocket on loopback, and the plugin calls the Plugin API inside Figma.
Running arbitrary code
mirra_exec runs any Plugin API code the agent writes, so it is off by default. Turning it on takes two separate switches: MIRRA_ALLOW_EXEC on the server, and Allow code execution ticked in the panel, which resets on every run.The socket only listens on loopback and rejects browser origins other than Figma's. It does not authenticate against other processes running as you while the plugin is open, which is worth knowing before you leave the panel running.
Under the hood
The repo is a small monorepo: packages/schema holds the tree and the wire protocol, packages/plugin the Figma side, and packages/server the MCP server and CLI. Tests run against a fake of the Figma sandbox, so they do not need Figma open. The fake does not reproduce FigJam's quirks, so FigJam changes still need a try in a real FigJam file.
If you try it, the issues are the best place to tell me what breaks.