@ttsalpha/mirra 0.0.0-stage → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +133 -2
- package/dist/index.js +41477 -0
- package/dist/plugin/main.js +1308 -0
- package/dist/plugin/manifest.json +58 -0
- package/dist/plugin/ui.html +282 -0
- package/package.json +54 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Son Tran @ttsalpha
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALING IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,134 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Mirra
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Two-way MCP server for Figma: an agent reads a design and renders one back through the
|
|
4
|
+
Figma Plugin API, so it needs no REST token and has no per-month call limit.
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
Claude ⇄ stdio ⇄ MCP server ⇄ ws://localhost:51730 ⇄ plugin iframe ⇄ plugin sandbox ⇄ Figma
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`mirra_read` returns the same tree `mirra_render` accepts, so one format covers reading a
|
|
11
|
+
design and building one, and an agent can write code from that tree without learning a
|
|
12
|
+
second shape.
|
|
13
|
+
|
|
14
|
+
## Requirements
|
|
15
|
+
|
|
16
|
+
- **Figma Desktop.** A development plugin can only be imported there, so the browser app
|
|
17
|
+
and Linux (which has no desktop build) are both out.
|
|
18
|
+
- **Node 24 or newer**, and pnpm to build.
|
|
19
|
+
- macOS or Windows. Only macOS is tested so far.
|
|
20
|
+
|
|
21
|
+
## Tools
|
|
22
|
+
|
|
23
|
+
| Tool | What it does |
|
|
24
|
+
| --------------------- | --------------------------------------------------------------------------------------- |
|
|
25
|
+
| `mirra_render` | Build a whole screen from one declarative tree |
|
|
26
|
+
| `mirra_read` | Read the selection, or a node, back as that same tree |
|
|
27
|
+
| `mirra_patch` | `set` / `append` / `replace` / `move` / `component` / `remove` by key, as one undo step |
|
|
28
|
+
| `mirra_find` | Locate layers by name, kind or key, and select them |
|
|
29
|
+
| `mirra_pages` | List the pages, add one, or switch to one |
|
|
30
|
+
| `mirra_screenshot` | Export a layer as PNG and hand the image back |
|
|
31
|
+
| `mirra_tokens` | List variables and styles, with the `$name` a tree refers to |
|
|
32
|
+
| `mirra_define_tokens` | Create or update variables, modes, aliases and paint/text/effect styles |
|
|
33
|
+
| `mirra_export_assets` | Write layers to disk as PNG or SVG |
|
|
34
|
+
| `mirra_ping` | Check the connection and report the open file |
|
|
35
|
+
| `mirra_exec` | Run Plugin API code; see **Running arbitrary code** below |
|
|
36
|
+
|
|
37
|
+
The tree mirrors flexbox: `layout: {dir, gap, pad, justify, align}`, and `w`/`h` take a
|
|
38
|
+
number, `"fill"` or `"hug"`; a frame with a layout hugs its content by default. Node types
|
|
39
|
+
are `frame`, `text`, `rect`, `ellipse`, `instance`, `svg` and `image`. Colours are hex
|
|
40
|
+
strings, a gradient, or `"$color/primary"` to bind a variable.
|
|
41
|
+
|
|
42
|
+
Every node also takes `minW`/`maxW`/`minH`/`maxH`, `constraints: {h, v}` and `at: {x, y}` to
|
|
43
|
+
pin it: inside an auto layout that lifts the node out of the flow, which is how a badge sits
|
|
44
|
+
on a corner.
|
|
45
|
+
|
|
46
|
+
`svg` and `image` take `src` as inline content or as a path on disk: the server reads the
|
|
47
|
+
file and inlines it, then records the path so `mirra_read` names it again rather than
|
|
48
|
+
handing back a data URI.
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npx @ttsalpha/mirra init
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
That copies the plugin to `~/.mirra/plugin`, the server to `~/.mirra/server` when it is
|
|
57
|
+
running from an npx cache that npm may clear later, and registers the server with every
|
|
58
|
+
client it finds: Claude Desktop, Cursor and VS Code by merging one entry into their config, Claude
|
|
59
|
+
Code through its own CLI. Each file is backed up beside itself first, and `--no-config`
|
|
60
|
+
skips all of it. The command is written with the real path of the Node binary that ran it,
|
|
61
|
+
because a version manager puts `node` behind a path that changes with every shell and a
|
|
62
|
+
GUI client never sees it.
|
|
63
|
+
|
|
64
|
+
Then, by hand:
|
|
65
|
+
|
|
66
|
+
1. Figma Desktop → Plugins → Development → Import plugin from manifest… →
|
|
67
|
+
`~/.mirra/plugin/manifest.json`
|
|
68
|
+
2. **Restart the clients**, or they will not see the tools
|
|
69
|
+
|
|
70
|
+
And once per working session: open a file in Figma, run the Mirra plugin, leave its panel
|
|
71
|
+
open. Figma has no background plugins, so closing the panel ends the connection.
|
|
72
|
+
|
|
73
|
+
The plugin holds a connection to every server it finds, so several clients can work on the
|
|
74
|
+
same file at once, and one that starts later is picked up within a few seconds. Operations
|
|
75
|
+
run one at a time, so two clients cannot interleave or land in the same undo step.
|
|
76
|
+
|
|
77
|
+
`mirra doctor` checks the port, the installed plugin and the live connection, so it says
|
|
78
|
+
which half is broken.
|
|
79
|
+
|
|
80
|
+
| Variable | What it does |
|
|
81
|
+
| -------------------- | --------------------------------------------------------------------------- |
|
|
82
|
+
| `MIRRA_PORT` | Pin one port instead of scanning 51730–51739 |
|
|
83
|
+
| `MIRRA_ROOT` | Resolve the paths tools are given from here, and refuse anything outside it |
|
|
84
|
+
| `MIRRA_ALLOW_EXEC` | Register `mirra_exec` at all; the panel still has to allow it |
|
|
85
|
+
| `MIRRA_ALLOW_ORIGIN` | Accept one more browser origin on the socket |
|
|
86
|
+
|
|
87
|
+
Without `MIRRA_ROOT`, `mirra_export_assets` writes wherever its `outDir` points and `svg`
|
|
88
|
+
and `image` read whatever path they are given, both relative to the working directory. A
|
|
89
|
+
client that starts the server outside a project, Claude Desktop among them, should set it.
|
|
90
|
+
|
|
91
|
+
## Claude Desktop
|
|
92
|
+
|
|
93
|
+
`init` covers Desktop, but every release also attaches a `.mcpb` bundle, which is less work
|
|
94
|
+
for anyone who would rather not open a terminal. It installs with one double-click, runs on
|
|
95
|
+
Desktop's own Node, and turns the port, the project folder and the code execution switch
|
|
96
|
+
into a form rather than environment variables. Figma Desktop and the plugin import are
|
|
97
|
+
still required.
|
|
98
|
+
|
|
99
|
+
## Running arbitrary code
|
|
100
|
+
|
|
101
|
+
`mirra_exec` runs whatever Plugin API code the agent writes against the open document. It
|
|
102
|
+
takes two keys, which are deliberately held by different people: the server only registers
|
|
103
|
+
the tool when `MIRRA_ALLOW_EXEC` is set, and the plugin only runs it while **Allow code
|
|
104
|
+
execution** is ticked in its panel. The tick resets every time the plugin runs.
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
claude mcp add -s user mirra-exec -e MIRRA_ALLOW_EXEC=1 -- npx -y @ttsalpha/mirra serve
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The socket listens on loopback and refuses any browser origin other than Figma's, so a page
|
|
111
|
+
you happen to have open cannot reach it. It is still unauthenticated against other
|
|
112
|
+
processes on the same machine: anything already running as you can talk to it while the
|
|
113
|
+
plugin is open.
|
|
114
|
+
|
|
115
|
+
## How it fits together
|
|
116
|
+
|
|
117
|
+
`packages/schema` holds the zod tree and the wire protocol, `packages/plugin` the Figma
|
|
118
|
+
side, `packages/server` the MCP server and CLI. The plugin bundle ships inside the server
|
|
119
|
+
package, which is what `mirra init` installs.
|
|
120
|
+
|
|
121
|
+
Tests run `render` and `read` against a hand-written fake of the Figma sandbox, so the
|
|
122
|
+
round trip is checked without Figma open; the server tests spawn `mirra serve` over stdio
|
|
123
|
+
with a stub plugin on the socket.
|
|
124
|
+
|
|
125
|
+
Three things Figma imposes on this setup. Its manifest validator rejects an IP literal, so
|
|
126
|
+
`networkAccess` lists `ws://localhost:<port>` rather than `127.0.0.1`, and the server
|
|
127
|
+
listens on both loopback addresses because the plugin's renderer resolves `localhost` to
|
|
128
|
+
`::1` first. That renderer also opens sockets slowly, so the plugin races every candidate
|
|
129
|
+
port at once instead of trying them one at a time; the panel probes `/health` over HTTP
|
|
130
|
+
when a round fails, which tells a missing server apart from a blocked one.
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
[MIT](LICENSE) © 2026 Son Tran @ttsalpha
|