@mp3wizard/figma-console-mcp 1.17.3 → 1.19.2
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 +13 -12
- package/dist/cloudflare/core/annotation-tools.js +230 -0
- package/dist/cloudflare/core/cloud-websocket-connector.js +93 -0
- package/dist/cloudflare/core/deep-component-tools.js +128 -0
- package/dist/cloudflare/core/design-code-tools.js +65 -7
- package/dist/cloudflare/core/enrichment/enrichment-service.js +108 -12
- package/dist/cloudflare/core/figjam-tools.js +485 -0
- package/dist/cloudflare/core/figma-api.js +7 -4
- package/dist/cloudflare/core/figma-desktop-connector.js +108 -0
- package/dist/cloudflare/core/figma-tools.js +445 -55
- package/dist/cloudflare/core/port-discovery.js +88 -0
- package/dist/cloudflare/core/resolve-package-root.js +11 -0
- package/dist/cloudflare/core/slides-tools.js +607 -0
- package/dist/cloudflare/core/websocket-connector.js +93 -0
- package/dist/cloudflare/core/websocket-server.js +18 -9
- package/dist/cloudflare/index.js +164 -41
- package/dist/core/annotation-tools.d.ts +14 -0
- package/dist/core/annotation-tools.d.ts.map +1 -0
- package/dist/core/annotation-tools.js +231 -0
- package/dist/core/annotation-tools.js.map +1 -0
- package/dist/core/deep-component-tools.d.ts +14 -0
- package/dist/core/deep-component-tools.d.ts.map +1 -0
- package/dist/core/deep-component-tools.js +129 -0
- package/dist/core/deep-component-tools.js.map +1 -0
- package/dist/core/design-code-tools.d.ts.map +1 -1
- package/dist/core/design-code-tools.js +65 -7
- package/dist/core/design-code-tools.js.map +1 -1
- package/dist/core/enrichment/enrichment-service.d.ts.map +1 -1
- package/dist/core/enrichment/enrichment-service.js +108 -12
- package/dist/core/enrichment/enrichment-service.js.map +1 -1
- package/dist/core/figma-api.d.ts +1 -1
- package/dist/core/figma-api.d.ts.map +1 -1
- package/dist/core/figma-api.js +7 -4
- package/dist/core/figma-api.js.map +1 -1
- package/dist/core/figma-connector.d.ts +5 -0
- package/dist/core/figma-connector.d.ts.map +1 -1
- package/dist/core/figma-desktop-connector.d.ts +20 -0
- package/dist/core/figma-desktop-connector.d.ts.map +1 -1
- package/dist/core/figma-desktop-connector.js +83 -0
- package/dist/core/figma-desktop-connector.js.map +1 -1
- package/dist/core/figma-tools.d.ts.map +1 -1
- package/dist/core/figma-tools.js +355 -26
- package/dist/core/figma-tools.js.map +1 -1
- package/dist/core/port-discovery.d.ts +21 -0
- package/dist/core/port-discovery.d.ts.map +1 -1
- package/dist/core/port-discovery.js +88 -0
- package/dist/core/port-discovery.js.map +1 -1
- package/dist/core/resolve-package-root.d.ts +2 -0
- package/dist/core/resolve-package-root.d.ts.map +1 -0
- package/dist/core/resolve-package-root.js +12 -0
- package/dist/core/resolve-package-root.js.map +1 -0
- package/dist/core/types/design-code.d.ts +1 -0
- package/dist/core/types/design-code.d.ts.map +1 -1
- package/dist/core/websocket-connector.d.ts +5 -0
- package/dist/core/websocket-connector.d.ts.map +1 -1
- package/dist/core/websocket-connector.js +18 -0
- package/dist/core/websocket-connector.js.map +1 -1
- package/dist/core/websocket-server.d.ts.map +1 -1
- package/dist/core/websocket-server.js +7 -9
- package/dist/core/websocket-server.js.map +1 -1
- package/dist/local.d.ts +6 -0
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +58 -1
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/code.js +906 -4
- package/figma-desktop-bridge/ui-full.html +80 -0
- package/figma-desktop-bridge/ui.html +82 -0
- package/package.json +1 -1
|
@@ -1748,6 +1748,50 @@ function generateAccessibilitySection(node, parsedDesc, codeInfo) {
|
|
|
1748
1748
|
}
|
|
1749
1749
|
return lines.join("\n");
|
|
1750
1750
|
}
|
|
1751
|
+
function generateDesignAnnotationsSection(node) {
|
|
1752
|
+
// Annotations come from the Desktop Bridge plugin (node.annotations)
|
|
1753
|
+
// They are available when the component was fetched via Desktop Bridge
|
|
1754
|
+
const annotations = node.annotations || [];
|
|
1755
|
+
if (annotations.length === 0) {
|
|
1756
|
+
return [
|
|
1757
|
+
"",
|
|
1758
|
+
"## Design Annotations",
|
|
1759
|
+
"",
|
|
1760
|
+
"_No design annotations found on this node. Designers can add annotations in Dev Mode to specify animation timings, easing curves, interaction behaviors, and other implementation details._",
|
|
1761
|
+
"_Use `figma_get_annotations` with `include_children=true` to check child nodes for annotations._",
|
|
1762
|
+
"",
|
|
1763
|
+
].join("\n");
|
|
1764
|
+
}
|
|
1765
|
+
const lines = ["", "## Design Annotations", ""];
|
|
1766
|
+
lines.push(`Found **${annotations.length}** annotation(s) on this component:`, "");
|
|
1767
|
+
for (let i = 0; i < annotations.length; i++) {
|
|
1768
|
+
const ann = annotations[i];
|
|
1769
|
+
const num = i + 1;
|
|
1770
|
+
// Header with category if available
|
|
1771
|
+
const categoryLabel = ann.categoryName ? ` (${ann.categoryName})` : (ann.categoryId ? ` (category: ${ann.categoryId})` : "");
|
|
1772
|
+
lines.push(`### Annotation ${num}${categoryLabel}`);
|
|
1773
|
+
lines.push("");
|
|
1774
|
+
// Label content
|
|
1775
|
+
if (ann.labelMarkdown) {
|
|
1776
|
+
// Indent markdown content and render it
|
|
1777
|
+
lines.push(ann.labelMarkdown);
|
|
1778
|
+
lines.push("");
|
|
1779
|
+
}
|
|
1780
|
+
else if (ann.label) {
|
|
1781
|
+
lines.push(ann.label);
|
|
1782
|
+
lines.push("");
|
|
1783
|
+
}
|
|
1784
|
+
// Pinned properties
|
|
1785
|
+
if (ann.properties && ann.properties.length > 0) {
|
|
1786
|
+
lines.push("**Pinned Properties:**");
|
|
1787
|
+
for (const prop of ann.properties) {
|
|
1788
|
+
lines.push(`- \`${prop.type}\``);
|
|
1789
|
+
}
|
|
1790
|
+
lines.push("");
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
return lines.join("\n");
|
|
1794
|
+
}
|
|
1751
1795
|
function generateChangelogSection(codeInfo) {
|
|
1752
1796
|
if (!codeInfo?.changelog || codeInfo.changelog.length === 0)
|
|
1753
1797
|
return "";
|
|
@@ -2083,7 +2127,7 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2083
2127
|
logger.info({ fileKey, nodeId, canonicalSource, enrich }, "Starting design-code parity check");
|
|
2084
2128
|
const api = await getFigmaAPI();
|
|
2085
2129
|
// Fetch component node
|
|
2086
|
-
const nodesResponse = await api.getNodes(fileKey, [nodeId], { depth:
|
|
2130
|
+
const nodesResponse = await api.getNodes(fileKey, [nodeId], { depth: 4 });
|
|
2087
2131
|
const nodeData = nodesResponse?.nodes?.[nodeId];
|
|
2088
2132
|
if (!nodeData?.document) {
|
|
2089
2133
|
throw new Error(`Node ${nodeId} not found in file ${fileKey}`);
|
|
@@ -2234,7 +2278,7 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2234
2278
|
// -----------------------------------------------------------------------
|
|
2235
2279
|
// Tool: figma_generate_component_doc
|
|
2236
2280
|
// -----------------------------------------------------------------------
|
|
2237
|
-
server.tool("figma_generate_component_doc", "Generate AI-complete component documentation from a Figma component. Produces structured markdown with anatomy, per-variant color tokens, typography, content guidelines (parsed from Figma description), icon mapping, spacing tokens, and design-code parity analysis. Merges Figma design data with optional code-side info (CVA definitions, sub-component APIs, source files). Output works with any docs platform. For richest output, read the component source code first and pass codeInfo.", {
|
|
2281
|
+
server.tool("figma_generate_component_doc", "Generate AI-complete component documentation from a Figma component. Produces structured markdown with anatomy, per-variant color tokens, typography, content guidelines (parsed from Figma description), design annotations (animation timings, interaction specs, accessibility notes from Dev Mode), icon mapping, spacing tokens, and design-code parity analysis. Merges Figma design data with optional code-side info (CVA definitions, sub-component APIs, source files). Output works with any docs platform. For richest output, read the component source code first and pass codeInfo.", {
|
|
2238
2282
|
fileUrl: z
|
|
2239
2283
|
.string()
|
|
2240
2284
|
.url()
|
|
@@ -2252,6 +2296,7 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2252
2296
|
behavior: z.boolean().optional().default(false),
|
|
2253
2297
|
implementation: z.boolean().optional().default(true),
|
|
2254
2298
|
accessibility: z.boolean().optional().default(true),
|
|
2299
|
+
designAnnotations: z.boolean().optional().default(true),
|
|
2255
2300
|
relatedComponents: z.boolean().optional().default(false),
|
|
2256
2301
|
changelog: z.boolean().optional().default(true),
|
|
2257
2302
|
parity: z.boolean().optional().default(true),
|
|
@@ -2350,19 +2395,27 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2350
2395
|
// REST API getNodes() often returns empty description for COMPONENT_SET nodes,
|
|
2351
2396
|
// so fall back to Desktop Bridge plugin API which has the reliable description.
|
|
2352
2397
|
let description = node.descriptionMarkdown || node.description || componentMeta?.description || "";
|
|
2353
|
-
if (
|
|
2398
|
+
if (getDesktopConnector) {
|
|
2354
2399
|
try {
|
|
2355
2400
|
const connector = await getDesktopConnector();
|
|
2356
2401
|
const bridgeResult = await connector.getComponentFromPluginUI(nodeId);
|
|
2357
2402
|
if (bridgeResult.success && bridgeResult.component) {
|
|
2358
|
-
description
|
|
2359
|
-
if (description) {
|
|
2360
|
-
|
|
2403
|
+
// Fetch description from bridge if REST API returned empty
|
|
2404
|
+
if (!description) {
|
|
2405
|
+
description = bridgeResult.component.descriptionMarkdown || bridgeResult.component.description || "";
|
|
2406
|
+
if (description) {
|
|
2407
|
+
logger.info("Fetched description via Desktop Bridge (REST API returned empty)");
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
// Always fetch annotations from bridge (REST API never has them)
|
|
2411
|
+
if (bridgeResult.component.annotations && bridgeResult.component.annotations.length > 0) {
|
|
2412
|
+
node.annotations = bridgeResult.component.annotations;
|
|
2413
|
+
logger.info({ count: node.annotations.length }, "Fetched annotations via Desktop Bridge for documentation");
|
|
2361
2414
|
}
|
|
2362
2415
|
}
|
|
2363
2416
|
}
|
|
2364
2417
|
catch {
|
|
2365
|
-
logger.warn("Desktop Bridge
|
|
2418
|
+
logger.warn("Desktop Bridge fetch failed, proceeding without bridge-sourced data");
|
|
2366
2419
|
}
|
|
2367
2420
|
}
|
|
2368
2421
|
const fileUrl_ = `${url}?node-id=${nodeId.replace(":", "-")}`;
|
|
@@ -2441,6 +2494,11 @@ export function registerDesignCodeTools(server, getFigmaAPI, getCurrentUrl, vari
|
|
|
2441
2494
|
parts.push(generateAccessibilitySection(node, parsedDesc, codeInfo));
|
|
2442
2495
|
includedSections.push("accessibility");
|
|
2443
2496
|
}
|
|
2497
|
+
if (s.designAnnotations !== false) {
|
|
2498
|
+
const annotationsSection = generateDesignAnnotationsSection(node);
|
|
2499
|
+
parts.push(annotationsSection);
|
|
2500
|
+
includedSections.push("designAnnotations");
|
|
2501
|
+
}
|
|
2444
2502
|
if (s.parity && hasCodeInfo && hasFigmaData && codeInfo) {
|
|
2445
2503
|
const paritySection = generateParitySection(node, codeInfo);
|
|
2446
2504
|
if (paritySection) {
|
|
@@ -152,20 +152,116 @@ export class EnrichmentService {
|
|
|
152
152
|
}
|
|
153
153
|
enriched.styles_used = stylesUsed;
|
|
154
154
|
}
|
|
155
|
-
// Extract variables used
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
155
|
+
// Extract variables used and detect hardcoded values by walking the node tree
|
|
156
|
+
const varsUsed = [];
|
|
157
|
+
const hardcodedValues = [];
|
|
158
|
+
const variables = this.extractVariablesMap(data);
|
|
159
|
+
// Walk node tree to find boundVariables and hardcoded values
|
|
160
|
+
const walkForTokens = (node, path = "") => {
|
|
161
|
+
if (!node)
|
|
162
|
+
return;
|
|
163
|
+
const nodePath = path ? `${path} > ${node.name || node.id}` : (node.name || node.id);
|
|
164
|
+
const bv = node.boundVariables || {};
|
|
165
|
+
// Check fills
|
|
166
|
+
if (node.fills && Array.isArray(node.fills)) {
|
|
167
|
+
for (let i = 0; i < node.fills.length; i++) {
|
|
168
|
+
const fill = node.fills[i];
|
|
169
|
+
if (fill.type === "SOLID" && fill.visible !== false) {
|
|
170
|
+
const fillBv = bv.fills;
|
|
171
|
+
if (fillBv && (Array.isArray(fillBv) ? fillBv[i] : fillBv)) {
|
|
172
|
+
const varRef = Array.isArray(fillBv) ? fillBv[i] : fillBv;
|
|
173
|
+
if (varRef?.id) {
|
|
174
|
+
const varInfo = variables.get(varRef.id);
|
|
175
|
+
varsUsed.push({
|
|
176
|
+
variableId: varRef.id,
|
|
177
|
+
variableName: varInfo?.name || varRef.id,
|
|
178
|
+
property: "fill",
|
|
179
|
+
nodePath,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// Hardcoded fill
|
|
185
|
+
const c = fill.color;
|
|
186
|
+
if (c) {
|
|
187
|
+
hardcodedValues.push({
|
|
188
|
+
property: "fill",
|
|
189
|
+
value: `rgb(${Math.round(c.r * 255)}, ${Math.round(c.g * 255)}, ${Math.round(c.b * 255)})`,
|
|
190
|
+
nodePath,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Check strokes
|
|
198
|
+
if (node.strokes && Array.isArray(node.strokes)) {
|
|
199
|
+
for (let i = 0; i < node.strokes.length; i++) {
|
|
200
|
+
const stroke = node.strokes[i];
|
|
201
|
+
if (stroke.type === "SOLID" && stroke.visible !== false) {
|
|
202
|
+
const strokeBv = bv.strokes;
|
|
203
|
+
if (strokeBv && (Array.isArray(strokeBv) ? strokeBv[i] : strokeBv)) {
|
|
204
|
+
const varRef = Array.isArray(strokeBv) ? strokeBv[i] : strokeBv;
|
|
205
|
+
if (varRef?.id) {
|
|
206
|
+
const varInfo = variables.get(varRef.id);
|
|
207
|
+
varsUsed.push({
|
|
208
|
+
variableId: varRef.id,
|
|
209
|
+
variableName: varInfo?.name || varRef.id,
|
|
210
|
+
property: "stroke",
|
|
211
|
+
nodePath,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
const c = stroke.color;
|
|
217
|
+
if (c) {
|
|
218
|
+
hardcodedValues.push({
|
|
219
|
+
property: "stroke",
|
|
220
|
+
value: `rgb(${Math.round(c.r * 255)}, ${Math.round(c.g * 255)}, ${Math.round(c.b * 255)})`,
|
|
221
|
+
nodePath,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Check spacing/sizing tokens
|
|
229
|
+
const spacingProps = ["itemSpacing", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "cornerRadius"];
|
|
230
|
+
for (const prop of spacingProps) {
|
|
231
|
+
if (node[prop] !== undefined && node[prop] !== 0) {
|
|
232
|
+
if (bv[prop]?.id) {
|
|
233
|
+
const varInfo = variables.get(bv[prop].id);
|
|
234
|
+
varsUsed.push({
|
|
235
|
+
variableId: bv[prop].id,
|
|
236
|
+
variableName: varInfo?.name || bv[prop].id,
|
|
237
|
+
property: prop,
|
|
238
|
+
nodePath,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
hardcodedValues.push({
|
|
243
|
+
property: prop,
|
|
244
|
+
value: node[prop],
|
|
245
|
+
nodePath,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Recurse into children
|
|
251
|
+
if (node.children && Array.isArray(node.children)) {
|
|
252
|
+
for (const child of node.children) {
|
|
253
|
+
walkForTokens(child, nodePath);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
walkForTokens(component);
|
|
258
|
+
enriched.variables_used = varsUsed;
|
|
259
|
+
enriched.hardcoded_values = hardcodedValues;
|
|
164
260
|
// Calculate token coverage
|
|
165
|
-
const totalProps =
|
|
166
|
-
const tokenProps =
|
|
261
|
+
const totalProps = varsUsed.length + (enriched.styles_used?.length || 0) + hardcodedValues.length;
|
|
262
|
+
const tokenProps = varsUsed.length + (enriched.styles_used?.length || 0);
|
|
167
263
|
enriched.token_coverage =
|
|
168
|
-
totalProps > 0 ? Math.round((tokenProps / totalProps) * 100) :
|
|
264
|
+
totalProps > 0 ? Math.round((tokenProps / totalProps) * 100) : 100;
|
|
169
265
|
this.logger.info({ componentId: component.id, coverage: enriched.token_coverage }, "Component enrichment complete");
|
|
170
266
|
return enriched;
|
|
171
267
|
}
|