@mp3wizard/figma-console-mcp 1.22.4 → 1.22.6

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
@@ -17,12 +17,11 @@
17
17
 
18
18
  Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
19
19
 
20
- - **🐛 Plugin debugging** - Capture console logs, errors, and stack traces
21
- - **📸 Visual debugging** - Take screenshots for context
22
20
  - **🎨 Design system extraction** - Pull variables, components, and styles
21
+ - **📸 Visual debugging** - Take screenshots for context
23
22
  - **✏️ Design creation** - Create UI components, frames, and layouts directly in Figma
24
23
  - **🔧 Variable management** - Create, update, rename, and delete design tokens
25
- - **⚡ Real-time monitoring** - Watch logs as plugins execute
24
+ - **⚡ Real-time monitoring** - Watch console logs from the Desktop Bridge plugin
26
25
  - **📌 FigJam boards** - Create stickies, flowcharts, tables, and code blocks on collaborative boards
27
26
  - **♿ Accessibility scanning** - 14 WCAG design checks with conformance level tagging, component scorecards, axe-core code scanning, design-to-code parity
28
27
  - **☁️ Cloud Write Relay** - Web AI clients (Claude.ai, v0, Replit) can design in Figma via cloud pairing
@@ -792,9 +791,11 @@ The architecture supports adding new apps with minimal boilerplate — each app
792
791
 
793
792
  ## 🛤️ Roadmap
794
793
 
795
- **Current Status:** v1.17.0 (Stable) - Production-ready with FigJam + Slides support, Cloud Write Relay, Design System Kit, WebSocket-only connectivity, smart multi-file tracking, 94+ tools, Comments API, and MCP Apps
794
+ **Current Status:** v1.22.4 (Stable) - Production-ready with 14 WCAG accessibility rules, Phase B lint checks (disabled variant context + token misuse detection), FigJam + Slides support, Cloud Write Relay, Design System Kit, WebSocket-only connectivity, smart multi-file tracking, 94+ tools, Comments API, and MCP Apps
796
795
 
797
796
  **Recent Releases:**
