figma-rust-mcp 6.0.0 → 7.0.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/README.md CHANGED
@@ -10,12 +10,12 @@ Enables AI agents (Google Antigravity, Claude Code, Cursor, Windsurf, VS Code, Z
10
10
 
11
11
  - **Lossless Rust Tree Compression**: Compact/minimal tree responses share identical style bundles when the complete JSON becomes smaller. Node IDs, hierarchy, text, geometry and state differences remain intact. Full detail bypasses Rust compression; no heuristic merging of screens or repeated items.
12
12
  - **Pure Rust Native Performance**: Single native binary, no runtime or GC. Measure it on your machine with `npm run bench` or the plugin's **Bench** tab (see [Benchmarks](#-benchmarks)).
13
- - **Scoped In-Memory Index (`figma_index`)**: Indexes page roots first and loads frame descendants on demand. Search reports the indexed scope and completeness.
13
+ - **Scoped In-Memory Index (`figma_read` search)**: Indexes page roots first and loads frame descendants on demand. Search reports the indexed scope and completeness.
14
14
  - **Binary IPC & Chunk Streaming**: Powered by **MessagePack** (`rmp-serde`) and progressive subtree chunking for instant transfers of massive design files.
15
15
  - **Revisioned Updates**: Property patches update affected nodes; revision gaps trigger a fresh snapshot.
16
16
  - **Instant Design-to-Code (`figma_to_code`)**: Compiles Figma frames into clean, semantic components (**React + Tailwind**, **React Native**, **Vue 3 + Tailwind**, **HTML**, **SwiftUI**).
17
- - **Design Token Exporter (`figma_get_tokens`)**: Exports variables and styles to **Tailwind config**, **CSS custom properties** (`:root` & dark mode), **TypeScript consts**, or **W3C DTCG Token Studio JSON**.
18
- - **Batch Asset Extractor (`figma_export_assets` / `figma_export_asset`)**: Extracts SVG icons and raster images directly to project folders (`outputPath`) with auto-generated TypeScript barrel exports (`index.ts`) — zero chat token bloating.
17
+ - **Design Token Exporter (`figma_to_code` `target: "tokens"`)**: Exports variables and styles to **Tailwind config**, **CSS custom properties** (`:root` & dark mode), **TypeScript consts**, or **W3C DTCG Token Studio JSON**.
18
+ - **Batch Asset Extractor (`figma_prepare_design` `only: "assets"`)**: Extracts SVG icons and raster images directly to project folders (`outputPath`) with auto-generated TypeScript barrel exports (`index.ts`) — zero chat token bloating.
19
19
  - **Sandboxed JS Drawing Engine (`figma_write`)**: Powered by **Boa** (pure Rust ECMAScript engine) with built-in asset resolvers and 7 icon packs (*Ionicons, Lucide, Tabler, Bootstrap, Fluent, Phosphor*).
20
20
  - **Multi-Tab / Multi-Session Support**: Seamlessly connects to multiple open Figma files simultaneously.
21
21
  - **100% Localhost Privacy**: All communication stays strictly on `127.0.0.1:41730`.
@@ -212,25 +212,58 @@ Figma plugin only if the reported hash does not change.
212
212
 
213
213
  ## 🛠️ MCP Tools Reference
214
214
 
215
- `figma-rust-mcp` provides **15 first-class MCP tools**:
215
+ `figma-rust-mcp` exposes **7 tools**. Each covers one job; modes select the
216
+ variant (`operation`, `task`, `only`, `target`). The plugin **Usage** tab shows
217
+ how often each tool and mode is called.
216
218
 
217
219
  ### 1. `figma_status`
218
- Checks live bridge connection status, connected Figma tabs/files, in-memory index health, queue length, and latency statistics.
220
+ Bridge connection, connected tabs (`sessionId`), index health, queue length and latency.
221
+ - **`task`**: `"start"` (with `frameId`) reserves a frame and returns a `taskId`; `"end"` (with `taskId`) releases it; `"list"` shows reservations. See [Multiple tabs and frame tasks](#multiple-tabs-and-frame-tasks).
219
222
 
220
- ### 2. `figma_inspect_node`
221
- Reads a specific node by ID or name through the canonical flat `read_nodes` response. Select fields explicitly to control the work performed.
223
+ ### 2. `figma_read`
224
+ Every read, search and export. **`operation`**:
225
+ - **Nodes**: `read_nodes` (canonical flat API), `inspect_node` (one node with CSS, layout, tokens, typography; defaults to the selection), `get_selection`, `get_spec`, `get_page_nodes`, `scan_design`, `get_css`.
226
+ - **Search** (in-memory index): `search_nodes` (`query`, `nodeType`), `search_components`, `search_styles` (`styleType`), `search_variables` (`collection`), `subtree`, `typography` (text/font table with mixed runs), `index_status` (index health, cache hits, tool timings), `refresh` (page roots, or expand `nodeId`; `includeComponents`).
227
+ - **Design system**: `rules` (markdown rule sheet of tokens, text styles and components), `get_styles`, `get_variables`, `get_tokens`, `get_local_components`, `get_component_map`, `get_unmapped_components`.
228
+ - **Export**: `screenshot`, `export_image` (png/jpg, `scale`), `export_svg`. Pass `outputPath` to save to disk instead of returning data.
222
229
 
223
- ### 3. `figma_to_code`
224
- Compiles any Figma node (Frame, Component, Section, or selection) directly into clean, production-ready component code.
225
- - **`framework`**: `"react-tailwind"` (default), `"react-native"`, `"vue-tailwind"`, `"html"`, `"swiftui"`.
226
- - **`outputPath`**: Directly saves the generated component to your codebase (e.g. `src/components/UserProfileCard.tsx`).
227
- - **`componentName`**: Custom component name override.
230
+ Compact/minimal `get_selection` and `get_design` responses may contain a root
231
+ `_compression` object with `version: 1` and a `styles` dictionary. For each node
232
+ with `styleRef`, merge `_compression.styles[node.styleRef]` into that node and
233
+ remove `styleRef`; remove root `_compression` to reconstruct the plugin payload
234
+ exactly. Style references are dictionary lookups, not inherited CSS. Payloads
235
+ with reserved-field collisions or no net byte savings stay inline. `detail: "full"` bypasses this Rust transformation and queries the plugin rather than a
236
+ compact selection cache. Plugin detail, visibility, precision, depth and node
237
+ budgets still determine the source payload; compression cannot restore fields
238
+ omitted there. Code generation continues to consume inline styles.
239
+
240
+ No token-reduction or AI-latency percentage is claimed without a tokenizer and
241
+ end-to-end benchmark on real designs.
242
+
243
+ ### 3. `figma_write`
244
+ Executes JavaScript draw commands inside the sandboxed VM to build or modify designs on the Figma canvas.
245
+ - Supports Auto-layout (`layoutMode`, `itemSpacing`, `padding`), typography, fills, strokes, corner radius, drop shadows, and component creation.
246
+ - Supports icon loading: `figma.loadIcon("ionicons", "heart", { size: 24, fill: "#ff4757" })`.
247
+ - Supports image insertion: `figma.loadImage(url, { width: 300, height: 200 })`.
248
+ - Supports Design Tokens: `createVariableCollection`, `createVariable`, `addVariableMode`, `applyVariable`.
249
+ - Supports Prototyping & Reactions: `setReactions`, `getReactions`, `setScrollBehavior`.
250
+
251
+ ### 4. `figma_docs`
252
+ Built-in documentation, design rules, layout guidelines and examples for `figma_write`.
253
+ - **`section`**: `"rules"` | `"layout"` | `"api"` | `"tokens"` | `"icons"`
254
+
255
+ ### 5. `figma_prepare_design`
256
+ **Start here for design-to-code.** One call returns a grounding pack: screenshot, every visible text (through expanded instances, with coverage), SVG icons exported to `iconDir` with import statements, matched codebase components and an implementation checklist.
257
+ - **`nodeId`**, **`iconDir`** (default `src/assets/icons`), **`projectDir`** (default `.`), **`detail`**, **`outputPath`**.
258
+ - **`only: "assets"`**: just export SVG icons (`iconDir`) and raster images (`imageDir`, default `public/images`) with an `index.ts` barrel (`createBarrel`).
259
+ - **`only: "components"`**: just list codebase components (`src/components`, `components`, ...) and match `nodeId` against them.
228
260
 
229
- ### 4. `figma_get_tokens`
230
- Exports Figma Variables (Design Tokens), Color Styles, Typography Styles, and Elevation/Shadow Styles directly into frontend code.
231
- - **`format`**: `"tailwind"` (`tailwind.config.js` theme.extend), `"css"` (`:root` CSS custom properties with light/dark theme modes), `"typescript"` (`tokens.ts`), `"w3c"` (W3C DTCG Token Studio JSON), or `"json"`.
232
- - **`outputPath`**: Writes directly to disk (e.g. `src/styles/tokens.css` or `tailwind.config.js`).
233
- - **`collection`** / **`mode`** / **`prefix`**: Optional filters and naming prefixes.
261
+ ### 6. `figma_to_code`
262
+ Compiles a node (or the selection) into component code.
263
+ - **`framework`**: `"react-tailwind"` (default), `"react-shadcn"`, `"react-native"`, `"vue-tailwind"`, `"html"`, `"swiftui"`, `"clean-spec"`.
264
+ - **`outputPath`**, **`componentName`**.
265
+ - **`target: "tokens"`**: export variables and styles. **`format`**: `"css"` (default, `:root` + light/dark modes), `"tailwind"`, `"typescript"`, `"w3c"` (DTCG), `"json"`; **`collection`** / **`mode`** / **`prefix`**; **`outputPath`**.
266
+ - **`target: "project"`**: scaffold a design system — `globals.css` (Tailwind layers + CSS variables), `tailwind.config.ts` and one component per local component / component set (first variant) in `componentDir`. Existing files and components already in the codebase are skipped unless **`overwrite`**. Options: **`projectDir`**, **`componentDir`** (`src/components/ui`), **`stylesPath`** (`src/styles/globals.css`), **`tailwindConfigPath`** (`tailwind.config.ts`), **`limit`** (30, max 100); frameworks `react-tailwind`, `react-shadcn`, `vue-tailwind`, `html`.
234
267
 
235
268
  Token exports use schema v2 and preserve full paint/effect stacks, alpha, gradient
236
269
  transforms, collection modes and raw alias values. CSS, Tailwind, TypeScript and
@@ -252,99 +285,26 @@ Run `npm run build:plugin` after changing plugin source, and `npm run test:token
252
285
  for the token/contract/version regression suite. Existing background servers need
253
286
  a restart with the new binary to load the updated runtime.
254
287
 
288
+ ### 7. `figma_verify_ui`
289
+ Compares rendered HTML/React styles against the Figma spec: size, per-side padding, gap, colors, per-corner radius and typography. Returns a match percentage, discrepancies and CSS/Tailwind fixes.
290
+ - **`nodeId`** / **`nodeName`**, **`computedStyles`**, **`url`** / **`selector`**.
291
+ - Typography: pass a text `nodeId` and browser `computedStyles` (`font-size` in px, numeric `font-weight`, `font-family`). Mixed text accepts `computedStyles.segments`, an ordered array matching the Figma runs. Fractional pixels are preserved.
255
292
 
256
- ### 5. `figma_get_selection`
257
- Inspection of currently selected layers/frames, with shared-style compression when it reduces response size.
258
- - **`detail`**: `"compact"` (default), `"minimal"`, `"full"`.
293
+ ### Legacy tool names
259
294
 
260
- Compact/minimal `get_selection` and `get_design` responses may contain a root
261
- `_compression` object with `version: 1` and a `styles` dictionary. For each node
262
- with `styleRef`, merge `_compression.styles[node.styleRef]` into that node and
263
- remove `styleRef`; remove root `_compression` to reconstruct the plugin payload
264
- exactly. Style references are dictionary lookups, not inherited CSS. Payloads
265
- with reserved-field collisions or no net byte savings stay inline. `detail: "full"` bypasses this Rust transformation and queries the plugin rather than a
266
- compact selection cache. Plugin detail, visibility, precision, depth and node
267
- budgets still determine the source payload; compression cannot restore fields
268
- omitted there. Code generation continues to consume inline styles.
269
-
270
- No token-reduction or AI-latency percentage is claimed without a tokenizer and
271
- end-to-end benchmark on real designs.
272
- - **`depth`**: Tree depth limit or `"full"`.
273
-
274
- ### 6. `figma_export_asset`
275
- Exports any Figma node/layer directly into PNG, JPG, or SVG.
276
- - **`outputPath`**: When specified (e.g. `src/assets/logo.png`), saves directly to disk and returns minimal JSON metadata instead of bloating context with large base64 strings.
277
- - **`format`**: `"png"` (default), `"jpg"`, `"svg"`.
278
- - **`scale`**: Scaling factor (default `2` for high-res).
279
-
280
- ### 7. `figma_export_assets`
281
- Batch extracts all SVG icons and PNG images from a Figma frame/page directly into project directories.
282
- - **`iconDir`**: Directory path for SVG icons (e.g. `src/assets/icons`).
283
- - **`imageDir`**: Directory path for raster images (e.g. `public/images`).
284
- - **`createBarrel`**: Automatically creates an `index.ts` barrel export file in `iconDir`.
285
-
286
- ### 8. `figma_index`
287
- In-memory queries against pre-indexed Figma file structures (latency for your file: plugin **Bench** tab).
288
- - **`operation`**:
289
- - `"status"`: View index health and node counts.
290
- - `"search_nodes"`: Search nodes by text name, query, and type (`FRAME`, `TEXT`, `COMPONENT`, `INSTANCE`).
291
- - `"get_node"`: Instant node lookup by ID.
292
- - `"search_components"`: Find component sets and variants.
293
- - `"search_styles"`: Find paint, text, and effect styles.
294
- - `"search_variables"`: Find design token variables by name or collection.
295
- - `"refresh"`: Refresh page roots, or expand the frame specified by `nodeId`.
296
- - `"subtree"`: Read/cache one `nodeId`, default depth 2 and 1000 nodes. Increase `depth` or `maxNodes` only for the section you need; check `meta` for truncation.
297
- - `"typography"`: Rust-generated text/font table for one `nodeId`, with mixed-style runs preserved. Default limit 200 rows. Null font fields mean unknown/mixed, not a guessed default.
298
-
299
- `status` includes per-tab dispatch cache hits/misses, bridge calls and total bridge wait time, plus process-wide tool call counts and total/average milliseconds. Node changes invalidate related subtrees; style changes, unknown events and global writes invalidate the whole cache. Detailed subtree data is loaded on demand. The initial index remains shallow.
300
-
301
- For typography verification, call `figma_verify_ui` with a text `nodeId` and browser `computedStyles` (`font-size` in px, numeric `font-weight`, `font-family`). Mixed text accepts `computedStyles.segments`, an ordered array of CSS objects matching the Figma runs. Size checks preserve fractional pixels.
302
-
303
- ### 9. `figma_read`
304
- Universal reader for advanced queries:
305
- - **Design-to-code**: `read_nodes`, `get_css`, `get_component_map`, `get_unmapped_components`.
306
- - **Inspection & Hierarchy**: `read_nodes`, `get_selection`, `get_page_nodes`, `scan_design`, `search_nodes`.
307
- - **Design Systems**: `get_styles`, `get_variables`, `get_local_components`, `get_tokens`.
308
- - **Visuals & Canvas**: `screenshot`, `export_svg`, `export_image`, `get_viewport`.
309
-
310
- ### 10. `figma_write`
311
- Executes JavaScript draw commands inside the sandboxed VM to build or modify designs on the Figma canvas.
312
- - Supports Auto-layout (`layoutMode`, `itemSpacing`, `padding`), typography, fills, strokes, corner radius, drop shadows, and component creation.
313
- - Supports icon loading: `figma.loadIcon("ionicons", "heart", { size: 24, fill: "#ff4757" })`.
314
- - Supports image insertion: `figma.loadImage(url, { width: 300, height: 200 })`.
315
- - Supports Design Tokens: `createVariableCollection`, `createVariable`, `addVariableMode`, `applyVariable`.
316
- - Supports Prototyping & Reactions: `setReactions`, `getReactions`, `setScrollBehavior`.
317
-
318
- ### 11. `figma_rules`
319
- Audits the current Figma document and generates a complete design system rule sheet (color tokens, typography styles, variables, component catalog) from the in-memory cache.
295
+ Tools from v6 and earlier are hidden from `tools/list` but still accepted, so existing prompts keep working:
320
296
 
321
- ### 12. `figma_docs`
322
- Fetches built-in documentation, design rules, layout guidelines, and code examples for `figma_write`.
323
- - **`section`**: `"rules"` | `"layout"` | `"api"` | `"tokens"` | `"icons"`
324
-
325
- ### 13. `figma_verify_ui`
326
- Validates and compares actual rendered HTML/React UI styles and layout against Figma design specifications.
327
- - Checks dimensions, padding, flex gap, colors, border-radius, and typography.
328
- - Returns an exact match percentage (`match_percentage`), discrepancy breakdown, and actionable CSS/Tailwind fixes.
329
- - **`nodeId`** / **`nodeName`**: Target Figma node spec.
330
- - **`computedStyles`**: Key-value pairs of computed CSS properties from browser/component inspection.
331
- - **`url`** / **`selector`**: Contextual target URL or CSS selector inspected.
332
-
333
- ### 14. `figma_match_components`
334
- Scans your local codebase directories (`src/components`, `components/ui`) to discover existing React/Vue components and matches them directly against Figma design layers, preventing duplicate component creation and ensuring reuse of existing design systems.
335
- - **`projectDir`**: Base directory of your project (default: current directory `"."`).
336
- - **`nodeId`**: Optional Figma node ID to check specific match against local components.
337
-
338
- ### 15. `figma_prepare_design`
339
- **All-In-One Multimodal Grounding Pack for AI**: The ultimate single-call tool for code generation that eliminates missing UI elements and icon guesswork.
340
- - Simultaneously extracts **100% of visible text elements** (greeting, badges, timestamps, labels).
341
- - Automatically **exports all SVG vector icons** into your local project directory (`iconDir`) and provides exact TypeScript import statements.
342
- - Captures high-res **visual canvas screenshots** saved locally for immediate multimodal LLM inspection.
343
- - Discovers existing components in your codebase for direct reuse.
344
- - Generates an actionable **Implementation Checklist** ensuring complete design fidelity in 1 single shot.
345
- - **`nodeId`**: Target Figma screen/frame ID.
346
- - **`iconDir`**: Target directory for SVG icons (e.g. `src/assets/icons` or `assets/images`).
347
- - **`projectDir`**: Base directory for component discovery.
297
+ | v6 tool | v7 call |
298
+ |---|---|
299
+ | `figma_get_selection` | `figma_read` `operation: "get_selection"` |
300
+ | `figma_inspect_node` | `figma_read` `operation: "inspect_node"` |
301
+ | `figma_export_asset` | `figma_read` `operation: "export_image"` / `"export_svg"` |
302
+ | `figma_index` | `figma_read` search operations, `index_status`, `refresh` |
303
+ | `figma_rules` | `figma_read` `operation: "rules"` |
304
+ | `figma_get_tokens` | `figma_to_code` `target: "tokens"` |
305
+ | `figma_export_assets` | `figma_prepare_design` `only: "assets"` |
306
+ | `figma_match_components` | `figma_prepare_design` `only: "components"` |
307
+ | `figma_task` | `figma_status` `task: "start" \| "end" \| "list"` |
348
308
 
349
309
  ---
350
310
 
@@ -354,13 +314,13 @@ Each plugin runtime has its own `sessionId`; tabs of the same file share a
354
314
  `documentId`. Use `figma_status` to discover tabs. With multiple connected tabs,
355
315
  pass an exact `sessionId`; the server rejects ambiguous routing.
356
316
 
357
- For multiple agents editing one file, call `figma_task` with `action: "start"`,
317
+ For multiple agents editing one file, call `figma_status` with `task: "start"`,
358
318
  `sessionId` and `frameId`, then pass the returned `taskId` on subsequent tools.
359
319
  Each task reserves a separate FRAME directly under a PAGE or SECTION, without
360
320
  shared component definitions. Writes stay inside that frame; global styles,
361
321
  variables, page and selection changes are blocked. A frame cannot be reserved
362
- twice, including across tabs of the same document. Release it with `figma_task`
363
- using `action: "end"` and `taskId`; restart tasks after a plugin reload.
322
+ twice, including across tabs of the same document. Release it with `figma_status`
323
+ using `task: "end"` and `taskId`; restart tasks after a plugin reload.
364
324
 
365
325
  Tools on one tab run sequentially, including every awaited operation in a
366
326
  `figma_write` call. Different tabs run independently. `npm run test:sessions`
@@ -373,14 +333,14 @@ Version 4 requires both server and plugin runtime protocol 3. Update both and
373
333
  restart the plugin. Opening the bridge indexes only visible top-level nodes on
374
334
  the active page, plus local styles and variables. Selection expands the selected
375
335
  frame. Component catalogue discovery is deferred; request it with
376
- `figma_index({ operation: "refresh", includeComponents: true })`.
336
+ `figma_read({ operation: "refresh", includeComponents: true })`.
377
337
 
378
338
  Search covers indexed scopes only. Inspect `scope` and `complete` in search
379
339
  responses; an empty result does not prove a layer is absent from the file.
380
340
  Refresh a specific frame to expand its scope:
381
341
 
382
342
  ```js
383
- figma_index({ operation: "refresh", nodeId: "123:456", expandInstances: true })
343
+ figma_read({ operation: "refresh", nodeId: "123:456", expandInstances: true })
384
344
  figma_read({ operation: "read_nodes", nodeId: "123:456",
385
345
  fields: ["geometry", "content", "text", "style", "layout", "tokens", "component"],
386
346
  limit: 200, expandInstances: true })
@@ -406,9 +366,9 @@ options; no master component is substituted for an instance.
406
366
 
407
367
  Breaking output changes: public `get_design`, `get_design_context` and
408
368
  `get_node_detail` aliases now return the same envelope as `read_nodes`.
409
- `figma_inspect_node` and index `get_node`, `subtree`, `typography` also return
369
+ `figma_read` `inspect_node`, `subtree` and `typography` also return
410
370
  that envelope. Replace nested `children` consumers with `nodes` and parent/child
411
- IDs. Replace index `maxNodes` with `limit` and cursor pagination. Internal
371
+ IDs. Replace `subtree` `maxNodes` with `limit` and cursor pagination. Internal
412
372
  script helpers used for code generation retain their specialized formats.
413
373
 
414
374
  Canvas traversal reads each child list once, yields after an 8ms slice, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "figma-rust-mcp",
3
- "version": "6.0.0",
3
+ "version": "7.0.0",
4
4
  "description": "Claude Code to Figma — Bidirectional MCP bridge. AI draws UI on Figma canvas, reads designs back. Works with Claude, Cursor, VS Code, Windsurf, Antigravity. No API key needed.",
5
5
  "mcpName": "io.github.BuiHung1612/figma-rust-mcp",
6
6
  "type": "module",
@@ -3,9 +3,14 @@ import assert from 'node:assert/strict';
3
3
  import { spawn } from 'node:child_process';
4
4
  import { createServer } from 'node:net';
5
5
  import { createInterface } from 'node:readline';
6
- import { readFileSync } from 'node:fs';
6
+ import { mkdtempSync, readFileSync } from 'node:fs';
7
+ import { tmpdir } from 'node:os';
8
+ import { join } from 'node:path';
7
9
  import vm from 'node:vm';
8
10
 
11
+ // Spawned servers persist tool usage under HOME; keep the user's real file untouched.
12
+ process.env.HOME = process.env.USERPROFILE = mkdtempSync(join(tmpdir(), 'figma-mcp-test-'));
13
+
9
14
  const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
10
15
  const deferred = () => { let resolve; const promise = new Promise(r => { resolve = r; }); return { promise, resolve }; };
11
16
  async function until(predicate) {
@@ -106,11 +111,11 @@ test('multiple tabs, reconnect ownership and independent frame tasks across MCP
106
111
  assert.equal(ambiguous.isError, true); assert.match(ambiguous.content[0].text, /Ambiguous/);
107
112
  const missing = await rpc('figma_read', { operation: 'get_design', sessionId: 'missing' });
108
113
  assert.equal(missing.isError, true);
109
- const taskA = decode(ok(await first('figma_task', { action: 'start', sessionId: 'tab-a', frameId: 'frame-a' })));
110
- const collision = await second('figma_task', { action: 'start', sessionId: 'tab-b', frameId: 'frame-a' });
114
+ const taskA = decode(ok(await first('figma_status', { task: 'start', sessionId: 'tab-a', frameId: 'frame-a' })));
115
+ const collision = await second('figma_status', { task: 'start', sessionId: 'tab-b', frameId: 'frame-a' });
111
116
  assert.equal(collision.isError, true); assert.match(collision.content[0].text, /reserved/);
112
- const taskB = decode(ok(await second('figma_task', { action: 'start', sessionId: 'tab-a', frameId: 'frame-b' })));
113
- const taskC = decode(ok(await rpc('figma_task', { action: 'start', sessionId: 'tab-b', frameId: 'frame-c' })));
117
+ const taskB = decode(ok(await second('figma_status', { task: 'start', sessionId: 'tab-a', frameId: 'frame-b' })));
118
+ const taskC = decode(ok(await rpc('figma_status', { task: 'start', sessionId: 'tab-b', frameId: 'frame-c' })));
114
119
  assert.equal(decode(ok(await first('figma_get_selection', { taskId: taskA.taskId }))).id, 'frame-a');
115
120
  assert.equal(decode(ok(await second('figma_get_selection', { taskId: taskB.taskId }))).id, 'frame-b');
116
121
  const writeA = first('figma_write', { taskId: taskA.taskId, code: 'await figma.modify({id:"child-a",note:"paused"}); await figma.modify({id:"child-a",note:"second"});' });
@@ -149,14 +154,14 @@ test('multiple tabs, reconnect ownership and independent frame tasks across MCP
149
154
  tabA.socket.send(JSON.stringify({ id: waiting.id, sessionId: 'tab-a', success: true, data: { id: 'frame-a', tab: 'tab-a' } }));
150
155
  assert.equal(decode(ok(await routed)).tab, 'tab-a');
151
156
  assert.ok(ignored.id);
152
- ok(await first('figma_task', { action: 'end', taskId: taskA.taskId }));
153
- ok(await second('figma_task', { action: 'end', taskId: taskB.taskId }));
157
+ ok(await first('figma_status', { task: 'end', taskId: taskA.taskId }));
158
+ ok(await second('figma_status', { task: 'end', taskId: taskB.taskId }));
154
159
  tabB.socket.close();
155
160
  await until(async () => !(await (await fetch(`${base}/sessions`)).json()).sessions.find(session => session.id === 'tab-b').connected);
156
- assert.equal(decode(ok(await rpc('figma_task', { action: 'end', taskId: taskC.taskId }))).pluginDisconnected, true);
161
+ assert.equal(decode(ok(await rpc('figma_status', { task: 'end', taskId: taskC.taskId }))).pluginDisconnected, true);
157
162
  await plugin('tab-b', tabB);
158
- const restarted = decode(ok(await rpc('figma_task', { action: 'start', sessionId: 'tab-b', frameId: 'frame-c' })));
159
- ok(await rpc('figma_task', { action: 'end', taskId: restarted.taskId }));
163
+ const restarted = decode(ok(await rpc('figma_status', { task: 'start', sessionId: 'tab-b', frameId: 'frame-c' })));
164
+ ok(await rpc('figma_status', { task: 'end', taskId: restarted.taskId }));
160
165
  });
161
166
 
162
167
  test('index sync travels from plugin through UI to bridge and returns a matching confirmation', { timeout: 20000 }, async t => {
@@ -22,13 +22,13 @@ INPUT=$(cat << 'JSON'
22
22
  JSON
23
23
  )
24
24
 
25
- OUTPUT=$(echo "$INPUT" | $BIN_PATH --port 38499 2>/dev/null)
25
+ OUTPUT=$(echo "$INPUT" | HOME="$(mktemp -d)" $BIN_PATH --port 38499 2>/dev/null)
26
26
  echo "$OUTPUT" | head -n 4
27
27
 
28
28
  # Verify tool registration in JSON-RPC output
29
- echo "--- Test 3: Verify Tool Registrations (15 tools) ---"
30
- for TOOL in figma_status figma_get_selection figma_inspect_node figma_to_code figma_get_tokens figma_export_asset figma_export_assets figma_index figma_read figma_write figma_rules figma_docs figma_match_components figma_prepare_design figma_verify_ui; do
31
- echo "$OUTPUT" | grep -q "\"name\":\"$TOOL\"" && echo "✓ $TOOL registered"
29
+ echo "--- Test 3: Verify Tool Registrations (7 tools) ---"
30
+ for TOOL in figma_status figma_read figma_write figma_docs figma_prepare_design figma_to_code figma_verify_ui; do
31
+ echo "$OUTPUT" | grep -q "\"name\":\"$TOOL\"" && echo "✓ $TOOL registered" || { echo "✗ $TOOL missing"; exit 1; }
32
32
  done
33
33
 
34
34
  # Test 4: Node/NPX wrapper test