figma-rust-mcp 4.0.0 → 4.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 +22 -4
- package/bin/figma-rust-mcp.js +13 -4
- package/package.json +2 -1
- package/scripts/bench.mjs +37 -0
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Enables AI agents (Google Antigravity, Claude Code, Cursor, Windsurf, VS Code, Z
|
|
|
9
9
|
## ⚡ Highlights
|
|
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
|
-
- **Pure Rust Native Performance**:
|
|
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
13
|
- **Scoped In-Memory Index (`figma_index`)**: 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.
|
|
@@ -30,7 +30,7 @@ Enables AI agents (Google Antigravity, Claude Code, Cursor, Windsurf, VS Code, Z
|
|
|
30
30
|
│ figma-rust-mcp (Pure Rust Engine) │
|
|
31
31
|
│ • MCP Streamable HTTP (/mcp) & legacy SSE (/sse, /message) │
|
|
32
32
|
│ • Dynamic Runtime Server (/plugin/code.js, /plugin/ui.html) │
|
|
33
|
-
│ • In-Memory Fast Index (
|
|
33
|
+
│ • In-Memory Fast Index (Scoped Lookups & Incremental Diffs) │
|
|
34
34
|
│ • Design-to-Code Compiler (React/Tailwind, Vue, RN, SwiftUI) │
|
|
35
35
|
│ • Design Token Transformer (CSS, Tailwind, TypeScript, W3C DTCG) │
|
|
36
36
|
│ • Sandboxed JS Runtime (Boa ECMAScript Engine) │
|
|
@@ -283,7 +283,7 @@ Batch extracts all SVG icons and PNG images from a Figma frame/page directly int
|
|
|
283
283
|
- **`createBarrel`**: Automatically creates an `index.ts` barrel export file in `iconDir`.
|
|
284
284
|
|
|
285
285
|
### 8. `figma_index`
|
|
286
|
-
|
|
286
|
+
In-memory queries against pre-indexed Figma file structures (latency for your file: plugin **Bench** tab).
|
|
287
287
|
- **`operation`**:
|
|
288
288
|
- `"status"`: View index health and node counts.
|
|
289
289
|
- `"search_nodes"`: Search nodes by text name, query, and type (`FRAME`, `TEXT`, `COMPONENT`, `INSTANCE`).
|
|
@@ -315,7 +315,7 @@ Executes JavaScript draw commands inside the sandboxed VM to build or modify des
|
|
|
315
315
|
- Supports Prototyping & Reactions: `setReactions`, `getReactions`, `setScrollBehavior`.
|
|
316
316
|
|
|
317
317
|
### 11. `figma_rules`
|
|
318
|
-
Audits the current Figma document and generates a complete design system rule sheet (color tokens, typography styles, variables, component catalog)
|
|
318
|
+
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.
|
|
319
319
|
|
|
320
320
|
### 12. `figma_docs`
|
|
321
321
|
Fetches built-in documentation, design rules, layout guidelines, and code examples for `figma_write`.
|
|
@@ -419,6 +419,24 @@ Revision gaps request resynchronization. Page switches rebuild the shallow
|
|
|
419
419
|
index without preloading every page. Exact-read caching evicts one least
|
|
420
420
|
recently used entry when its 64-entry capacity is reached.
|
|
421
421
|
|
|
422
|
+
## 📏 Benchmarks
|
|
423
|
+
|
|
424
|
+
`npm run bench` builds the release binary, spawns it on a random port and prints:
|
|
425
|
+
|
|
426
|
+
| Metric | How it is measured |
|
|
427
|
+
|---|---|
|
|
428
|
+
| Cold start | `spawn` → first successful `GET /health`, from outside the process |
|
|
429
|
+
| Startup | `main()` entry → HTTP listener bound |
|
|
430
|
+
| RAM | Resident set size (macOS/Linux; not reported on Windows) |
|
|
431
|
+
| MCP `tools/list` | 200 in-process dispatches, p50/p95 |
|
|
432
|
+
| Index search | 200 searches over the current tab's index, p50/p95 (plugin tab only) |
|
|
433
|
+
| Plugin round trip | 10 `get_viewport` calls through Figma, p50/p95 (plugin tab only) |
|
|
434
|
+
|
|
435
|
+
The plugin's **Bench** tab runs the same report (`POST /benchmark`) against the
|
|
436
|
+
live server, so index and plugin numbers reflect your real file. Sample run on an
|
|
437
|
+
Apple Silicon Mac (v4.0.1): cold start 45–530ms, startup 26–126ms, RSS ~7.6MB,
|
|
438
|
+
`tools/list` p50 0.07ms. Your numbers will differ; quote your own.
|
|
439
|
+
|
|
422
440
|
## 💻 Development & Testing
|
|
423
441
|
|
|
424
442
|
```bash
|
package/bin/figma-rust-mcp.js
CHANGED
|
@@ -435,6 +435,14 @@ function isNewer(latest, current) {
|
|
|
435
435
|
return false;
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
+
// --upgrade stays on the installed major; a new major is announced but never auto-installed.
|
|
439
|
+
function updateNotice(latest) {
|
|
440
|
+
if (latest.split('.')[0] !== pkgVersion.split('.')[0]) {
|
|
441
|
+
return `\n\x1b[36m⚡ v${latest} is a new major release with breaking changes (see CHANGELOG).\x1b[0m\n\x1b[36m Opt in: npm install -g figma-rust-mcp@latest\x1b[0m\n\n`;
|
|
442
|
+
}
|
|
443
|
+
return `\n\x1b[36m⚡ Update available: v${pkgVersion} → v${latest}\x1b[0m\n\x1b[36m Run: npx figma-rust-mcp --upgrade\x1b[0m\n\n`;
|
|
444
|
+
}
|
|
445
|
+
|
|
438
446
|
function checkForUpdatesAsync() {
|
|
439
447
|
const updateStateFile = path.join(os.homedir(), '.figma-rust-mcp', 'update-check.json');
|
|
440
448
|
try {
|
|
@@ -443,7 +451,7 @@ function checkForUpdatesAsync() {
|
|
|
443
451
|
const now = Date.now();
|
|
444
452
|
if (state.lastChecked && (now - state.lastChecked < 6 * 3600 * 1000)) {
|
|
445
453
|
if (state.latestVersion && isNewer(state.latestVersion, pkgVersion)) {
|
|
446
|
-
process.stderr.write(
|
|
454
|
+
process.stderr.write(updateNotice(state.latestVersion));
|
|
447
455
|
}
|
|
448
456
|
return;
|
|
449
457
|
}
|
|
@@ -467,7 +475,7 @@ function checkForUpdatesAsync() {
|
|
|
467
475
|
latestVersion: latest
|
|
468
476
|
}, null, 2));
|
|
469
477
|
if (latest && isNewer(latest, pkgVersion)) {
|
|
470
|
-
process.stderr.write(
|
|
478
|
+
process.stderr.write(updateNotice(latest));
|
|
471
479
|
}
|
|
472
480
|
}
|
|
473
481
|
} catch (_) {}
|
|
@@ -480,8 +488,9 @@ function checkForUpdatesAsync() {
|
|
|
480
488
|
async function upgrade() {
|
|
481
489
|
console.log('\x1b[36m[figma-rust-mcp]\x1b[0m Checking for updates and upgrading figma-rust-mcp...');
|
|
482
490
|
try {
|
|
483
|
-
|
|
484
|
-
|
|
491
|
+
const major = pkgVersion ? pkgVersion.split('.')[0] : 'latest';
|
|
492
|
+
execSync(`npm install -g figma-rust-mcp@${major}`, { stdio: 'inherit' });
|
|
493
|
+
console.log(`\x1b[32m✓ Upgraded figma-rust-mcp (${major === 'latest' ? 'latest' : `v${major}.x`}).\x1b[0m`);
|
|
485
494
|
setupPlugin();
|
|
486
495
|
if (isServiceInstalled()) {
|
|
487
496
|
console.log('\x1b[36mUpdating background service binary...\x1b[0m');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "figma-rust-mcp",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.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",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"test": "./scripts/test-rust-mcp.sh",
|
|
21
21
|
"test:tokens": "node --test scripts/test-plugin-tokens.mjs && cargo test",
|
|
22
22
|
"dev": "cargo run",
|
|
23
|
+
"bench": "cargo build --release && node scripts/bench.mjs",
|
|
23
24
|
"test:sessions": "cargo build && node --test scripts/test-bridge-sessions.mjs"
|
|
24
25
|
},
|
|
25
26
|
"engines": {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Benchmark: cold start measured from outside (spawn → /health answers), then the
|
|
2
|
+
// server's in-process report from POST /benchmark (same numbers as the plugin "Bench" tab).
|
|
3
|
+
// Usage: npm run bench or node scripts/bench.mjs [path/to/figma-rust-mcp]
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { statSync } from "node:fs";
|
|
6
|
+
|
|
7
|
+
const bin = process.argv[2] ?? `target/release/figma-rust-mcp${process.platform === "win32" ? ".exe" : ""}`;
|
|
8
|
+
const port = 40000 + Math.floor(Math.random() * 10000);
|
|
9
|
+
const base = `http://127.0.0.1:${port}`;
|
|
10
|
+
|
|
11
|
+
const t0 = performance.now();
|
|
12
|
+
const child = spawn(bin, ["--server", "--port", String(port)], { stdio: "ignore" });
|
|
13
|
+
let coldStartMs;
|
|
14
|
+
try {
|
|
15
|
+
while (coldStartMs === undefined) {
|
|
16
|
+
if (performance.now() - t0 > 10_000) throw new Error("server did not answer /health within 10s");
|
|
17
|
+
try {
|
|
18
|
+
if ((await fetch(`${base}/health`)).ok) coldStartMs = performance.now() - t0;
|
|
19
|
+
} catch {
|
|
20
|
+
await new Promise((r) => setTimeout(r, 2));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const r = await (await fetch(`${base}/benchmark`, { method: "POST" })).json();
|
|
24
|
+
const lat = (s) => (s ? `p50 ${s.p50Ms.toFixed(3)}ms · p95 ${s.p95Ms.toFixed(3)}ms (n=${s.n})` : "skipped");
|
|
25
|
+
const rows = {
|
|
26
|
+
version: `v${r.version}`,
|
|
27
|
+
"binary size": `${(statSync(bin).size / 1024 / 1024).toFixed(1)}MB`,
|
|
28
|
+
"cold start (spawn → /health)": `${coldStartMs.toFixed(1)}ms`,
|
|
29
|
+
"startup (main → listening)": r.startupMs != null ? `${r.startupMs.toFixed(2)}ms` : "—",
|
|
30
|
+
"RAM (RSS)": r.memoryMb != null ? `${r.memoryMb.toFixed(1)}MB` : "n/a on this OS",
|
|
31
|
+
"MCP tools/list (in-process)": lat(r.mcpToolsList),
|
|
32
|
+
"plugin round trip": r.pluginRoundTrip ? lat(r.pluginRoundTrip) : "skipped (fresh server has no Figma session)",
|
|
33
|
+
};
|
|
34
|
+
for (const [k, v] of Object.entries(rows)) console.log(`${k.padEnd(30)} ${v}`);
|
|
35
|
+
} finally {
|
|
36
|
+
child.kill();
|
|
37
|
+
}
|