797
+ - [x] **v1.22.4** - Security: fix 6 Medium hono/node-server CVEs via package.json overrides (CVE-2026-39406 through CVE-2026-39410, GHSA-26pp-8wgv-hjvm, GHSA-xpcf-pg52-r92g)
798
+ - [x] **v1.22.3** - Phase B accessibility: disabled variant context check, token misuse detection, WCAG interpretation fixes from accessibility consultant review, rule count 13 to 14
798
799
  - [x] **v1.17.0** - Figma Slides Support: 15 new tools for managing presentations — slides, transitions, content, reordering, and navigation. Inspired by Toni Haidamous (PR #11).
799
800
  - [x] **v1.16.0** - FigJam Support: 9 new tools for creating and reading FigJam boards — stickies, flowcharts, tables, code blocks, and connection graphs. Community-contributed by klgral and lukemoderwell.
800
801
  - [x] **v1.12.0** - Cloud Write Relay: web AI clients (Claude.ai, v0, Replit, Lovable) can create and modify Figma designs via cloud relay pairing — no Node.js required
@@ -839,6 +840,24 @@ npm run build
839
840
 
840
841
  ---
841
842
 
843
+ ## 🔒 Network Transparency
844
+
845
+ All outbound network connections made by this MCP server:
846
+
847
+ | Destination | Protocol | Purpose | Data Sent |
848
+ |-------------|----------|---------|-----------|
849
+ | `api.figma.com` | HTTPS | REST API (files, variables, components, styles, images, comments) | File keys, node IDs, API parameters |
850
+ | `www.figma.com` | HTTPS | OAuth 2.0 authorization flow | Client ID, auth codes, refresh tokens |
851
+ | `figma-console-mcp.southleft.com` | WSS/HTTPS | Cloud relay for web AI clients (Cloud Mode only) | Metadata only: fileName, fileKey, currentPage |
852
+ | Figma S3 CDN | HTTPS | Rendered image downloads (temporary URLs) | None (download only) |
853
+ | `localhost:9223-9232` | WS | Desktop Bridge plugin (local only) | Plugin commands/responses |
854
+
855
+ **Not present:** telemetry, analytics, tracking, third-party data services, obfuscated code, or environment variable leakage. Full audit available in [`Security review report/`](Security%20review%20report/).
856
+
857
+ > **Local Mode users:** `src/local.ts` does not connect to the cloud relay — only Figma API and localhost WebSocket.
858
+
859
+ ---
860
+
842
861
  ## 📄 License
843
862
 
844
863
  MIT - See [LICENSE](LICENSE) file for details.
@@ -32,25 +32,27 @@ export class CloudWebSocketConnector {
32
32
  return this.sendCommand('GET_VARIABLES_DATA', {}, 10000);
33
33
  }
34
34
  async getVariables(fileKey) {
35
+ // IMPORTANT: bare try/catch with top-level `return`, NO inner IIFE.
36
+ // See issue #68 + the matching note in websocket-connector.ts. The plugin
37
+ // (code.js) wraps every EXECUTE_CODE payload in its own async IIFE; nesting
38
+ // another swallows the inner return and silently drops the variables.
35
39
  const code = `
36
- (async () => {
37
- try {
38
- if (typeof figma === 'undefined') {
39
- throw new Error('Figma API not available in this context');
40
- }
41
- const variables = await figma.variables.getLocalVariablesAsync();
42
- const collections = await figma.variables.getLocalVariableCollectionsAsync();
43
- return {
44
- success: true,
45
- timestamp: Date.now(),
46
- fileMetadata: { fileName: figma.root.name, fileKey: figma.fileKey || null },
47
- variables: variables.map(function(v) { return { id: v.id, name: v.name, key: v.key, resolvedType: v.resolvedType, valuesByMode: v.valuesByMode, variableCollectionId: v.variableCollectionId, scopes: v.scopes, description: v.description, hiddenFromPublishing: v.hiddenFromPublishing }; }),
48
- variableCollections: collections.map(function(c) { return { id: c.id, name: c.name, key: c.key, modes: c.modes, defaultModeId: c.defaultModeId, variableIds: c.variableIds }; })
49
- };
50
- } catch (error) {
51
- return { success: false, error: error.message };
40
+ try {
41
+ if (typeof figma === 'undefined') {
42
+ throw new Error('Figma API not available in this context');
52
43
  }
53
- })()
44
+ const variables = await figma.variables.getLocalVariablesAsync();
45
+ const collections = await figma.variables.getLocalVariableCollectionsAsync();
46
+ return {
47
+ success: true,
48
+ timestamp: Date.now(),
49
+ fileMetadata: { fileName: figma.root.name, fileKey: figma.fileKey || null },
50
+ variables: variables.map(function(v) { return { id: v.id, name: v.name, key: v.key, resolvedType: v.resolvedType, valuesByMode: v.valuesByMode, variableCollectionId: v.variableCollectionId, scopes: v.scopes, description: v.description, hiddenFromPublishing: v.hiddenFromPublishing }; }),
51
+ variableCollections: collections.map(function(c) { return { id: c.id, name: c.name, key: c.key, modes: c.modes, defaultModeId: c.defaultModeId, variableIds: c.variableIds }; })
52
+ };
53
+ } catch (error) {
54
+ return { success: false, error: error.message };
55
+ }
54
56
  `;
55
57
  return this.sendCommand('EXECUTE_CODE', { code, timeout: 30000 }, 32000);
56
58
  }
@@ -2180,7 +2180,11 @@ const codeSpecSchema = z.object({
2180
2180
  semanticElement: z.string().optional().describe("Semantic HTML element (e.g., 'button', 'a', 'input')"),
2181
2181
  supportsDisabled: z.boolean().optional().describe("Whether code supports disabled/aria-disabled state"),
2182
2182
  supportsError: z.boolean().optional().describe("Whether code supports aria-invalid/error state"),
2183
- renderedSize: z.tuple([z.number(), z.number()]).optional().describe("Rendered size [width, height] in px"),
2183
+ // NOTE: Use array-with-length, NOT z.tuple tuples emit JSON Schema `items: [...]`
2184
+ // (array of schemas), which Gemini's stricter Function Calling validator rejects with
2185
+ // "is not of type 'object', 'boolean'". See issue #64. A constrained array emits
2186
+ // `items: { type: 'number' }` which all major MCP clients accept.
2187
+ renderedSize: z.array(z.number()).min(2).max(2).optional().describe("Rendered size [width, height] in px"),
2184
2188
  }).optional().describe("Accessibility properties from code. Tip: use figma_scan_code_accessibility with mapToCodeSpec:true to auto-generate this from component HTML."),
2185
2189
  metadata: z.object({
2186
2190
  name: z.string().optional(),
@@ -2350,16 +2354,22 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
2350
2354
  logger.warn("Enrichment failed, proceeding without token data");
2351
2355
  }
2352
2356
  }
2357
+ // Cast to the structural CodeSpec interface. The Zod schema infers
2358
+ // `accessibility.renderedSize` as `number[]` (post-#64 fix uses
2359
+ // `z.array(z.number()).min(2).max(2)` for Gemini compat), but at runtime
2360
+ // the validator guarantees exactly two numbers, matching CodeSpec's
2361
+ // `[number, number]`. TypeScript can't bridge the inference gap.
2362
+ const codeSpecTyped = codeSpec;
2353
2363
  // Run all comparators (use nodeForVisual for design properties, nodeForAPI for component API)
2354
2364
  const discrepancies = [];
2355
- compareVisual(nodeForVisual, codeSpec, discrepancies);
2356
- compareSpacing(nodeForVisual, codeSpec, discrepancies);
2357
- compareTypography(nodeForVisual, codeSpec, discrepancies);
2358
- compareTokens(enrichedData, codeSpec, discrepancies);
2359
- compareComponentAPI(nodeForAPI, codeSpec, discrepancies);
2360
- compareAccessibility(node, codeSpec, discrepancies);
2361
- compareNaming(node, codeSpec, discrepancies);
2362
- compareMetadata(node, componentMeta, codeSpec, discrepancies);
2365
+ compareVisual(nodeForVisual, codeSpecTyped, discrepancies);
2366
+ compareSpacing(nodeForVisual, codeSpecTyped, discrepancies);
2367
+ compareTypography(nodeForVisual, codeSpecTyped, discrepancies);
2368
+ compareTokens(enrichedData, codeSpecTyped, discrepancies);
2369
+ compareComponentAPI(nodeForAPI, codeSpecTyped, discrepancies);
2370
+ compareAccessibility(node, codeSpecTyped, discrepancies);
2371
+ compareNaming(node, codeSpecTyped, discrepancies);
2372
+ compareMetadata(node, componentMeta, codeSpecTyped, discrepancies);
2363
2373
  // Sort by severity
2364
2374
  const severityOrder = {
2365
2375
  critical: 0,
@@ -2410,7 +2420,7 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
2410
2420
  : [],
2411
2421
  tokenCoverage: enrichedData?.token_coverage,
2412
2422
  },
2413
- codeData: codeSpec,
2423
+ codeData: codeSpecTyped,
2414
2424
  };
