@mp3wizard/figma-console-mcp 1.22.5 → 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.
@@ -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) }],