@mp3wizard/figma-console-mcp 1.22.5 → 1.23.1
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 +15 -38
- package/dist/cloudflare/core/cloud-websocket-connector.js +19 -17
- package/dist/cloudflare/core/design-code-tools.js +23 -39
- package/dist/cloudflare/core/diff/changelog-formatter.js +275 -0
- package/dist/cloudflare/core/diff/diff-engine.js +334 -0
- package/dist/cloudflare/core/diff/property-compare.js +36 -0
- package/dist/cloudflare/core/diff/version-cache.js +74 -0
- package/dist/cloudflare/core/figma-api.js +19 -0
- package/dist/cloudflare/core/figma-tools.js +15 -6
- package/dist/cloudflare/core/version-tools.js +1014 -0
- package/dist/cloudflare/core/websocket-connector.js +24 -18
- package/dist/cloudflare/index.js +17 -13
- package/dist/core/design-code-tools.d.ts +1 -12
- package/dist/core/design-code-tools.d.ts.map +1 -1
- package/dist/core/design-code-tools.js +23 -39
- package/dist/core/design-code-tools.js.map +1 -1
- package/dist/core/diff/changelog-formatter.d.ts +35 -0
- package/dist/core/diff/changelog-formatter.d.ts.map +1 -0
- package/dist/core/diff/changelog-formatter.js +276 -0
- package/dist/core/diff/changelog-formatter.js.map +1 -0
- package/dist/core/diff/diff-engine.d.ts +113 -0
- package/dist/core/diff/diff-engine.d.ts.map +1 -0
- package/dist/core/diff/diff-engine.js +335 -0
- package/dist/core/diff/diff-engine.js.map +1 -0
- package/dist/core/diff/property-compare.d.ts +19 -0
- package/dist/core/diff/property-compare.d.ts.map +1 -0
- package/dist/core/diff/property-compare.js +37 -0
- package/dist/core/diff/property-compare.js.map +1 -0
- package/dist/core/diff/version-cache.d.ts +40 -0
- package/dist/core/diff/version-cache.d.ts.map +1 -0
- package/dist/core/diff/version-cache.js +75 -0
- package/dist/core/diff/version-cache.js.map +1 -0
- package/dist/core/figma-api.d.ts +29 -0
- package/dist/core/figma-api.d.ts.map +1 -1
- package/dist/core/figma-api.js +19 -0
- package/dist/core/figma-api.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/version-tools.d.ts +30 -0
- package/dist/core/version-tools.d.ts.map +1 -0
- package/dist/core/version-tools.js +1015 -0
- package/dist/core/version-tools.js.map +1 -0
- 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/dist/local.d.ts.map +1 -1
- package/dist/local.js +8 -0
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/code.js +1 -1
- package/package.json +108 -1
|
@@ -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
|
@@ -20,6 +20,7 @@ import { FigmaAPI, extractFileKey } from "./core/figma-api.js";
|
|
|
20
20
|
import { registerFigmaAPITools } from "./core/figma-tools.js";
|
|
21
21
|
import { registerDesignCodeTools } from "./core/design-code-tools.js";
|
|
22
22
|
import { registerCommentTools } from "./core/comment-tools.js";
|
|
23
|
+
import { registerVersionTools } from "./core/version-tools.js";
|
|
23
24
|
import { registerAnnotationTools } from "./core/annotation-tools.js";
|
|
24
25
|
import { registerDeepComponentTools } from "./core/deep-component-tools.js";
|
|
25
26
|
import { registerDesignSystemTools } from "./core/design-system-tools.js";
|
|
@@ -69,7 +70,7 @@ export class FigmaConsoleMCPv3 extends McpAgent {
|
|
|
69
70
|
super(...arguments);
|
|
70
71
|
this.server = new McpServer({
|
|
71
72
|
name: "Figma Console MCP",
|
|
72
|
-
version: "1.
|
|
73
|
+
version: "1.23.0",
|
|
73
74
|
});
|
|
74
75
|
this.browserManager = null;
|
|
75
76
|
this.consoleMonitor = null;
|
|
@@ -793,6 +794,8 @@ export class FigmaConsoleMCPv3 extends McpAgent {
|
|
|
793
794
|
{ isRemoteMode: true }, getCloudDesktopConnector);
|
|
794
795
|
// Register Comment tools
|
|
795
796
|
registerCommentTools(this.server, async () => await this.getFigmaAPI(), () => this.browserManager?.getCurrentUrl() || null, { isRemoteMode: true });
|
|
797
|
+
// Register Version History tools
|
|
798
|
+
registerVersionTools(this.server, async () => await this.getFigmaAPI(), () => this.browserManager?.getCurrentUrl() || null, { isRemoteMode: true });
|
|
796
799
|
// Register Design System Kit tool
|
|
797
800
|
registerDesignSystemTools(this.server, async () => await this.getFigmaAPI(), () => this.browserManager?.getCurrentUrl() || null, undefined, // variablesCache
|
|
798
801
|
{ isRemoteMode: true });
|
|
@@ -1055,7 +1058,7 @@ export default {
|
|
|
1055
1058
|
});
|
|
1056
1059
|
const statelessServer = new McpServer({
|
|
1057
1060
|
name: "Figma Console MCP",
|
|
1058
|
-
version: "1.
|
|
1061
|
+
version: "1.23.0",
|
|
1059
1062
|
});
|
|
1060
1063
|
// ================================================================
|
|
1061
1064
|
// Cloud Write Relay — Pairing Tool (stateless /mcp path)
|
|
@@ -1151,6 +1154,7 @@ export default {
|
|
|
1151
1154
|
registerDesignCodeTools(statelessServer, async () => statelessApi, getCloudFileUrl, new Map(), // Fresh variables cache per request
|
|
1152
1155
|
{ isRemoteMode: true }, getCloudDesktopConnector);
|
|
1153
1156
|
registerCommentTools(statelessServer, async () => statelessApi, getCloudFileUrl);
|
|
1157
|
+
registerVersionTools(statelessServer, async () => statelessApi, getCloudFileUrl);
|
|
1154
1158
|
registerDesignSystemTools(statelessServer, async () => statelessApi, getCloudFileUrl, new Map(), // Fresh variables cache per request
|
|
1155
1159
|
{ isRemoteMode: true });
|
|
1156
1160
|
await statelessServer.connect(transport);
|
|
@@ -1172,7 +1176,7 @@ export default {
|
|
|
1172
1176
|
const metadata = {
|
|
1173
1177
|
resource: url.origin,
|
|
1174
1178
|
authorization_servers: [`${url.origin}/`],
|
|
1175
|
-
scopes_supported: ["file_content:read", "file_variables:read", "library_content:read"],
|
|
1179
|
+
scopes_supported: ["file_content:read", "file_versions:read", "file_variables:read", "file_comments:read", "file_comments:write", "library_content:read"],
|
|
1176
1180
|
bearer_methods_supported: ["header"],
|
|
1177
1181
|
resource_signing_alg_values_supported: ["RS256"]
|
|
1178
1182
|
};
|
|
@@ -1191,7 +1195,7 @@ export default {
|
|
|
1191
1195
|
authorization_endpoint: `${url.origin}/authorize`,
|
|
1192
1196
|
token_endpoint: `${url.origin}/token`,
|
|
1193
1197
|
registration_endpoint: `${url.origin}/oauth/register`,
|
|
1194
|
-
scopes_supported: ["file_content:read", "file_variables:read", "library_content:read"],
|
|
1198
|
+
scopes_supported: ["file_content:read", "file_versions:read", "file_variables:read", "file_comments:read", "file_comments:write", "library_content:read"],
|
|
1195
1199
|
response_types_supported: ["code"],
|
|
1196
1200
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
1197
1201
|
token_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
|
|
@@ -1254,7 +1258,7 @@ export default {
|
|
|
1254
1258
|
const figmaAuthUrl = new URL("https://www.figma.com/oauth");
|
|
1255
1259
|
figmaAuthUrl.searchParams.set("client_id", env.FIGMA_OAUTH_CLIENT_ID);
|
|
1256
1260
|
figmaAuthUrl.searchParams.set("redirect_uri", `${oauthOrigin}/oauth/callback`);
|
|
1257
|
-
figmaAuthUrl.searchParams.set("scope", "file_content:read,file_variables:read,library_content:read");
|
|
1261
|
+
figmaAuthUrl.searchParams.set("scope", "file_content:read,file_versions:read,file_variables:read,file_comments:read,file_comments:write,library_content:read");
|
|
1258
1262
|
figmaAuthUrl.searchParams.set("state", stateToken);
|
|
1259
1263
|
figmaAuthUrl.searchParams.set("response_type", "code");
|
|
1260
1264
|
return Response.redirect(figmaAuthUrl.toString(), 302);
|
|
@@ -1295,7 +1299,7 @@ export default {
|
|
|
1295
1299
|
token_type: "Bearer",
|
|
1296
1300
|
expires_in: Math.max(0, Math.floor((tokenData.expiresAt - Date.now()) / 1000)),
|
|
1297
1301
|
refresh_token: tokenData.refreshToken,
|
|
1298
|
-
scope: "file_content:read file_variables:read library_content:read"
|
|
1302
|
+
scope: "file_content:read file_versions:read file_variables:read file_comments:read file_comments:write library_content:read"
|
|
1299
1303
|
}), {
|
|
1300
1304
|
headers: {
|
|
1301
1305
|
"Content-Type": "application/json",
|
|
@@ -1372,7 +1376,7 @@ export default {
|
|
|
1372
1376
|
token_type: "Bearer",
|
|
1373
1377
|
expires_in: tokenData.expires_in,
|
|
1374
1378
|
refresh_token: tokenData.refresh_token || refreshToken,
|
|
1375
|
-
scope: "file_content:read file_variables:read library_content:read"
|
|
1379
|
+
scope: "file_content:read file_versions:read file_variables:read file_comments:read file_comments:write library_content:read"
|
|
1376
1380
|
}), {
|
|
1377
1381
|
headers: {
|
|
1378
1382
|
"Content-Type": "application/json",
|
|
@@ -1444,7 +1448,7 @@ export default {
|
|
|
1444
1448
|
const figmaAuthUrl = new URL("https://www.figma.com/oauth");
|
|
1445
1449
|
figmaAuthUrl.searchParams.set("client_id", env.FIGMA_OAUTH_CLIENT_ID);
|
|
1446
1450
|
figmaAuthUrl.searchParams.set("redirect_uri", redirectUri);
|
|
1447
|
-
figmaAuthUrl.searchParams.set("scope", "file_content:read,file_variables:read,library_content:read");
|
|
1451
|
+
figmaAuthUrl.searchParams.set("scope", "file_content:read,file_versions:read,file_variables:read,file_comments:read,file_comments:write,library_content:read");
|
|
1448
1452
|
figmaAuthUrl.searchParams.set("state", stateToken);
|
|
1449
1453
|
figmaAuthUrl.searchParams.set("response_type", "code");
|
|
1450
1454
|
return Response.redirect(figmaAuthUrl.toString(), 302);
|
|
@@ -1690,7 +1694,7 @@ export default {
|
|
|
1690
1694
|
return new Response(JSON.stringify({
|
|
1691
1695
|
status: "healthy",
|
|
1692
1696
|
service: "Figma Console MCP",
|
|
1693
|
-
version: "1.
|
|
1697
|
+
version: "1.23.0",
|
|
1694
1698
|
endpoints: {
|
|
1695
1699
|
mcp: ["/sse", "/mcp"],
|
|
1696
1700
|
oauth_mcp_spec: ["/.well-known/oauth-authorization-server", "/authorize", "/token", "/oauth/register"],
|
|
@@ -1736,13 +1740,13 @@ export default {
|
|
|
1736
1740
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1737
1741
|
<title>Figma Console MCP - The Most Comprehensive MCP Server for Figma</title>
|
|
1738
1742
|
<link rel="icon" type="image/svg+xml" href="https://docs.figma-console-mcp.southleft.com/favicon.svg">
|
|
1739
|
-
<meta name="description" content="Turn your Figma design system into a living API.
|
|
1743
|
+
<meta name="description" content="Turn your Figma design system into a living API. 100+ tools give AI assistants deep access to design tokens, component specs, variables, and programmatic design creation.">
|
|
1740
1744
|
|
|
1741
1745
|
<!-- Open Graph -->
|
|
1742
1746
|
<meta property="og:type" content="website">
|
|
1743
1747
|
<meta property="og:url" content="https://figma-console-mcp.southleft.com">
|
|
1744
1748
|
<meta property="og:title" content="Figma Console MCP - Turn Your Design System Into a Living API">
|
|
1745
|
-
<meta property="og:description" content="The most comprehensive MCP server for Figma.
|
|
1749
|
+
<meta property="og:description" content="The most comprehensive MCP server for Figma. 100+ tools give AI assistants deep access to design tokens, components, variables, and programmatic design creation.">
|
|
1746
1750
|
<meta property="og:image" content="https://docs.figma-console-mcp.southleft.com/images/og-image.jpg">
|
|
1747
1751
|
<meta property="og:image:width" content="1200">
|
|
1748
1752
|
<meta property="og:image:height" content="630">
|
|
@@ -1750,7 +1754,7 @@ export default {
|
|
|
1750
1754
|
<!-- Twitter -->
|
|
1751
1755
|
<meta name="twitter:card" content="summary_large_image">
|
|
1752
1756
|
<meta name="twitter:title" content="Figma Console MCP - Turn Your Design System Into a Living API">
|
|
1753
|
-
<meta name="twitter:description" content="The most comprehensive MCP server for Figma.
|
|
1757
|
+
<meta name="twitter:description" content="The most comprehensive MCP server for Figma. 100+ tools give AI assistants deep access to design tokens, components, variables, and programmatic design creation.">
|
|
1754
1758
|
<meta name="twitter:image" content="https://docs.figma-console-mcp.southleft.com/images/og-image.jpg">
|
|
1755
1759
|
|
|
1756
1760
|
<meta name="theme-color" content="#0D9488">
|
|
@@ -2637,7 +2641,7 @@ export default {
|
|
|
2637
2641
|
<div class="grid-cell showcase-cell rule-left">
|
|
2638
2642
|
<div class="showcase-label">What AI Can Access</div>
|
|
2639
2643
|
<div class="showcase-stat">
|
|
2640
|
-
<span class="number">
|
|
2644
|
+
<span class="number">100+</span>
|
|
2641
2645
|
<span class="label">MCP tools for Figma</span>
|
|
2642
2646
|
</div>
|
|
2643
2647
|
<div class="capability-list">
|
|
@@ -6,17 +6,7 @@
|
|
|
6
6
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
7
|
import type { FigmaAPI } from "./figma-api.js";
|
|
8
8
|
import type { CompanyDocsContentEntry } from "./types/design-code.js";
|
|
9
|
-
|
|
10
|
-
export declare function figmaRGBAToHex(color: {
|
|
11
|
-
r: number;
|
|
12
|
-
g: number;
|
|
13
|
-
b: number;
|
|
14
|
-
a?: number;
|
|
15
|
-
}): string;
|
|
16
|
-
/** Normalize a color string for comparison (uppercase hex without alpha if fully opaque) */
|
|
17
|
-
export declare function normalizeColor(color: string): string;
|
|
18
|
-
/** Compare numeric values with a tolerance */
|
|
19
|
-
export declare function numericClose(a: number, b: number, tolerance?: number): boolean;
|
|
9
|
+
export { figmaRGBAToHex, normalizeColor, numericClose } from "./diff/property-compare.js";
|
|
20
10
|
/** Calculate parity score from discrepancy counts */
|
|
21
11
|
export declare function calculateParityScore(critical: number, major: number, minor: number, info: number): number;
|
|
22
12
|
/** Split markdown by H2 headers for platforms that need chunking */
|
|
@@ -123,5 +113,4 @@ export declare function registerDesignCodeTools(server: McpServer, getFigmaAPI:
|
|
|
123
113
|
}>, options?: {
|
|
124
114
|
isRemoteMode?: boolean;
|
|
125
115
|
}, getDesktopConnector?: () => Promise<any>): void;
|
|
126
|
-
export {};
|
|
127
116
|
//# sourceMappingURL=design-code-tools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"design-code-tools.d.ts","sourceRoot":"","sources":["../../src/core/design-code-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAK/C,OAAO,KAAK,EAUX,uBAAuB,EACvB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"design-code-tools.d.ts","sourceRoot":"","sources":["../../src/core/design-code-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,35 +12,9 @@ const enrichmentService = new EnrichmentService(logger);
|
|
|
12
12
|
// ============================================================================
|
|
13
13
|
// Shared Helpers
|
|
14
14
|
// ============================================================================
|
|
15
|
-
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
const g = Math.round(color.g * 255);
|
|
19
|
-
const b = Math.round(color.b * 255);
|
|
20
|
-
const hex = `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`.toUpperCase();
|
|
21
|
-
if (color.a !== undefined && color.a < 1) {
|
|
22
|
-
const a = Math.round(color.a * 255);
|
|
23
|
-
return `${hex}${a.toString(16).padStart(2, "0")}`;
|
|
24
|
-
}
|
|
25
|
-
return hex;
|
|
26
|
-
}
|
|
27
|
-
/** Normalize a color string for comparison (uppercase hex without alpha if fully opaque) */
|
|
28
|
-
export function normalizeColor(color) {
|
|
29
|
-
let c = color.trim().toUpperCase();
|
|
30
|
-
// Strip alpha if fully opaque (FF)
|
|
31
|
-
if (c.length === 9 && c.endsWith("FF")) {
|
|
32
|
-
c = c.slice(0, 7);
|
|
33
|
-
}
|
|
34
|
-
// Expand shorthand (#RGB -> #RRGGBB)
|
|
35
|
-
if (/^#[0-9A-F]{3}$/.test(c)) {
|
|
36
|
-
c = `#${c[1]}${c[1]}${c[2]}${c[2]}${c[3]}${c[3]}`;
|
|
37
|
-
}
|
|
38
|
-
return c;
|
|
39
|
-
}
|
|
40
|
-
/** Compare numeric values with a tolerance */
|
|
41
|
-
export function numericClose(a, b, tolerance = 1) {
|
|
42
|
-
return Math.abs(a - b) <= tolerance;
|
|
43
|
-
}
|
|
15
|
+
// Re-exported from the shared diff module so existing imports continue to work.
|
|
16
|
+
export { figmaRGBAToHex, normalizeColor, numericClose } from "./diff/property-compare.js";
|
|
17
|
+
import { figmaRGBAToHex, normalizeColor, numericClose } from "./diff/property-compare.js";
|
|
44
18
|
/** Calculate parity score from discrepancy counts */
|
|
45
19
|
export function calculateParityScore(critical, major, minor, info) {
|
|
46
20
|
return Math.max(0, 100 - (critical * 15 + major * 8 + minor * 3 + info * 1));
|
|
@@ -2180,7 +2154,11 @@ const codeSpecSchema = z.object({
|
|
|
2180
2154
|
semanticElement: z.string().optional().describe("Semantic HTML element (e.g., 'button', 'a', 'input')"),
|
|
2181
2155
|
supportsDisabled: z.boolean().optional().describe("Whether code supports disabled/aria-disabled state"),
|
|
2182
2156
|
supportsError: z.boolean().optional().describe("Whether code supports aria-invalid/error state"),
|
|
2183
|
-
|
|
2157
|
+
// NOTE: Use array-with-length, NOT z.tuple — tuples emit JSON Schema `items: [...]`
|
|
2158
|
+
// (array of schemas), which Gemini's stricter Function Calling validator rejects with
|
|
2159
|
+
// "is not of type 'object', 'boolean'". See issue #64. A constrained array emits
|
|
2160
|
+
// `items: { type: 'number' }` which all major MCP clients accept.
|
|
2161
|
+
renderedSize: z.array(z.number()).min(2).max(2).optional().describe("Rendered size [width, height] in px"),
|
|
2184
2162
|
}).optional().describe("Accessibility properties from code. Tip: use figma_scan_code_accessibility with mapToCodeSpec:true to auto-generate this from component HTML."),
|
|
2185
2163
|
metadata: z.object({
|
|
2186
2164
|
name: z.string().optional(),
|
|
@@ -2350,16 +2328,22 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2350
2328
|
logger.warn("Enrichment failed, proceeding without token data");
|
|
2351
2329
|
}
|
|
2352
2330
|
}
|
|
2331
|
+
// Cast to the structural CodeSpec interface. The Zod schema infers
|
|
2332
|
+
// `accessibility.renderedSize` as `number[]` (post-#64 fix uses
|
|
2333
|
+
// `z.array(z.number()).min(2).max(2)` for Gemini compat), but at runtime
|
|
2334
|
+
// the validator guarantees exactly two numbers, matching CodeSpec's
|
|
2335
|
+
// `[number, number]`. TypeScript can't bridge the inference gap.
|
|
2336
|
+
const codeSpecTyped = codeSpec;
|
|
2353
2337
|
// Run all comparators (use nodeForVisual for design properties, nodeForAPI for component API)
|
|
2354
2338
|
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,
|
|
2339
|
+
compareVisual(nodeForVisual, codeSpecTyped, discrepancies);
|
|
2340
|
+
compareSpacing(nodeForVisual, codeSpecTyped, discrepancies);
|
|
2341
|
+
compareTypography(nodeForVisual, codeSpecTyped, discrepancies);
|
|
2342
|
+
compareTokens(enrichedData, codeSpecTyped, discrepancies);
|
|
2343
|
+
compareComponentAPI(nodeForAPI, codeSpecTyped, discrepancies);
|
|
2344
|
+
compareAccessibility(node, codeSpecTyped, discrepancies);
|
|
2345
|
+
compareNaming(node, codeSpecTyped, discrepancies);
|
|
2346
|
+
compareMetadata(node, componentMeta, codeSpecTyped, discrepancies);
|
|
2363
2347
|
// Sort by severity
|
|
2364
2348
|
const severityOrder = {
|
|
2365
2349
|
critical: 0,
|
|
@@ -2410,7 +2394,7 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2410
2394
|
: [],
|
|
2411
2395
|
tokenCoverage: enrichedData?.token_coverage,
|
|
2412
2396
|
},
|
|
2413
|
-
codeData:
|
|
2397
|
+
codeData: codeSpecTyped,
|
|
2414
2398
|
};
|
|
2415
2399
|
return {
|
|
2416
2400
|
content: [{ type: "text", text: JSON.stringify(result) }],
|