2415
2425
  return {
2416
2426
  content: [{ type: "text", text: JSON.stringify(result) }],
@@ -1549,18 +1549,27 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
1549
1549
  const desktopResult = refreshCache
1550
1550
  ? await connector.getVariables(fileKey)
1551
1551
  : await connector.getVariablesFromPluginUI(fileKey);
1552
- if (desktopResult.success && desktopResult.variables) {
1552
+ // EXECUTE_CODE responses come back wrapped one level deeper:
1553
+ // `{ success: true, result: { success: true, variables, ... } }`
1554
+ // because handleResult in ui-full.html nests the script return value
1555
+ // under `result`. The plugin-UI cache path (GET_VARIABLES_DATA) does
1556
+ // not nest. Unwrap when we detect the EXECUTE_CODE shape so both
1557
+ // paths produce a uniform { success, variables, ... } below. See #68.
1558
+ const variableData = desktopResult?.result?.variables
1559
+ ? desktopResult.result
1560
+ : desktopResult;
1561
+ if (variableData?.success && variableData?.variables) {
1553
1562
  logger.info({
1554
- variableCount: desktopResult.variables.length,
1555
- collectionCount: desktopResult.variableCollections?.length
1563
+ variableCount: variableData.variables.length,
1564
+ collectionCount: variableData.variableCollections?.length
1556
1565
  }, "Successfully retrieved variables via Desktop connection!");
1557
1566
  // Prepare data for caching (using the raw data, not enriched)
1558
1567
  const dataForCache = {
1559
1568
  fileKey,
1560
1569
  source: "desktop_connection",
1561
- timestamp: desktopResult.timestamp || Date.now(),
1562
- variables: desktopResult.variables,
1563
- variableCollections: desktopResult.variableCollections,
1570
+ timestamp: variableData.timestamp || Date.now(),
1571
+ variables: variableData.variables,
1572
+ variableCollections: variableData.variableCollections,
1564
1573
  };
1565
1574
  // Store in cache with LRU eviction
1566
1575
  if (variablesCache) {
@@ -33,26 +33,32 @@ export class WebSocketConnector {
33
33
  return this.wsServer.sendCommand('GET_VARIABLES_DATA', {}, 10000, fileKey);
34
34
  }
35
35
  async getVariables(fileKey) {
36
- // Execute the same variables-fetching code in the plugin worker context
36
+ // Execute the same variables-fetching code in the plugin worker context.
37
+ //
38
+ // IMPORTANT: Do NOT wrap this in an inner `(async () => { ... })()` IIFE.
39
+ // figma-desktop-bridge/code.js already wraps every EXECUTE_CODE payload in
40
+ // `(async function() { <code> })()`. An inner IIFE turns `return X` into a
41
+ // statement-expression that builds (but doesn't return) a Promise — the
42
+ // outer async returns undefined, and the result is silently dropped. See
43
+ // issue #68. The bare try/catch with top-level `return` is the contract
44
+ // code.js expects.
37
45
  const code = `
38
- (async () => {
39
- try {
40
- if (typeof figma === 'undefined') {
41
- throw new Error('Figma API not available in this context');
42
- }
43
- const variables = await figma.variables.getLocalVariablesAsync();
44
- const collections = await figma.variables.getLocalVariableCollectionsAsync();
45
- return {
46
- success: true,
47
- timestamp: Date.now(),
48
- fileMetadata: { fileName: figma.root.name, fileKey: figma.fileKey || null },
49
- variables: variables.map(function(v) { return { id: v.id, name: v.name, key: v.key, resolvedType: v.resolvedType, valuesByMode: v.valuesByMode, variableCollectionId: v.variableCollectionId, scopes: v.scopes, description: v.description, hiddenFromPublishing: v.hiddenFromPublishing }; }),
50
- variableCollections: collections.map(function(c) { return { id: c.id, name: c.name, key: c.key, modes: c.modes, defaultModeId: c.defaultModeId, variableIds: c.variableIds }; })
51
- };
52
- } catch (error) {
53
- return { success: false, error: error.message };
46
+ try {
47
+ if (typeof figma === 'undefined') {
48
+ throw new Error('Figma API not available in this context');
54
49
  }
55
- })()
50
+ const variables = await figma.variables.getLocalVariablesAsync();
51
+ const collections = await figma.variables.getLocalVariableCollectionsAsync();
52
+ return {
53
+ success: true,
54
+ timestamp: Date.now(),
55
+ fileMetadata: { fileName: figma.root.name, fileKey: figma.fileKey || null },
56
+ variables: variables.map(function(v) { return { id: v.id, name: v.name, key: v.key, resolvedType: v.resolvedType, valuesByMode: v.valuesByMode, variableCollectionId: v.variableCollectionId, scopes: v.scopes, description: v.description, hiddenFromPublishing: v.hiddenFromPublishing }; }),
57
+ variableCollections: collections.map(function(c) { return { id: c.id, name: c.name, key: c.key, modes: c.modes, defaultModeId: c.defaultModeId, variableIds: c.variableIds }; })
58
+ };
59
+ } catch (error) {
60
+ return { success: false, error: error.message };
61
+ }
56
62
  `;
57
63
  return this.wsServer.sendCommand('EXECUTE_CODE', { code, timeout: 30000 }, 32000, fileKey);
58
64
  }
@@ -69,7 +69,7 @@ export class FigmaConsoleMCPv3 extends McpAgent {
69
69
  super(...arguments);
70
70
  this.server = new McpServer({
71
71
  name: "Figma Console MCP",
72
- version: "1.22.0",
72
+ version: "1.22.4",
73
73
  });
74
74
  this.browserManager = null;
75
75
  this.consoleMonitor = null;
@@ -1055,7 +1055,7 @@ export default {
1055
1055
  });
1056
1056
  const statelessServer = new McpServer({
1057
1057
  name: "Figma Console MCP",
1058
- version: "1.22.0",
1058
+ version: "1.22.4",
1059
1059
  });
1060
1060
  // ================================================================
1061
1061
  // Cloud Write Relay — Pairing Tool (stateless /mcp path)
@@ -1690,7 +1690,7 @@ export default {
1690
1690
  return new Response(JSON.stringify({
1691
1691
  status: "healthy",
1692
1692
  service: "Figma Console MCP",
1693
- version: "1.22.0",
1693
+ version: "1.22.4",
1694
1694
  endpoints: {
1695
1695
  mcp: ["/sse", "/mcp"],
1696
1696
  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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"}
@@ -2180,7 +2180,11 @@ const codeSpecSchema = z.object({
2180
2180
  semanticElement: z.string().optional().describe("Semantic HTML element (e.g., 'button', 'a', 'input')"),
2181
2181
  supportsDisabled: z.boolean().optional().describe("Whether code supports disabled/aria-disabled state"),
2182
2182
  supportsError: z.boolean().optional().describe("Whether code supports aria-invalid/error state"),
2183
- renderedSize: z.tuple([z.number(), z.number()]).optional().describe("Rendered size [width, height] in px"),
2183
+ // NOTE: Use array-with-length, NOT z.tuple tuples emit JSON Schema `items: [...]`
2184
+ // (array of schemas), which Gemini's stricter Function Calling validator rejects with
2185
+ // "is not of type 'object', 'boolean'". See issue #64. A constrained array emits
2186
+ // `items: { type: 'number' }` which all major MCP clients accept.
2187
+ renderedSize: z.array(z.number()).min(2).max(2).optional().describe("Rendered size [width, height] in px"),
2184
2188
  }).optional().describe("Accessibility properties from code. Tip: use figma_scan_code_accessibility with mapToCodeSpec:true to auto-generate this from component HTML."),
2185
2189
  metadata: z.object({
2186
2190
  name: z.string().optional(),
@@ -2350,16 +2354,22 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
2350
2354
  logger.warn("Enrichment failed, proceeding without token data");
2351
2355
  }
2352
2356
  }
2357
+ // Cast to the structural CodeSpec interface. The Zod schema infers
2358
+ // `accessibility.renderedSize` as `number[]` (post-#64 fix uses
2359
+ // `z.array(z.number()).min(2).max(2)` for Gemini compat), but at runtime
2360
+ // the validator guarantees exactly two numbers, matching CodeSpec's
2361
+ // `[number, number]`. TypeScript can't bridge the inference gap.
2362
+ const codeSpecTyped = codeSpec;
2353
2363
  // Run all comparators (use nodeForVisual for design properties, nodeForAPI for component API)
2354
2364
  const discrepancies = [];
2355
- compareVisual(nodeForVisual, codeSpec, discrepancies);
2356
- compareSpacing(nodeForVisual, codeSpec, discrepancies);
2357
- compareTypography(nodeForVisual, codeSpec, discrepancies);
2358
- compareTokens(enrichedData, codeSpec, discrepancies);
2359
- compareComponentAPI(nodeForAPI, codeSpec, discrepancies);
2360
- compareAccessibility(node, codeSpec, discrepancies);
2361
- compareNaming(node, codeSpec, discrepancies);
2362
- compareMetadata(node, componentMeta, codeSpec, discrepancies);
2365
+ compareVisual(nodeForVisual, codeSpecTyped, discrepancies);
2366
+ compareSpacing(nodeForVisual, codeSpecTyped, discrepancies);
2367
+ compareTypography(nodeForVisual, codeSpecTyped, discrepancies);
2368
+ compareTokens(enrichedData, codeSpecTyped, discrepancies);
2369
+ compareComponentAPI(nodeForAPI, codeSpecTyped, discrepancies);
2370
+ compareAccessibility(node, codeSpecTyped, discrepancies);
2371
+ compareNaming(node, codeSpecTyped, discrepancies);
2372
+ compareMetadata(node, componentMeta, codeSpecTyped, discrepancies);
2363
2373
  // Sort by severity
2364
2374
  const severityOrder = {
2365
2375
  critical: 0,
@@ -2410,7 +2420,7 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
2410
2420
  : [],
2411
2421
  tokenCoverage: enrichedData?.token_coverage,
2412
2422
  },
2413
- codeData: codeSpec,
2423
+ codeData: codeSpecTyped,
2414
2424
  };
2415
2425
  return {
2416
2426
  content: [{ type: "text", text: JSON.stringify(result) }],