figma-rust-mcp 4.0.1 → 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 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**: Starts in `< 1ms`, uses `~3MB RAM`, zero GC pauses.
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 (<1ms Lookups & Incremental Diffs) │
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
- Instant `< 1ms` in-memory queries against pre-indexed Figma file structures.
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) in `< 1ms` from cache.
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
@@ -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(`\n\x1b[36m⚡ Update available: v${pkgVersion} → v${state.latestVersion} (Run: npx figma-rust-mcp --upgrade)\x1b[0m\n\n`);
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(`\n\x1b[36m⚡ Update available: v${pkgVersion} → v${latest}\x1b[0m\n\x1b[36m Run: npx figma-rust-mcp --upgrade\x1b[0m\n\n`);
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
- execSync('npm install -g figma-rust-mcp@latest', { stdio: 'inherit' });
484
- console.log('\x1b[32m✓ Upgraded figma-rust-mcp to latest version.\x1b[0m');
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.0.1",
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
+ }