figma-rust-mcp 3.0.0 → 3.0.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 +12 -1
- package/package.json +1 -1
- package/plugin/code.js +198 -17
package/README.md
CHANGED
|
@@ -147,7 +147,7 @@ Add to your `~/.gemini/config/mcp_config.json` or project `.agents/mcp_config.js
|
|
|
147
147
|
|
|
148
148
|
## 🛠️ MCP Tools Reference
|
|
149
149
|
|
|
150
|
-
`figma-mcp` provides **
|
|
150
|
+
`figma-mcp` provides **15 first-class MCP tools**:
|
|
151
151
|
|
|
152
152
|
### 1. `figma_status`
|
|
153
153
|
Checks live bridge connection status, connected Figma tabs/files, in-memory index health, queue length, and latency statistics.
|
|
@@ -230,6 +230,17 @@ Scans your local codebase directories (`src/components`, `components/ui`) to dis
|
|
|
230
230
|
- **`projectDir`**: Base directory of your project (default: current directory `"."`).
|
|
231
231
|
- **`nodeId`**: Optional Figma node ID to check specific match against local components.
|
|
232
232
|
|
|
233
|
+
### 15. `figma_prepare_design`
|
|
234
|
+
**All-In-One Multimodal Grounding Pack for AI**: The ultimate single-call tool for code generation that eliminates missing UI elements and icon guesswork.
|
|
235
|
+
- Simultaneously extracts **100% of visible text elements** (greeting, badges, timestamps, labels).
|
|
236
|
+
- Automatically **exports all SVG vector icons** into your local project directory (`iconDir`) and provides exact TypeScript import statements.
|
|
237
|
+
- Captures high-res **visual canvas screenshots** saved locally for immediate multimodal LLM inspection.
|
|
238
|
+
- Discovers existing components in your codebase for direct reuse.
|
|
239
|
+
- Generates an actionable **Implementation Checklist** ensuring complete design fidelity in 1 single shot.
|
|
240
|
+
- **`nodeId`**: Target Figma screen/frame ID.
|
|
241
|
+
- **`iconDir`**: Target directory for SVG icons (e.g. `src/assets/icons` or `assets/images`).
|
|
242
|
+
- **`projectDir`**: Base directory for component discovery.
|
|
243
|
+
|
|
233
244
|
---
|
|
234
245
|
|
|
235
246
|
## 💻 Development & Testing
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "figma-rust-mcp",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Claude Code to Figma — Bidirectional MCP bridge. AI draws UI on Figma canvas, reads designs back. Works with Claude, Cursor, VS Code, Windsurf, Antigravity. No API key needed.",
|
|
5
5
|
"mcpName": "io.github.BuiHung1612/figma-mcp",
|
|
6
6
|
"type": "module",
|
package/plugin/code.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// figma-mcp — Figma Plugin main thread v1.2.1
|
|
2
2
|
// Handles both WRITE (draw UI) and READ (extract design) operations.
|
|
3
3
|
// AUTO-GENERATED by scripts/build-plugin.js — edit plugin-src/*.js instead
|
|
4
|
-
// Generated: 2026-08-
|
|
4
|
+
// Generated: 2026-08-29T05:29:59.260Z
|
|
5
5
|
|
|
6
6
|
// ── plugin-src/utils.js ──
|
|
7
7
|
// ─── UTILS ────────────────────────────────────────────────────────────────────
|
|
@@ -113,6 +113,109 @@ function solidStroke(hex, strokeOpacity) {
|
|
|
113
113
|
return [stroke];
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
// ── Variable & Token Deep Resolver ───────────────────────────────────────────
|
|
117
|
+
var variableCache = new Map();
|
|
118
|
+
|
|
119
|
+
async function getVariableSafeAsync(id) {
|
|
120
|
+
if (!id) return null;
|
|
121
|
+
if (variableCache.has(id)) return variableCache.get(id);
|
|
122
|
+
try {
|
|
123
|
+
if (figma.variables && typeof figma.variables.getVariableByIdAsync === "function") {
|
|
124
|
+
var v = await figma.variables.getVariableByIdAsync(id);
|
|
125
|
+
if (v) variableCache.set(id, v);
|
|
126
|
+
return v;
|
|
127
|
+
}
|
|
128
|
+
} catch(e) {}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Recursively resolves a variable (and its VARIABLE_ALIAS chains) to concrete primitive value & hex
|
|
133
|
+
async function resolveVariableValueAsync(variableOrId, contextNodeOrModeMap, depth, visited) {
|
|
134
|
+
if (!variableOrId) return null;
|
|
135
|
+
depth = depth || 0;
|
|
136
|
+
visited = visited || {};
|
|
137
|
+
if (depth > 6) return null;
|
|
138
|
+
|
|
139
|
+
var variable = null;
|
|
140
|
+
if (typeof variableOrId === "string") {
|
|
141
|
+
if (visited[variableOrId]) return null;
|
|
142
|
+
visited[variableOrId] = true;
|
|
143
|
+
variable = await getVariableSafeAsync(variableOrId);
|
|
144
|
+
} else {
|
|
145
|
+
variable = variableOrId;
|
|
146
|
+
if (variable && variable.id) {
|
|
147
|
+
if (visited[variable.id]) return null;
|
|
148
|
+
visited[variable.id] = true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!variable || !variable.valuesByMode) return null;
|
|
153
|
+
|
|
154
|
+
// Determine active mode
|
|
155
|
+
var modeId = null;
|
|
156
|
+
var colId = variable.variableCollectionId;
|
|
157
|
+
if (contextNodeOrModeMap) {
|
|
158
|
+
if (typeof contextNodeOrModeMap === "object" && contextNodeOrModeMap.resolvedVariableModes) {
|
|
159
|
+
modeId = contextNodeOrModeMap.resolvedVariableModes[colId];
|
|
160
|
+
} else if (typeof contextNodeOrModeMap === "object" && contextNodeOrModeMap[colId]) {
|
|
161
|
+
modeId = contextNodeOrModeMap[colId];
|
|
162
|
+
} else if (typeof contextNodeOrModeMap === "string") {
|
|
163
|
+
modeId = contextNodeOrModeMap;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
var availableModes = Object.keys(variable.valuesByMode);
|
|
167
|
+
if (!modeId || variable.valuesByMode[modeId] === undefined) {
|
|
168
|
+
modeId = availableModes.length > 0 ? availableModes[0] : null;
|
|
169
|
+
}
|
|
170
|
+
if (!modeId) return null;
|
|
171
|
+
|
|
172
|
+
var rawVal = variable.valuesByMode[modeId];
|
|
173
|
+
if (rawVal === undefined || rawVal === null) return null;
|
|
174
|
+
|
|
175
|
+
// If alias, follow recursively down to primitive token
|
|
176
|
+
if (typeof rawVal === "object" && rawVal.type === "VARIABLE_ALIAS" && rawVal.id) {
|
|
177
|
+
var targetResult = await resolveVariableValueAsync(rawVal.id, contextNodeOrModeMap, depth + 1, visited);
|
|
178
|
+
return {
|
|
179
|
+
type: "ALIAS",
|
|
180
|
+
name: variable.name,
|
|
181
|
+
variableId: variable.id,
|
|
182
|
+
resolvedType: variable.resolvedType,
|
|
183
|
+
targetId: rawVal.id,
|
|
184
|
+
targetName: targetResult ? (targetResult.primitiveName || targetResult.name) : null,
|
|
185
|
+
primitiveName: targetResult ? (targetResult.primitiveName || targetResult.name) : variable.name,
|
|
186
|
+
resolvedValue: targetResult ? targetResult.resolvedValue : null,
|
|
187
|
+
hex: targetResult ? targetResult.hex : null,
|
|
188
|
+
raw: rawVal,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// If RGBA color
|
|
193
|
+
if (typeof rawVal === "object" && "r" in rawVal && "g" in rawVal && "b" in rawVal) {
|
|
194
|
+
var hex = rgbToHex(rawVal);
|
|
195
|
+
var alpha = rawVal.a !== undefined ? Math.round(rawVal.a * 100) / 100 : 1;
|
|
196
|
+
return {
|
|
197
|
+
type: "COLOR",
|
|
198
|
+
name: variable.name,
|
|
199
|
+
variableId: variable.id,
|
|
200
|
+
resolvedType: "COLOR",
|
|
201
|
+
resolvedValue: hex,
|
|
202
|
+
hex: hex,
|
|
203
|
+
alpha: alpha,
|
|
204
|
+
primitiveName: variable.name,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Primitive value
|
|
209
|
+
return {
|
|
210
|
+
type: variable.resolvedType || typeof rawVal,
|
|
211
|
+
name: variable.name,
|
|
212
|
+
variableId: variable.id,
|
|
213
|
+
resolvedType: variable.resolvedType || typeof rawVal,
|
|
214
|
+
resolvedValue: rawVal,
|
|
215
|
+
primitiveName: variable.name,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
116
219
|
// Paint/effect helpers (buildFillArray, buildGradientPaint, buildEffect,
|
|
117
220
|
// applyEffects, applyCornerRadii) live in paint-and-effects.js.
|
|
118
221
|
|
|
@@ -1245,7 +1348,7 @@ const handlers = {};
|
|
|
1245
1348
|
|
|
1246
1349
|
handlers.status = async () => ({
|
|
1247
1350
|
connected: true,
|
|
1248
|
-
version: "3.0.
|
|
1351
|
+
version: "3.0.2",
|
|
1249
1352
|
fileName: figma.root.name,
|
|
1250
1353
|
currentPage: figma.currentPage.name,
|
|
1251
1354
|
pageCount: figma.root.children.length,
|
|
@@ -3198,6 +3301,26 @@ handlers.get_node_detail = async function(params) {
|
|
|
3198
3301
|
} else if (f.type === "IMAGE") {
|
|
3199
3302
|
fd.scaleMode = f.scaleMode || "FILL";
|
|
3200
3303
|
}
|
|
3304
|
+
|
|
3305
|
+
// Link bound variable token if present
|
|
3306
|
+
try {
|
|
3307
|
+
if (node.boundVariables && node.boundVariables.fills) {
|
|
3308
|
+
var fBindings = Array.isArray(node.boundVariables.fills) ? node.boundVariables.fills : [node.boundVariables.fills];
|
|
3309
|
+
if (fBindings[fi] && fBindings[fi].id) {
|
|
3310
|
+
var boundVar = await resolveVariableValueAsync(fBindings[fi].id, node, 0);
|
|
3311
|
+
if (boundVar) {
|
|
3312
|
+
fd.boundToken = {
|
|
3313
|
+
id: fBindings[fi].id,
|
|
3314
|
+
name: boundVar.name,
|
|
3315
|
+
primitive: boundVar.primitiveName,
|
|
3316
|
+
resolvedHex: boundVar.hex || boundVar.resolvedValue,
|
|
3317
|
+
};
|
|
3318
|
+
if (boundVar.hex) fd.color = boundVar.hex;
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
} catch(e3) {}
|
|
3323
|
+
|
|
3201
3324
|
detail.fills.push(fd);
|
|
3202
3325
|
}
|
|
3203
3326
|
}
|
|
@@ -3207,14 +3330,46 @@ handlers.get_node_detail = async function(params) {
|
|
|
3207
3330
|
try {
|
|
3208
3331
|
if (node.strokes && node.strokes.length) {
|
|
3209
3332
|
var dStrokes = node.strokes;
|
|
3333
|
+
var strokeBindings = (node.boundVariables && node.boundVariables.strokes) ? (Array.isArray(node.boundVariables.strokes) ? node.boundVariables.strokes : [node.boundVariables.strokes]) : [];
|
|
3334
|
+
|
|
3210
3335
|
if (dStrokes.length === 1 && dStrokes[0].type === "SOLID") {
|
|
3211
3336
|
detail.stroke = rgbToHex(dStrokes[0].color);
|
|
3337
|
+
try {
|
|
3338
|
+
if (strokeBindings[0] && strokeBindings[0].id) {
|
|
3339
|
+
var boundStrokeVar = await resolveVariableValueAsync(strokeBindings[0].id, node, 0);
|
|
3340
|
+
if (boundStrokeVar) {
|
|
3341
|
+
detail.strokeBoundToken = {
|
|
3342
|
+
id: strokeBindings[0].id,
|
|
3343
|
+
name: boundStrokeVar.name,
|
|
3344
|
+
primitive: boundStrokeVar.primitiveName,
|
|
3345
|
+
resolvedHex: boundStrokeVar.hex || boundStrokeVar.resolvedValue,
|
|
3346
|
+
};
|
|
3347
|
+
if (boundStrokeVar.hex) detail.stroke = boundStrokeVar.hex;
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
} catch(e4) {}
|
|
3212
3351
|
} else {
|
|
3213
|
-
detail.strokes =
|
|
3352
|
+
detail.strokes = [];
|
|
3353
|
+
for (var si = 0; si < dStrokes.length; si++) {
|
|
3354
|
+
var s = dStrokes[si];
|
|
3214
3355
|
var sd = { type: s.type };
|
|
3215
3356
|
if (s.type === "SOLID") sd.color = rgbToHex(s.color);
|
|
3216
|
-
|
|
3217
|
-
|
|
3357
|
+
try {
|
|
3358
|
+
if (strokeBindings[si] && strokeBindings[si].id) {
|
|
3359
|
+
var bsv = await resolveVariableValueAsync(strokeBindings[si].id, node, 0);
|
|
3360
|
+
if (bsv) {
|
|
3361
|
+
sd.boundToken = {
|
|
3362
|
+
id: strokeBindings[si].id,
|
|
3363
|
+
name: bsv.name,
|
|
3364
|
+
primitive: bsv.primitiveName,
|
|
3365
|
+
resolvedHex: bsv.hex || bsv.resolvedValue,
|
|
3366
|
+
};
|
|
3367
|
+
if (bsv.hex) sd.color = bsv.hex;
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
} catch(e5) {}
|
|
3371
|
+
detail.strokes.push(sd);
|
|
3372
|
+
}
|
|
3218
3373
|
}
|
|
3219
3374
|
detail.strokeWeight = node.strokeWeight;
|
|
3220
3375
|
detail.strokeAlign = node.strokeAlign;
|
|
@@ -3302,7 +3457,7 @@ handlers.get_node_detail = async function(params) {
|
|
|
3302
3457
|
try { detail.textAlign = node.textAlignHorizontal ? node.textAlignHorizontal.toLowerCase() : null; } catch(e) {}
|
|
3303
3458
|
}
|
|
3304
3459
|
|
|
3305
|
-
// P1: Resolve bound variables → name + resolvedType + value (
|
|
3460
|
+
// P1: Resolve bound variables → name + resolvedType + value (deep recursive resolution)
|
|
3306
3461
|
try {
|
|
3307
3462
|
if (node.boundVariables) {
|
|
3308
3463
|
var bv = {};
|
|
@@ -3318,16 +3473,18 @@ handlers.get_node_detail = async function(params) {
|
|
|
3318
3473
|
if (!alias || !alias.id) continue;
|
|
3319
3474
|
var entry = { id: alias.id };
|
|
3320
3475
|
try {
|
|
3321
|
-
var variable = await
|
|
3476
|
+
var variable = await getVariableSafeAsync(alias.id);
|
|
3322
3477
|
if (variable) {
|
|
3323
3478
|
entry.name = variable.name;
|
|
3324
3479
|
entry.resolvedType = variable.resolvedType;
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3480
|
+
var resolvedToken = await resolveVariableValueAsync(variable, node, 0);
|
|
3481
|
+
if (resolvedToken) {
|
|
3482
|
+
entry.value = resolvedToken.resolvedValue !== undefined ? resolvedToken.resolvedValue : (resolvedToken.hex || resolvedToken.value);
|
|
3483
|
+
if (resolvedToken.type === "ALIAS") {
|
|
3484
|
+
entry.aliasTarget = resolvedToken.targetName;
|
|
3485
|
+
entry.primitiveName = resolvedToken.primitiveName;
|
|
3486
|
+
entry.resolvedValue = resolvedToken.resolvedValue;
|
|
3487
|
+
}
|
|
3331
3488
|
}
|
|
3332
3489
|
}
|
|
3333
3490
|
} catch(e2) {}
|
|
@@ -3694,6 +3851,12 @@ handlers.get_design_context = async function(params) {
|
|
|
3694
3851
|
};
|
|
3695
3852
|
};
|
|
3696
3853
|
|
|
3854
|
+
// Aliases for developer convenience & MCP tool naming compatibility
|
|
3855
|
+
handlers.inspect_node = handlers.get_design_context;
|
|
3856
|
+
handlers.inspect = handlers.get_design_context;
|
|
3857
|
+
handlers.get_node_info = handlers.get_node_detail;
|
|
3858
|
+
handlers.node_detail = handlers.get_node_detail;
|
|
3859
|
+
|
|
3697
3860
|
// get_component_map — list every component instance in a frame with variant properties
|
|
3698
3861
|
// Use this to map Figma components to their code equivalents
|
|
3699
3862
|
handlers.get_component_map = async function(params) {
|
|
@@ -3944,22 +4107,37 @@ handlers.get_variables = async function() {
|
|
|
3944
4107
|
}
|
|
3945
4108
|
} catch(e) { /* fall back to per-id lookups */ }
|
|
3946
4109
|
|
|
4110
|
+
var resolvedTokensMap = {};
|
|
4111
|
+
|
|
3947
4112
|
for (var ci = 0; ci < localCollections.length; ci++) {
|
|
3948
4113
|
var col = localCollections[ci];
|
|
3949
4114
|
var variables = [];
|
|
3950
4115
|
for (var vi = 0; vi < col.variableIds.length; vi++) {
|
|
3951
4116
|
var vid = col.variableIds[vi];
|
|
3952
|
-
var v = bulkLoaded ? varsById[vid] : await
|
|
4117
|
+
var v = bulkLoaded ? varsById[vid] : await getVariableSafeAsync(vid);
|
|
3953
4118
|
if (!v) continue;
|
|
3954
4119
|
var values = {};
|
|
3955
4120
|
for (var modeId in v.valuesByMode) {
|
|
3956
4121
|
if (Object.prototype.hasOwnProperty.call(v.valuesByMode, modeId)) {
|
|
3957
4122
|
var val = v.valuesByMode[modeId];
|
|
3958
|
-
// Convert color values to hex
|
|
3959
4123
|
if (val && typeof val === "object" && "r" in val && "g" in val && "b" in val) {
|
|
3960
|
-
|
|
4124
|
+
var hexVal = rgbToHex(val);
|
|
4125
|
+
values[modeId] = hexVal;
|
|
4126
|
+
resolvedTokensMap[v.name] = hexVal;
|
|
4127
|
+
} else if (val && typeof val === "object" && val.type === "VARIABLE_ALIAS" && val.id) {
|
|
4128
|
+
var resolvedAlias = await resolveVariableValueAsync(val.id, modeId, 1);
|
|
4129
|
+
var finalHexOrVal = resolvedAlias ? (resolvedAlias.resolvedValue || resolvedAlias.hex || resolvedAlias.value) : null;
|
|
4130
|
+
values[modeId] = {
|
|
4131
|
+
type: "VARIABLE_ALIAS",
|
|
4132
|
+
aliasId: val.id,
|
|
4133
|
+
aliasName: resolvedAlias ? resolvedAlias.name : null,
|
|
4134
|
+
primitiveName: resolvedAlias ? resolvedAlias.primitiveName : null,
|
|
4135
|
+
resolvedValue: finalHexOrVal,
|
|
4136
|
+
};
|
|
4137
|
+
if (finalHexOrVal) resolvedTokensMap[v.name] = finalHexOrVal;
|
|
3961
4138
|
} else {
|
|
3962
4139
|
values[modeId] = val;
|
|
4140
|
+
if (val !== undefined && val !== null) resolvedTokensMap[v.name] = val;
|
|
3963
4141
|
}
|
|
3964
4142
|
}
|
|
3965
4143
|
}
|
|
@@ -3984,9 +4162,12 @@ handlers.get_variables = async function() {
|
|
|
3984
4162
|
} catch(e) {
|
|
3985
4163
|
return { error: "Variables API not available: " + e.message, collections: [] };
|
|
3986
4164
|
}
|
|
3987
|
-
return { collections: collections };
|
|
4165
|
+
return { collections: collections, resolvedTokens: resolvedTokensMap };
|
|
3988
4166
|
};
|
|
3989
4167
|
|
|
4168
|
+
handlers.get_tokens = handlers.get_variable_tokens;
|
|
4169
|
+
handlers.tokens = handlers.get_variable_tokens;
|
|
4170
|
+
|
|
3990
4171
|
// ─── EXPORT ASSETS (Batch Icon & Image Extractor) ────────────────────────────
|
|
3991
4172
|
handlers.export_assets = async function(params) {
|
|
3992
4173
|
var id = params ? (params.id || params.nodeId) : null;
|