figma-rust-mcp 5.0.2 → 5.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/README.md +18 -0
- package/package.json +1 -1
- package/scripts/test-plugin-tokens.mjs +20 -0
package/README.md
CHANGED
|
@@ -487,3 +487,21 @@ node scripts/build-plugin.js
|
|
|
487
487
|
## 📄 License
|
|
488
488
|
|
|
489
489
|
MIT © [BuiHung1612](https://github.com/BuiHung1612) — Free to use, modify, and distribute. See [LICENSE](LICENSE) for details.
|
|
490
|
+
|
|
491
|
+
### Bounded design specs
|
|
492
|
+
|
|
493
|
+
Use `figma_read` with `operation: "get_spec"` instead of loading a full frame into the model context:
|
|
494
|
+
|
|
495
|
+
```json
|
|
496
|
+
{"operation":"get_spec","nodeId":"1467:84778","mode":"overview","sessionId":"<tab-id>"}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
- `overview` defaults to depth 2, returning section IDs for discovery. It does **not** claim deep text coverage.
|
|
500
|
+
- Read each section with `mode: "spec"`; `mode: "detail"` reads one node at depth 0. `fields` can select geometry/layout or content/text independently.
|
|
501
|
+
- `maxBytes` defaults to 24000 raw node bytes per page. Continue `nextCursor` with the same `sessionId`, mode and optional byte budget. No records are dropped. A single large node, response metadata and resolved component references can exceed this target.
|
|
502
|
+
- Exact repeated style bundles use `_compression.styles`; repeated node properties use `templates`. Decode template references first, then style references. Identity, topology, copy, geometry, variants and mixed text remain reconstructable. This is property sharing, not heuristic merging of visually similar subtrees.
|
|
503
|
+
- `coverage.subtreeComplete` distinguishes a complete deep read from exhausted pagination at a shallow depth. Counts describe the returned page, not unread descendants.
|
|
504
|
+
- Pass `outputPath` on a fresh request to save **all pages of the requested scope** as raw JSON and return only coverage/path. A shallow overview export remains shallow. Raw data is also available through the existing session cache and node-ID reads.
|
|
505
|
+
- Existing `read_nodes` remains raw by default; `detail: "compact"` enables lossless shared-style compression. It also accepts `maxBytes`.
|
|
506
|
+
|
|
507
|
+
`figma_prepare_design` now follows expanded instance pagination for text and reports separate text/asset coverage. Default compact output omits the redundant `colorPalette`; use `detail: "full"` to retain it. Pass `outputPath` to save the grounding pack plus raw design records outside context. Asset export caps and failures are reported; exported icons do not imply all assets were inspected.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "figma-rust-mcp",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.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",
|
|
@@ -812,3 +812,23 @@ test('creating a paint style invalidates the style names before the next design
|
|
|
812
812
|
await r.handlers.createPaintStyle({ name: 'Brand/New', color: '#ff0000' });
|
|
813
813
|
assert.equal((await r.getStyleNameMapAsync())['new-style'], 'Brand/New');
|
|
814
814
|
});
|
|
815
|
+
|
|
816
|
+
test('byte-bounded reads paginate without dropping unicode, oversized nodes or descendants', async () => {
|
|
817
|
+
const page = {id:'page',type:'PAGE'};
|
|
818
|
+
const frame = {id:'frame',name:'Frame',type:'FRAME',parent:page};
|
|
819
|
+
frame.children = Array.from({length:12},(_,i)=>({id:`text:${i}`,name:'Label',type:'TEXT',parent:frame,
|
|
820
|
+
characters:i === 4 ? '漢'.repeat(2000) : 'é'.repeat(100),visible:true}));
|
|
821
|
+
page.children = [frame];
|
|
822
|
+
const r = runtime({root:{id:"root"},currentPage:page,getNodeByIdAsync:async()=>frame});
|
|
823
|
+
let read = await r.handlers.read_nodes({id:'frame',expandInstances:true,maxBytes:1024,limit:500});
|
|
824
|
+
const all = [...read.nodes]; let oversized = false, pages = 1;
|
|
825
|
+
while(read.nextCursor) {
|
|
826
|
+
assert.ok(pages++ < 20);
|
|
827
|
+
read = await r.handlers.read_nodes({cursor:read.nextCursor,maxBytes:1024,limit:500});
|
|
828
|
+
oversized ||= read.oversizedNode; all.push(...read.nodes);
|
|
829
|
+
}
|
|
830
|
+
assert.equal(read.complete,true); assert.equal(oversized,true);
|
|
831
|
+
assert.deepEqual(plain(all.map(n=>n.id)),['frame',...frame.children.map(n=>n.id)]);
|
|
832
|
+
assert.equal(all[5].content,'漢'.repeat(2000));
|
|
833
|
+
await assert.rejects(r.handlers.read_nodes({id:'frame',maxBytes:0}),/maxBytes/);
|
|
834
|
+
});
|