figma-console-mcp 1.29.0 → 1.29.2
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/design-system-tools.d.ts +1 -1
- package/dist/core/design-system-tools.d.ts.map +1 -1
- package/dist/core/design-system-tools.js +19 -9
- package/dist/core/design-system-tools.js.map +1 -1
- package/dist/core/tokens-tools.js +1 -1
- package/dist/core/variable-resolver.d.ts +45 -0
- package/dist/core/variable-resolver.d.ts.map +1 -0
- package/dist/core/variable-resolver.js +86 -0
- package/dist/core/variable-resolver.js.map +1 -0
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +2 -1
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/code.js +1 -1
- 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
|
-
> **🆕 Shared Library Inspection (v1.29.0):** Three new tools fill the gap between "I see a component key in search results" and "I can actually use it." `figma_get_library_component_by_key` resolves any component key to full property definitions + variant keys + visual specs — without needing the source library file's URL. `figma_get_library_variables` lists every variable from your subscribed libraries (no Enterprise plan required — uses the Plugin API path that works on Pro and Org). `figma_import_library_variable` brings a library token into the current file so it can be bound to nodes alongside the file's own variables. Works on every Figma plan. 106 tools total. [See what's new →](CHANGELOG.md#
|
|
11
|
+
> **🆕 Shared Library Inspection (v1.29.0, patched v1.29.1–v1.29.2):** Three new tools fill the gap between "I see a component key in search results" and "I can actually use it." `figma_get_library_component_by_key` resolves any component key to full property definitions + variant keys + visual specs — without needing the source library file's URL. `figma_get_library_variables` lists every variable from your subscribed libraries (no Enterprise plan required — uses the Plugin API path that works on Pro and Org). `figma_import_library_variable` brings a library token into the current file so it can be bound to nodes alongside the file's own variables. Works on every Figma plan. 106 tools total. **Patched v1.29.1:** `figma_get_design_system_kit` now reads variables on any plan via the bridge too — no more 403 for non-Enterprise users. **Patched v1.29.2:** `figma_generate_component_doc` renders component descriptions faithfully and auto-detects each component's atomic-design level. [See what's new →](CHANGELOG.md#1292---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.29.
|
|
811
|
+
**Current Status:** v1.29.2 (Stable) - Production-ready with 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.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.
|
|
815
|
+
- [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.
|
|
814
816
|
- [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.
|
|
815
817
|
- [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.
|
|
816
818
|
- [x] **v1.28.0** - Full formatter coverage for `figma_export_tokens`. Seven new output formats: Tailwind v4 `@theme inline`, Tailwind v3 config, SCSS variables, TypeScript module, JSON flat/nested, Style Dictionary v3, Tokens Studio multi-file. Combined with DTCG + CSS variables, ships **10 fully-implemented output formats** with zero third-party build-tool dependencies. Tool description updated, docs/tools.md table all-green. 22 new Jest tests, 1151 total 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) {
|