@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 +23 -4
- 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
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
|
|
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.
|
|
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
|
-
|
|
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) }],
|