figma-console-mcp 1.29.1 → 1.30.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 +4 -2
- package/dist/cloudflare/core/design-code-tools.js +51 -5
- package/dist/cloudflare/core/design-system-tools.js +19 -9
- package/dist/cloudflare/core/tokens-tools.js +1 -1
- package/dist/cloudflare/core/variable-resolver.js +85 -0
- package/dist/cloudflare/index.js +5 -5
- package/dist/core/design-code-tools.d.ts.map +1 -1
- package/dist/core/design-code-tools.js +51 -5
- package/dist/core/design-code-tools.js.map +1 -1
- package/dist/core/tokens-tools.js +1 -1
- package/dist/core/websocket-connector.d.ts.map +1 -1
- package/dist/core/websocket-connector.js +2 -0
- package/dist/core/websocket-connector.js.map +1 -1
- package/dist/core/write-tools.d.ts.map +1 -1
- package/dist/core/write-tools.js +35 -8
- package/dist/core/write-tools.js.map +1 -1
- package/figma-desktop-bridge/code.js +156 -27
- package/figma-desktop-bridge/ui.html +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
> **Your design system as an API.** Model Context Protocol server that bridges design and development—giving AI assistants complete access to Figma for **extraction**, **creation**, **debugging**, and **bidirectional token sync**.
|
|
10
10
|
|
|
11
|
-
> **🆕
|
|
11
|
+
> **🆕 Native variable binding & typography (v1.30.0):** The structured write tools now handle the operations that used to force you into raw `figma_execute`. `figma_set_fills` / `figma_set_strokes` bind a fill or stroke to a color variable via a new `variableId` param (works on any Figma plan through the bridge — no Enterprise Variables API). `figma_set_text` gains `fontFamily` / `fontStyle` with space-insensitive normalization (`SemiBold` → `Semi Bold`) and graceful fallback. `figma_instantiate_component` now pre-loads instance fonts before text overrides (no more silently-skipped overrides) and reports failed overrides in a `warnings` array. Works on every Figma plan. [See what's new →](CHANGELOG.md#1300---2026-06-02)
|
|
12
12
|
|
|
13
13
|
## What is this?
|
|
14
14
|
|
|
@@ -808,9 +808,11 @@ The architecture supports adding new apps with minimal boilerplate — each app
|
|
|
808
808
|
|
|
809
809
|
## 🛤️ Roadmap
|
|
810
810
|
|
|
811
|
-
**Current Status:** v1.
|
|
811
|
+
**Current Status:** v1.30.0 (Stable) - Production-ready with native variable binding on fills/strokes + typography control in the write tools, shared-library inspection (key-based component resolution + library variable read/import without Enterprise plan), 10-format token export pipeline (DTCG, CSS, Tailwind v4, Tailwind v3, SCSS, TS module, JSON flat/nested, Style Dictionary v3, Tokens Studio), bidirectional Figma↔code token sync, version history & time-series awareness, FigJam + Slides support, Cloud Write Relay, Design System Kit, WebSocket-only connectivity, smart multi-file tracking, **106 tools** (Local) / **95 tools** (Cloud) / **9 tools** (Remote read-only), Comments API, cross-MCP identity disambiguation, and MCP Apps.
|
|
812
812
|
|
|
813
813
|
**Recent Releases:**
|
|
814
|
+
- [x] **v1.30.0** - Native variable binding + typography in the structured write tools, closing the Plugin API gaps that used to force raw `figma_execute`. `figma_set_fills` / `figma_set_strokes` accept a `variableId` to bind a fill/stroke to a color variable via `setBoundVariableForPaint` (any plan, via the bridge). `figma_set_text` gains `fontFamily` / `fontStyle` with space-insensitive normalization (`SemiBold` → `Semi Bold`) and graceful `Regular` fallback. `figma_instantiate_component` pre-loads instance text fonts before applying overrides (fixes silently-skipped text overrides on non-Regular weights) and returns a `warnings` array for failed overrides. Also fixes a mixed-font crash in `figma_set_text` and a `ui.html` relay that was dropping new message fields. No new tools; **plugin re-import required** (bridge `ui.html` + `code.js` changed). Validated live; 1185 tests passing.
|
|
815
|
+
- [x] **v1.29.2** - Bug fix: `figma_generate_component_doc` now renders Figma component descriptions faithfully and reliably tags atomic-design level. Single-`#` headings in descriptions render as real sections (Usage Guidelines, Implementation Considerations, Accessibility Requirements, Content Configuration) instead of leaking as `- # Heading` list items; frontmatter `description` takes the first sentence instead of truncating on the word "Accessibility"; the generated Figma URL no longer doubles `?node-id=`; and the component's atomic level (atom/molecule/organism/template) is auto-detected via a single `ids=<node>` file request + divider walk-back, with no dependency on library publishing. No new tools; plugin re-import not required.
|
|
814
816
|
- [x] **v1.29.1** - Bug fix: `figma_get_design_system_kit` now resolves variables bridge-first (Desktop Bridge / cloud relay → REST fallback) instead of calling the Enterprise-only Variables REST API directly. Non-Enterprise users no longer hit a 403 on the kit's token section when a bridge is connected, and a REST 403 now points the caller back to the bridge instead of dead-ending. 7 new tests, 1185 total passing. No new tools; plugin re-import not required.
|
|
815
817
|
- [x] **v1.29.0** - Shared library inspection: three new tools close the gap between "I have a component key" and "I can actually use it." `figma_get_library_component_by_key` resolves any 40-char component key to full `componentPropertyDefinitions` + variants (with their published keys) + per-variant visual specs — without needing the source library file's URL. `figma_get_library_variables` lists library tokens via Plugin API (works on every Figma plan; the REST equivalent is Enterprise-only). `figma_import_library_variable` imports a library token to the current file so it can be bound to nodes. 27 new tests, 1178 total passing. Plugin re-import optional.
|
|
816
818
|
- [x] **v1.28.1** - Bug fix patch surfacing from live-fire testing of the v1.28.0 formatters against multi-tier semantic-token design systems. Fixes: Tailwind v3 emitted empty `module.exports` for alias-only sets (now resolves alias chains to literal values); TypeScript module + JSON flat + JSON nested formatters emitted `"{alias.path}"` strings as literal values (now resolves); Tailwind v4 namespace-prefix doubling (`--color-theme-color-X` is now `--color-theme-X`). Adds `resolveAliasChain` public helper. 1151 tests still passing.
|
|
@@ -174,7 +174,7 @@ export function parseComponentDescription(description) {
|
|
|
174
174
|
for (const line of lines) {
|
|
175
175
|
const trimmed = line.trim();
|
|
176
176
|
// Detect section headers: bold text (**Header**), markdown headers (## Header), or plain text exact matches
|
|
177
|
-
const markdownHeaderMatch = trimmed.match(/^(
|
|
177
|
+
const markdownHeaderMatch = trimmed.match(/^(?:\*\*|#{1,6}\s*)(.+?)(?:\*\*)?$/);
|
|
178
178
|
const headerText = markdownHeaderMatch ? markdownHeaderMatch[1].trim().replace(/\*\*/g, "") : null;
|
|
179
179
|
// Check if this is a Figma per-property documentation block (e.g., "Show Left Icon: True – Purpose")
|
|
180
180
|
// These should be routed to "other" to avoid polluting content guidelines and accessibility sections
|
|
@@ -1380,7 +1380,47 @@ function buildParityInstruction(componentName, parityScore, counts, canonicalSou
|
|
|
1380
1380
|
// ============================================================================
|
|
1381
1381
|
// Documentation Section Generators
|
|
1382
1382
|
// ============================================================================
|
|
1383
|
-
|
|
1383
|
+
/**
|
|
1384
|
+
* Detect the atomic-design level (atom | molecule | organism | template) of a component
|
|
1385
|
+
* by finding its Figma page and walking the ordered page list back to the nearest
|
|
1386
|
+
* section-divider page (e.g. "ATOMS", "MOLECULES", "ORGANISMS"). Returns null when the
|
|
1387
|
+
* file doesn't use atomic-design page sections or the page can't be resolved — callers
|
|
1388
|
+
* then simply omit the `level` frontmatter. Best-effort and never throws.
|
|
1389
|
+
*/
|
|
1390
|
+
async function detectAtomicLevel(api, fileKey, nodeId, setNodeId, _componentMeta, _allComponentsMeta) {
|
|
1391
|
+
try {
|
|
1392
|
+
const targetId = setNodeId || nodeId;
|
|
1393
|
+
// Resolve the page the component lives on — independent of library-publish
|
|
1394
|
+
// status (published `containing_frame` metadata is empty for many files).
|
|
1395
|
+
// Requesting the file with `ids` returns every page in document order, but
|
|
1396
|
+
// prunes each page's children to only the path reaching the requested node,
|
|
1397
|
+
// so the single page whose subtree still contains the node is its home page.
|
|
1398
|
+
const pages = (await api.getFile(fileKey, { ids: [targetId] }))?.document?.children || [];
|
|
1399
|
+
const contains = (n) => n?.id === targetId || (Array.isArray(n?.children) && n.children.some(contains));
|
|
1400
|
+
const idx = pages.findIndex((p) => contains(p));
|
|
1401
|
+
if (idx < 0)
|
|
1402
|
+
return null;
|
|
1403
|
+
// Walk back to the nearest atomic-design divider page.
|
|
1404
|
+
const LEVELS = [
|
|
1405
|
+
["ATOM", "atom"],
|
|
1406
|
+
["MOLECULE", "molecule"],
|
|
1407
|
+
["ORGANISM", "organism"],
|
|
1408
|
+
["TEMPLATE", "template"],
|
|
1409
|
+
];
|
|
1410
|
+
for (let i = idx; i >= 0; i--) {
|
|
1411
|
+
const stripped = (pages[i]?.name || "").toUpperCase().replace(/[^A-Z]/g, "");
|
|
1412
|
+
for (const [marker, level] of LEVELS) {
|
|
1413
|
+
if (stripped.startsWith(marker))
|
|
1414
|
+
return level;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return null;
|
|
1418
|
+
}
|
|
1419
|
+
catch {
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
function generateFrontmatter(componentName, description, node, componentMeta, fileUrl, codeInfo, canonicalSource, level) {
|
|
1384
1424
|
const status = codeInfo?.changelog?.[0]
|
|
1385
1425
|
? "stable"
|
|
1386
1426
|
: componentMeta?.description?.toLowerCase().includes("deprecated")
|
|
@@ -1388,6 +1428,8 @@ function generateFrontmatter(componentName, description, node, componentMeta, fi
|
|
|
1388
1428
|
: "stable";
|
|
1389
1429
|
const version = codeInfo?.changelog?.[0]?.version || "1.0.0";
|
|
1390
1430
|
const tags = [componentName.toLowerCase()];
|
|
1431
|
+
if (level)
|
|
1432
|
+
tags.push(level);
|
|
1391
1433
|
if (node.type === "COMPONENT_SET")
|
|
1392
1434
|
tags.push("variants");
|
|
1393
1435
|
if (node.componentPropertyDefinitions)
|
|
@@ -1395,10 +1437,11 @@ function generateFrontmatter(componentName, description, node, componentMeta, fi
|
|
|
1395
1437
|
const lines = [
|
|
1396
1438
|
"---",
|
|
1397
1439
|
`title: ${componentName}`,
|
|
1398
|
-
`description: ${(description.split(
|
|
1440
|
+
`description: ${((description.split(/\n\s*\n|\n#{1,6}\s|\n\*\*/)[0] || description).replace(/\n/g, " ").replace(/\s+/g, " ").trim().split(/(?<=[.!?])\s+/)[0] || `${componentName} component`)}`,
|
|
1399
1441
|
`status: ${status}`,
|
|
1400
1442
|
`version: ${version}`,
|
|
1401
1443
|
`category: components`,
|
|
1444
|
+
...(level ? [`level: ${level}`] : []),
|
|
1402
1445
|
`tags: [${tags.join(", ")}]`,
|
|
1403
1446
|
`figma: ${fileUrl}`,
|
|
1404
1447
|
];
|
|
@@ -2560,7 +2603,9 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2560
2603
|
logger.warn("Desktop Bridge fetch failed, proceeding without bridge-sourced data");
|
|
2561
2604
|
}
|
|
2562
2605
|
}
|
|
2563
|
-
|
|
2606
|
+
// Strip any existing query (e.g. the connected file's ?node-id=<page>) before
|
|
2607
|
+
// appending the target node, otherwise the URL ends up with a doubled ?node-id=.
|
|
2608
|
+
const fileUrl_ = `${url.split("?")[0]}?node-id=${nodeId.replace(":", "-")}`;
|
|
2564
2609
|
// Parse the component description for structured content
|
|
2565
2610
|
const parsedDesc = parseComponentDescription(description);
|
|
2566
2611
|
// Determine canonical source
|
|
@@ -2589,7 +2634,8 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2589
2634
|
const parts = [];
|
|
2590
2635
|
const includedSections = [];
|
|
2591
2636
|
if (includeFrontmatter) {
|
|
2592
|
-
|
|
2637
|
+
const atomicLevel = await detectAtomicLevel(api, fileKey, nodeId, setInfo.setNodeId, componentMeta, allComponentsMeta);
|
|
2638
|
+
parts.push(generateFrontmatter(componentName, description, node, componentMeta, fileUrl_, codeInfo, canonicalSource, atomicLevel));
|
|
2593
2639
|
parts.push("");
|
|
2594
2640
|
}
|
|
2595
2641
|
if (s.overview) {
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* to generate code with structural fidelity to the real design system.
|
|
8
8
|
*/
|
|
9
9
|
import { z } from "zod";
|
|
10
|
-
import { extractFileKey
|
|
10
|
+
import { extractFileKey } from "./figma-api.js";
|
|
11
|
+
import { resolveFormattedVariables } from "./variable-resolver.js";
|
|
11
12
|
import { createChildLogger } from "./logger.js";
|
|
12
13
|
const logger = createChildLogger({ component: "design-system-tools" });
|
|
13
14
|
// ============================================================================
|
|
@@ -420,13 +421,15 @@ function compressKit(kit, level) {
|
|
|
420
421
|
// ============================================================================
|
|
421
422
|
// Tool Registration
|
|
422
423
|
// ============================================================================
|
|
423
|
-
export function registerDesignSystemTools(server, getFigmaAPI, getCurrentUrl, variablesCache, options) {
|
|
424
|
+
export function registerDesignSystemTools(server, getFigmaAPI, getCurrentUrl, variablesCache, options, getDesktopConnector) {
|
|
424
425
|
server.tool("figma_get_design_system_kit", "PREFERRED TOOL for design system extraction — replaces separate figma_get_styles, figma_get_variables, and figma_get_component calls. " +
|
|
425
426
|
"Returns tokens, components, and styles in a single optimized response with adaptive compression for large systems. " +
|
|
426
427
|
"Includes component visual specs (exact colors, padding, typography, layout), rendered screenshots, " +
|
|
427
428
|
"token values per mode (light/dark), and resolved style values. " +
|
|
428
429
|
"Use this instead of calling individual tools to avoid context window overflow. " +
|
|
429
|
-
"Ideal for AI code generation — use visualSpec for pixel-accurate reproduction."
|
|
430
|
+
"Ideal for AI code generation — use visualSpec for pixel-accurate reproduction. " +
|
|
431
|
+
"Tokens/variables are read through the connected Desktop Bridge or cloud relay and work on ANY Figma plan — no Enterprise required. " +
|
|
432
|
+
"If a tokens fetch ever reports the Variables REST API is plan-limited (403), the bridge/relay is the plan-independent path: ensure it's connected and retry rather than abandoning variables.", {
|
|
430
433
|
fileKey: z
|
|
431
434
|
.string()
|
|
432
435
|
.optional()
|
|
@@ -482,25 +485,32 @@ export function registerDesignSystemTools(server, getFigmaAPI, getCurrentUrl, va
|
|
|
482
485
|
try {
|
|
483
486
|
logger.info({ fileKey: resolvedFileKey }, "Fetching design tokens");
|
|
484
487
|
// Check cache first
|
|
485
|
-
let variablesData = null;
|
|
486
488
|
const cacheKey = `vars:${resolvedFileKey}`;
|
|
489
|
+
let formatted = null;
|
|
487
490
|
if (variablesCache) {
|
|
488
491
|
const cached = variablesCache.get(cacheKey);
|
|
489
492
|
if (cached && Date.now() - cached.timestamp < 5 * 60 * 1000) {
|
|
490
|
-
|
|
493
|
+
formatted = cached.data;
|
|
491
494
|
logger.info("Using cached variables data");
|
|
492
495
|
}
|
|
493
496
|
}
|
|
494
|
-
if (!
|
|
495
|
-
|
|
497
|
+
if (!formatted) {
|
|
498
|
+
// Bridge-first: the Desktop Bridge / cloud relay reads variables on
|
|
499
|
+
// ANY plan via the Plugin API. The Enterprise-only REST Variables API
|
|
500
|
+
// is the fallback, used only when no bridge is connected — so most
|
|
501
|
+
// users (non-Enterprise) no longer dead-end on a 403 here.
|
|
502
|
+
formatted = await resolveFormattedVariables({
|
|
503
|
+
getDesktopConnector,
|
|
504
|
+
getFigmaAPI,
|
|
505
|
+
fileKey: resolvedFileKey,
|
|
506
|
+
});
|
|
496
507
|
if (variablesCache) {
|
|
497
508
|
variablesCache.set(cacheKey, {
|
|
498
|
-
data:
|
|
509
|
+
data: formatted,
|
|
499
510
|
timestamp: Date.now(),
|
|
500
511
|
});
|
|
501
512
|
}
|
|
502
513
|
}
|
|
503
|
-
const formatted = formatVariables(variablesData);
|
|
504
514
|
const collections = groupVariablesByCollection(formatted);
|
|
505
515
|
kit.tokens = {
|
|
506
516
|
collections,
|
|
@@ -30,7 +30,7 @@ const logger = createChildLogger({ component: "tokens-tools" });
|
|
|
30
30
|
* on every exported token document. Kept in sync with package.json by
|
|
31
31
|
* scripts/release.sh — see step 3 of the release flow.
|
|
32
32
|
*/
|
|
33
|
-
const MCP_VERSION = "1.29.
|
|
33
|
+
const MCP_VERSION = "1.29.2";
|
|
34
34
|
const EXPORT_TOOL_DESCRIPTION = `Export Figma variables to design token files in your codebase. Bidirectional with figma_import_tokens — together they replace Style Dictionary and Tokens Studio's export pipeline for the popular styling methods.
|
|
35
35
|
|
|
36
36
|
FULLY-IMPLEMENTED OUTPUT FORMATS:
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge-first variable resolution.
|
|
3
|
+
*
|
|
4
|
+
* Figma's Variables REST API (`/files/:key/variables/local`) is **Enterprise-only**
|
|
5
|
+
* and returns 403 for the majority of users (Starter/Pro/Org). The Desktop Bridge
|
|
6
|
+
* and cloud relay read variables through the Plugin API
|
|
7
|
+
* (`figma.variables.getLocalVariablesAsync`), which works on **every** Figma plan.
|
|
8
|
+
*
|
|
9
|
+
* Orchestration tools (e.g. `figma_get_design_system_kit`) historically called the
|
|
10
|
+
* REST API directly and dead-ended on a 403 for non-Enterprise users — even when a
|
|
11
|
+
* bridge was connected. This helper mirrors `figma_get_variables`' resolution order
|
|
12
|
+
* so every variable-reading tool behaves consistently:
|
|
13
|
+
*
|
|
14
|
+
* 1. Desktop Bridge / cloud relay (any plan) ← preferred
|
|
15
|
+
* 2. REST Variables API (Enterprise only) ← fallback
|
|
16
|
+
*
|
|
17
|
+
* It returns the same normalized shape as `formatVariables()`.
|
|
18
|
+
*/
|
|
19
|
+
import { formatVariables, withTimeout } from "./figma-api.js";
|
|
20
|
+
import { createChildLogger } from "./logger.js";
|
|
21
|
+
const logger = createChildLogger({ component: "variable-resolver" });
|
|
22
|
+
/**
|
|
23
|
+
* The Desktop Bridge returns variables/collections as **arrays**, while
|
|
24
|
+
* `formatVariables()` expects **objects keyed by id** (the REST shape). Convert so
|
|
25
|
+
* the same formatter handles both transports.
|
|
26
|
+
*/
|
|
27
|
+
function bridgeArraysToFormatInput(data) {
|
|
28
|
+
const variables = {};
|
|
29
|
+
for (const v of data.variables || []) {
|
|
30
|
+
if (v && v.id)
|
|
31
|
+
variables[v.id] = v;
|
|
32
|
+
}
|
|
33
|
+
const variableCollections = {};
|
|
34
|
+
for (const c of data.variableCollections || []) {
|
|
35
|
+
if (c && c.id)
|
|
36
|
+
variableCollections[c.id] = c;
|
|
37
|
+
}
|
|
38
|
+
return { variables, variableCollections };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolve + format local variables, preferring the Desktop Bridge / cloud relay and
|
|
42
|
+
* falling back to the Enterprise-only REST API only when no bridge is connected.
|
|
43
|
+
*
|
|
44
|
+
* @throws a bridge-pointing error when the bridge is unavailable AND the REST API
|
|
45
|
+
* fails (e.g. 403 without Enterprise), so callers/LLMs retry via the bridge
|
|
46
|
+
* instead of treating variables as inaccessible.
|
|
47
|
+
*/
|
|
48
|
+
export async function resolveFormattedVariables(opts) {
|
|
49
|
+
const { getDesktopConnector, getFigmaAPI, fileKey } = opts;
|
|
50
|
+
const timeoutMs = opts.timeoutMs ?? 30000;
|
|
51
|
+
// 1. Desktop Bridge / cloud relay — works on ANY plan. Preferred.
|
|
52
|
+
if (getDesktopConnector) {
|
|
53
|
+
try {
|
|
54
|
+
const connector = await getDesktopConnector();
|
|
55
|
+
const raw = await withTimeout(connector.getVariables(fileKey), timeoutMs, "Desktop Bridge variables");
|
|
56
|
+
// EXECUTE_CODE responses nest the return value under `result`; unwrap so
|
|
57
|
+
// both the live and cached plugin paths produce a uniform shape. See #68.
|
|
58
|
+
const data = raw?.result?.variables ? raw.result : raw;
|
|
59
|
+
if (data?.success && Array.isArray(data.variables)) {
|
|
60
|
+
const formatted = formatVariables(bridgeArraysToFormatInput(data));
|
|
61
|
+
logger.info({ source: "desktop_bridge", variableCount: formatted.variables.length }, "Resolved variables via Desktop Bridge");
|
|
62
|
+
return { ...formatted, source: "desktop_bridge" };
|
|
63
|
+
}
|
|
64
|
+
logger.warn({ fileKey, error: data?.error }, "Desktop Bridge returned no variables; falling back to REST API");
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
logger.warn({ fileKey, error: err instanceof Error ? err.message : String(err) }, "Desktop Bridge variable fetch failed; falling back to REST API");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// 2. REST Variables API — Enterprise only. Last resort.
|
|
71
|
+
const api = await getFigmaAPI();
|
|
72
|
+
try {
|
|
73
|
+
const local = await withTimeout(api.getLocalVariables(fileKey), timeoutMs, "getLocalVariables");
|
|
74
|
+
const formatted = formatVariables(local);
|
|
75
|
+
logger.info({ source: "rest_api", variableCount: formatted.variables.length }, "Resolved variables via REST API");
|
|
76
|
+
return { ...formatted, source: "rest_api" };
|
|
77
|
+
}
|
|
78
|
+
catch (restErr) {
|
|
79
|
+
const msg = restErr instanceof Error ? restErr.message : String(restErr);
|
|
80
|
+
throw new Error(`[figma-console-mcp] Could not read variables. The Figma Variables REST API ` +
|
|
81
|
+
`is unavailable for this file (${msg}) — it requires an Enterprise plan. ` +
|
|
82
|
+
`Connect the Figma Console MCP Desktop Bridge plugin (or pair it via Cloud Mode) ` +
|
|
83
|
+
`to read variables on ANY plan, then retry.`);
|
|
84
|
+
}
|
|
85
|
+
}
|
package/dist/cloudflare/index.js
CHANGED
|
@@ -74,7 +74,7 @@ export class FigmaConsoleMCPv3 extends McpAgent {
|
|
|
74
74
|
this.server = (() => {
|
|
75
75
|
const s = new McpServer({
|
|
76
76
|
name: "Figma Console MCP",
|
|
77
|
-
version: "1.29.
|
|
77
|
+
version: "1.29.2",
|
|
78
78
|
});
|
|
79
79
|
// Identity wrap — every tool's response and thrown error gets stamped
|
|
80
80
|
// with our MCP name so cross-MCP attribution is unambiguous.
|
|
@@ -809,7 +809,7 @@ export class FigmaConsoleMCPv3 extends McpAgent {
|
|
|
809
809
|
registerVersionTools(this.server, async () => await this.getFigmaAPI(), () => this.browserManager?.getCurrentUrl() || null, { isRemoteMode: true });
|
|
810
810
|
// Register Design System Kit tool
|
|
811
811
|
registerDesignSystemTools(this.server, async () => await this.getFigmaAPI(), () => this.browserManager?.getCurrentUrl() || null, undefined, // variablesCache
|
|
812
|
-
{ isRemoteMode: true });
|
|
812
|
+
{ isRemoteMode: true }, getCloudDesktopConnector);
|
|
813
813
|
// Register Library Tools (key-based component inspection across shared libraries)
|
|
814
814
|
registerLibraryTools(this.server, async () => await this.getFigmaAPI());
|
|
815
815
|
// Register Library Variable Tools (Plugin-API based — list + import variables
|
|
@@ -1083,7 +1083,7 @@ export default {
|
|
|
1083
1083
|
});
|
|
1084
1084
|
const statelessServer = new McpServer({
|
|
1085
1085
|
name: "Figma Console MCP",
|
|
1086
|
-
version: "1.29.
|
|
1086
|
+
version: "1.29.2",
|
|
1087
1087
|
});
|
|
1088
1088
|
wrapServerForIdentity(statelessServer);
|
|
1089
1089
|
// ================================================================
|
|
@@ -1180,7 +1180,7 @@ export default {
|
|
|
1180
1180
|
registerCommentTools(statelessServer, async () => statelessApi, getCloudFileUrl);
|
|
1181
1181
|
registerVersionTools(statelessServer, async () => statelessApi, getCloudFileUrl);
|
|
1182
1182
|
registerDesignSystemTools(statelessServer, async () => statelessApi, getCloudFileUrl, new Map(), // Fresh variables cache per request
|
|
1183
|
-
{ isRemoteMode: true });
|
|
1183
|
+
{ isRemoteMode: true }, getCloudDesktopConnector);
|
|
1184
1184
|
registerLibraryTools(statelessServer, async () => statelessApi);
|
|
1185
1185
|
registerLibraryVariableTools(statelessServer, getCloudDesktopConnector);
|
|
1186
1186
|
await statelessServer.connect(transport);
|
|
@@ -1720,7 +1720,7 @@ export default {
|
|
|
1720
1720
|
return new Response(JSON.stringify({
|
|
1721
1721
|
status: "healthy",
|
|
1722
1722
|
service: "Figma Console MCP",
|
|
1723
|
-
version: "1.29.
|
|
1723
|
+
version: "1.29.2",
|
|
1724
1724
|
endpoints: {
|
|
1725
1725
|
mcp: ["/sse", "/mcp"],
|
|
1726
1726
|
oauth_mcp_spec: ["/.well-known/oauth-authorization-server", "/authorize", "/token", "/oauth/register"],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"design-code-tools.d.ts","sourceRoot":"","sources":["../../src/core/design-code-tools.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"design-code-tools.d.ts","sourceRoot":"","sources":["../../src/core/design-code-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -174,7 +174,7 @@ export function parseComponentDescription(description) {
|
|
|
174
174
|
for (const line of lines) {
|
|
175
175
|
const trimmed = line.trim();
|
|
176
176
|
// Detect section headers: bold text (**Header**), markdown headers (## Header), or plain text exact matches
|
|
177
|
-
const markdownHeaderMatch = trimmed.match(/^(
|
|
177
|
+
const markdownHeaderMatch = trimmed.match(/^(?:\*\*|#{1,6}\s*)(.+?)(?:\*\*)?$/);
|
|
178
178
|
const headerText = markdownHeaderMatch ? markdownHeaderMatch[1].trim().replace(/\*\*/g, "") : null;
|
|
179
179
|
// Check if this is a Figma per-property documentation block (e.g., "Show Left Icon: True – Purpose")
|
|
180
180
|
// These should be routed to "other" to avoid polluting content guidelines and accessibility sections
|
|
@@ -1380,7 +1380,47 @@ function buildParityInstruction(componentName, parityScore, counts, canonicalSou
|
|
|
1380
1380
|
// ============================================================================
|
|
1381
1381
|
// Documentation Section Generators
|
|
1382
1382
|
// ============================================================================
|
|
1383
|
-
|
|
1383
|
+
/**
|
|
1384
|
+
* Detect the atomic-design level (atom | molecule | organism | template) of a component
|
|
1385
|
+
* by finding its Figma page and walking the ordered page list back to the nearest
|
|
1386
|
+
* section-divider page (e.g. "ATOMS", "MOLECULES", "ORGANISMS"). Returns null when the
|
|
1387
|
+
* file doesn't use atomic-design page sections or the page can't be resolved — callers
|
|
1388
|
+
* then simply omit the `level` frontmatter. Best-effort and never throws.
|
|
1389
|
+
*/
|
|
1390
|
+
async function detectAtomicLevel(api, fileKey, nodeId, setNodeId, _componentMeta, _allComponentsMeta) {
|
|
1391
|
+
try {
|
|
1392
|
+
const targetId = setNodeId || nodeId;
|
|
1393
|
+
// Resolve the page the component lives on — independent of library-publish
|
|
1394
|
+
// status (published `containing_frame` metadata is empty for many files).
|
|
1395
|
+
// Requesting the file with `ids` returns every page in document order, but
|
|
1396
|
+
// prunes each page's children to only the path reaching the requested node,
|
|
1397
|
+
// so the single page whose subtree still contains the node is its home page.
|
|
1398
|
+
const pages = (await api.getFile(fileKey, { ids: [targetId] }))?.document?.children || [];
|
|
1399
|
+
const contains = (n) => n?.id === targetId || (Array.isArray(n?.children) && n.children.some(contains));
|
|
1400
|
+
const idx = pages.findIndex((p) => contains(p));
|
|
1401
|
+
if (idx < 0)
|
|
1402
|
+
return null;
|
|
1403
|
+
// Walk back to the nearest atomic-design divider page.
|
|
1404
|
+
const LEVELS = [
|
|
1405
|
+
["ATOM", "atom"],
|
|
1406
|
+
["MOLECULE", "molecule"],
|
|
1407
|
+
["ORGANISM", "organism"],
|
|
1408
|
+
["TEMPLATE", "template"],
|
|
1409
|
+
];
|
|
1410
|
+
for (let i = idx; i >= 0; i--) {
|
|
1411
|
+
const stripped = (pages[i]?.name || "").toUpperCase().replace(/[^A-Z]/g, "");
|
|
1412
|
+
for (const [marker, level] of LEVELS) {
|
|
1413
|
+
if (stripped.startsWith(marker))
|
|
1414
|
+
return level;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return null;
|
|
1418
|
+
}
|
|
1419
|
+
catch {
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
function generateFrontmatter(componentName, description, node, componentMeta, fileUrl, codeInfo, canonicalSource, level) {
|
|
1384
1424
|
const status = codeInfo?.changelog?.[0]
|
|
1385
1425
|
? "stable"
|
|
1386
1426
|
: componentMeta?.description?.toLowerCase().includes("deprecated")
|
|
@@ -1388,6 +1428,8 @@ function generateFrontmatter(componentName, description, node, componentMeta, fi
|
|
|
1388
1428
|
: "stable";
|
|
1389
1429
|
const version = codeInfo?.changelog?.[0]?.version || "1.0.0";
|
|
1390
1430
|
const tags = [componentName.toLowerCase()];
|
|
1431
|
+
if (level)
|
|
1432
|
+
tags.push(level);
|
|
1391
1433
|
if (node.type === "COMPONENT_SET")
|
|
1392
1434
|
tags.push("variants");
|
|
1393
1435
|
if (node.componentPropertyDefinitions)
|
|
@@ -1395,10 +1437,11 @@ function generateFrontmatter(componentName, description, node, componentMeta, fi
|
|
|
1395
1437
|
const lines = [
|
|
1396
1438
|
"---",
|
|
1397
1439
|
`title: ${componentName}`,
|
|
1398
|
-
`description: ${(description.split(
|
|
1440
|
+
`description: ${((description.split(/\n\s*\n|\n#{1,6}\s|\n\*\*/)[0] || description).replace(/\n/g, " ").replace(/\s+/g, " ").trim().split(/(?<=[.!?])\s+/)[0] || `${componentName} component`)}`,
|
|
1399
1441
|
`status: ${status}`,
|
|
1400
1442
|
`version: ${version}`,
|
|
1401
1443
|
`category: components`,
|
|
1444
|
+
...(level ? [`level: ${level}`] : []),
|
|
1402
1445
|
`tags: [${tags.join(", ")}]`,
|
|
1403
1446
|
`figma: ${fileUrl}`,
|
|
1404
1447
|
];
|
|
@@ -2560,7 +2603,9 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2560
2603
|
logger.warn("Desktop Bridge fetch failed, proceeding without bridge-sourced data");
|
|
2561
2604
|
}
|
|
2562
2605
|
}
|
|
2563
|
-
|
|
2606
|
+
// Strip any existing query (e.g. the connected file's ?node-id=<page>) before
|
|
2607
|
+
// appending the target node, otherwise the URL ends up with a doubled ?node-id=.
|
|
2608
|
+
const fileUrl_ = `${url.split("?")[0]}?node-id=${nodeId.replace(":", "-")}`;
|
|
2564
2609
|
// Parse the component description for structured content
|
|
2565
2610
|
const parsedDesc = parseComponentDescription(description);
|
|
2566
2611
|
// Determine canonical source
|
|
@@ -2589,7 +2634,8 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2589
2634
|
const parts = [];
|
|
2590
2635
|
const includedSections = [];
|
|
2591
2636
|
if (includeFrontmatter) {
|
|
2592
|
-
|
|
2637
|
+
const atomicLevel = await detectAtomicLevel(api, fileKey, nodeId, setInfo.setNodeId, componentMeta, allComponentsMeta);
|
|
2638
|
+
parts.push(generateFrontmatter(componentName, description, node, componentMeta, fileUrl_, codeInfo, canonicalSource, atomicLevel));
|
|
2593
2639
|
parts.push("");
|
|
2594
2640
|
}
|
|
2595
2641
|
if (s.overview) {
|