@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.
- package/dist/cloudflare/core/cloud-websocket-connector.js +19 -17
- package/dist/cloudflare/core/design-code-tools.js +20 -10
- package/dist/cloudflare/core/figma-tools.js +15 -6
- package/dist/cloudflare/core/websocket-connector.js +24 -18
- package/dist/cloudflare/index.js +3 -3
- package/dist/core/design-code-tools.d.ts.map +1 -1
- package/dist/core/design-code-tools.js +20 -10
- package/dist/core/design-code-tools.js.map +1 -1
- package/dist/core/figma-tools.d.ts.map +1 -1
- package/dist/core/figma-tools.js +15 -6
- package/dist/core/figma-tools.js.map +1 -1
- package/dist/core/websocket-connector.d.ts.map +1 -1
- package/dist/core/websocket-connector.js +24 -18
- package/dist/core/websocket-connector.js.map +1 -1
- package/figma-desktop-bridge/code.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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,
|
|
2356
|
-
compareSpacing(nodeForVisual,
|
|
2357
|
-
compareTypography(nodeForVisual,
|
|
2358
|
-
compareTokens(enrichedData,
|
|
2359
|
-
compareComponentAPI(nodeForAPI,
|
|
2360
|
-
compareAccessibility(node,
|
|
2361
|
-
compareNaming(node,
|
|
2362
|
-
compareMetadata(node, componentMeta,
|
|
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:
|
|
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
|
-
|
|
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:
|
|
1555
|
-
collectionCount:
|
|
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:
|
|
1562
|
-
variables:
|
|
1563
|
-
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
}
|
package/dist/cloudflare/index.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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,
|
|
2356
|
-
compareSpacing(nodeForVisual,
|
|
2357
|
-
compareTypography(nodeForVisual,
|
|
2358
|
-
compareTokens(enrichedData,
|
|
2359
|
-
compareComponentAPI(nodeForAPI,
|
|
2360
|
-
compareAccessibility(node,
|
|
2361
|
-
compareNaming(node,
|
|
2362
|
-
compareMetadata(node, componentMeta,
|
|
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:
|
|
2423
|
+
codeData: codeSpecTyped,
|
|
2414
2424
|
};
|
|
2415
2425
|
return {
|
|
2416
2426
|
content: [{ type: "text", text: JSON.stringify(result) }],
|