This is page 3 of 3. Use http://codebase.md/sichang824/mcp-figma?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .env.example
├── .envrc
├── .gitignore
├── .mcp.pid
├── bun.lockb
├── docs
│ ├── 01-overview.md
│ ├── 02-implementation-steps.md
│ ├── 03-components-and-features.md
│ ├── 04-usage-guide.md
│ ├── 05-project-status.md
│ ├── image.png
│ ├── README.md
│ └── widget-tools-guide.md
├── Makefile
├── manifest.json
├── package.json
├── prompt.md
├── README.md
├── README.zh.md
├── src
│ ├── config
│ │ └── env.ts
│ ├── index.ts
│ ├── plugin
│ │ ├── code.js
│ │ ├── code.ts
│ │ ├── creators
│ │ │ ├── componentCreators.ts
│ │ │ ├── containerCreators.ts
│ │ │ ├── elementCreator.ts
│ │ │ ├── imageCreators.ts
│ │ │ ├── shapeCreators.ts
│ │ │ ├── sliceCreators.ts
│ │ │ ├── specialCreators.ts
│ │ │ └── textCreator.ts
│ │ ├── manifest.json
│ │ ├── README.md
│ │ ├── tsconfig.json
│ │ ├── ui.html
│ │ └── utils
│ │ ├── colorUtils.ts
│ │ └── nodeUtils.ts
│ ├── resources.ts
│ ├── services
│ │ ├── figma-api.ts
│ │ ├── websocket.ts
│ │ └── widget-api.ts
│ ├── tools
│ │ ├── canvas.ts
│ │ ├── comment.ts
│ │ ├── component.ts
│ │ ├── file.ts
│ │ ├── frame.ts
│ │ ├── image.ts
│ │ ├── index.ts
│ │ ├── node.ts
│ │ ├── page.ts
│ │ ├── search.ts
│ │ ├── utils
│ │ │ └── widget-utils.ts
│ │ ├── version.ts
│ │ ├── widget
│ │ │ ├── analyze-widget-structure.ts
│ │ │ ├── get-widget-sync-data.ts
│ │ │ ├── get-widget.ts
│ │ │ ├── get-widgets.ts
│ │ │ ├── index.ts
│ │ │ ├── README.md
│ │ │ ├── search-widgets.ts
│ │ │ └── widget-tools.ts
│ │ └── zod-schemas.ts
│ ├── utils
│ │ ├── figma-utils.ts
│ │ └── widget-utils.ts
│ ├── utils.ts
│ ├── widget
│ │ └── utils
│ │ └── widget-tools.ts
│ └── widget-tools.ts
├── tsconfig.json
└── tsconfig.widget.json
```
# Files
--------------------------------------------------------------------------------
/src/plugin/code.js:
--------------------------------------------------------------------------------
```javascript
1 | // src/plugin/utils/colorUtils.ts
2 | function hexToRgb(hex) {
3 | hex = hex.replace("#", "");
4 | const r = parseInt(hex.substring(0, 2), 16) / 255;
5 | const g = parseInt(hex.substring(2, 4), 16) / 255;
6 | const b = parseInt(hex.substring(4, 6), 16) / 255;
7 | return { r, g, b };
8 | }
9 | function createSolidPaint(color) {
10 | if (typeof color === "string") {
11 | return { type: "SOLID", color: hexToRgb(color) };
12 | }
13 | return { type: "SOLID", color };
14 | }
15 |
16 | // src/plugin/utils/nodeUtils.ts
17 | function applyCommonProperties(node, data) {
18 | if (data.x !== undefined)
19 | node.x = data.x;
20 | if (data.y !== undefined)
21 | node.y = data.y;
22 | if (data.name)
23 | node.name = data.name;
24 | if (data.opacity !== undefined && "opacity" in node) {
25 | node.opacity = data.opacity;
26 | }
27 | if (data.blendMode && "blendMode" in node) {
28 | node.blendMode = data.blendMode;
29 | }
30 | if (data.effects && "effects" in node) {
31 | node.effects = data.effects;
32 | }
33 | if (data.constraints && "constraints" in node) {
34 | node.constraints = data.constraints;
35 | }
36 | if (data.isMask !== undefined && "isMask" in node) {
37 | node.isMask = data.isMask;
38 | }
39 | if (data.visible !== undefined)
40 | node.visible = data.visible;
41 | if (data.locked !== undefined)
42 | node.locked = data.locked;
43 | }
44 | function selectAndFocusNodes(nodes) {
45 | const nodesToFocus = Array.isArray(nodes) ? nodes : [nodes];
46 | figma.currentPage.selection = nodesToFocus;
47 | figma.viewport.scrollAndZoomIntoView(nodesToFocus);
48 | }
49 | function buildResultObject(result) {
50 | let resultObject = {};
51 | if (!result)
52 | return resultObject;
53 | if (Array.isArray(result)) {
54 | resultObject.count = result.length;
55 | if (result.length > 0) {
56 | resultObject.items = result.map((node) => ({
57 | id: node.id,
58 | type: node.type,
59 | name: node.name
60 | }));
61 | }
62 | } else {
63 | const node = result;
64 | resultObject.id = node.id;
65 | resultObject.type = node.type;
66 | resultObject.name = node.name;
67 | }
68 | return resultObject;
69 | }
70 |
71 | // src/plugin/creators/shapeCreators.ts
72 | function createRectangleFromData(data) {
73 | const rect = figma.createRectangle();
74 | rect.resize(data.width || 100, data.height || 100);
75 | if (data.fills) {
76 | rect.fills = data.fills;
77 | } else if (data.fill) {
78 | if (typeof data.fill === "string") {
79 | rect.fills = [createSolidPaint(data.fill)];
80 | } else {
81 | rect.fills = [data.fill];
82 | }
83 | }
84 | if (data.strokes)
85 | rect.strokes = data.strokes;
86 | if (data.strokeWeight !== undefined)
87 | rect.strokeWeight = data.strokeWeight;
88 | if (data.strokeAlign)
89 | rect.strokeAlign = data.strokeAlign;
90 | if (data.strokeCap)
91 | rect.strokeCap = data.strokeCap;
92 | if (data.strokeJoin)
93 | rect.strokeJoin = data.strokeJoin;
94 | if (data.dashPattern)
95 | rect.dashPattern = data.dashPattern;
96 | if (data.cornerRadius !== undefined)
97 | rect.cornerRadius = data.cornerRadius;
98 | if (data.topLeftRadius !== undefined)
99 | rect.topLeftRadius = data.topLeftRadius;
100 | if (data.topRightRadius !== undefined)
101 | rect.topRightRadius = data.topRightRadius;
102 | if (data.bottomLeftRadius !== undefined)
103 | rect.bottomLeftRadius = data.bottomLeftRadius;
104 | if (data.bottomRightRadius !== undefined)
105 | rect.bottomRightRadius = data.bottomRightRadius;
106 | return rect;
107 | }
108 | function createEllipseFromData(data) {
109 | const ellipse = figma.createEllipse();
110 | ellipse.resize(data.width || 100, data.height || 100);
111 | if (data.fills) {
112 | ellipse.fills = data.fills;
113 | } else if (data.fill) {
114 | if (typeof data.fill === "string") {
115 | ellipse.fills = [createSolidPaint(data.fill)];
116 | } else {
117 | ellipse.fills = [data.fill];
118 | }
119 | }
120 | if (data.arcData) {
121 | ellipse.arcData = {
122 | startingAngle: data.arcData.startingAngle !== undefined ? data.arcData.startingAngle : 0,
123 | endingAngle: data.arcData.endingAngle !== undefined ? data.arcData.endingAngle : 360,
124 | innerRadius: data.arcData.innerRadius !== undefined ? data.arcData.innerRadius : 0
125 | };
126 | }
127 | if (data.strokes)
128 | ellipse.strokes = data.strokes;
129 | if (data.strokeWeight !== undefined)
130 | ellipse.strokeWeight = data.strokeWeight;
131 | if (data.strokeAlign)
132 | ellipse.strokeAlign = data.strokeAlign;
133 | if (data.strokeCap)
134 | ellipse.strokeCap = data.strokeCap;
135 | if (data.strokeJoin)
136 | ellipse.strokeJoin = data.strokeJoin;
137 | if (data.dashPattern)
138 | ellipse.dashPattern = data.dashPattern;
139 | return ellipse;
140 | }
141 | function createPolygonFromData(data) {
142 | const polygon = figma.createPolygon();
143 | polygon.resize(data.width || 100, data.height || 100);
144 | if (data.pointCount)
145 | polygon.pointCount = data.pointCount;
146 | if (data.fills) {
147 | polygon.fills = data.fills;
148 | } else if (data.fill) {
149 | if (typeof data.fill === "string") {
150 | polygon.fills = [createSolidPaint(data.fill)];
151 | } else {
152 | polygon.fills = [data.fill];
153 | }
154 | } else if (data.color) {
155 | polygon.fills = [createSolidPaint(data.color)];
156 | }
157 | if (data.strokes)
158 | polygon.strokes = data.strokes;
159 | if (data.strokeWeight !== undefined)
160 | polygon.strokeWeight = data.strokeWeight;
161 | if (data.strokeAlign)
162 | polygon.strokeAlign = data.strokeAlign;
163 | if (data.strokeCap)
164 | polygon.strokeCap = data.strokeCap;
165 | if (data.strokeJoin)
166 | polygon.strokeJoin = data.strokeJoin;
167 | if (data.dashPattern)
168 | polygon.dashPattern = data.dashPattern;
169 | return polygon;
170 | }
171 | function createStarFromData(data) {
172 | const star = figma.createStar();
173 | star.resize(data.width || 100, data.height || 100);
174 | if (data.pointCount)
175 | star.pointCount = data.pointCount;
176 | if (data.innerRadius)
177 | star.innerRadius = data.innerRadius;
178 | if (data.fills) {
179 | star.fills = data.fills;
180 | } else if (data.fill) {
181 | if (typeof data.fill === "string") {
182 | star.fills = [createSolidPaint(data.fill)];
183 | } else {
184 | star.fills = [data.fill];
185 | }
186 | }
187 | return star;
188 | }
189 | function createLineFromData(data) {
190 | const line = figma.createLine();
191 | line.resize(data.width || 100, 0);
192 | if (data.rotation !== undefined)
193 | line.rotation = data.rotation;
194 | if (data.strokeWeight)
195 | line.strokeWeight = data.strokeWeight;
196 | if (data.strokeAlign)
197 | line.strokeAlign = data.strokeAlign;
198 | if (data.strokeCap)
199 | line.strokeCap = data.strokeCap;
200 | if (data.strokeJoin)
201 | line.strokeJoin = data.strokeJoin;
202 | if (data.dashPattern)
203 | line.dashPattern = data.dashPattern;
204 | if (data.strokes) {
205 | line.strokes = data.strokes;
206 | } else if (data.stroke) {
207 | if (typeof data.stroke === "string") {
208 | line.strokes = [createSolidPaint(data.stroke)];
209 | } else {
210 | line.strokes = [data.stroke];
211 | }
212 | } else if (data.color) {
213 | line.strokes = [createSolidPaint(data.color)];
214 | }
215 | return line;
216 | }
217 | function createVectorFromData(data) {
218 | const vector = figma.createVector();
219 | try {
220 | vector.resize(data.width || 100, data.height || 100);
221 | if (data.vectorNetwork) {
222 | vector.vectorNetwork = data.vectorNetwork;
223 | }
224 | if (data.vectorPaths) {
225 | vector.vectorPaths = data.vectorPaths;
226 | }
227 | if (data.handleMirroring) {
228 | vector.handleMirroring = data.handleMirroring;
229 | }
230 | if (data.fills) {
231 | vector.fills = data.fills;
232 | } else if (data.fill) {
233 | if (typeof data.fill === "string") {
234 | vector.fills = [createSolidPaint(data.fill)];
235 | } else {
236 | vector.fills = [data.fill];
237 | }
238 | } else if (data.color) {
239 | vector.fills = [createSolidPaint(data.color)];
240 | }
241 | if (data.strokes)
242 | vector.strokes = data.strokes;
243 | if (data.strokeWeight !== undefined)
244 | vector.strokeWeight = data.strokeWeight;
245 | if (data.strokeAlign)
246 | vector.strokeAlign = data.strokeAlign;
247 | if (data.strokeCap)
248 | vector.strokeCap = data.strokeCap;
249 | if (data.strokeJoin)
250 | vector.strokeJoin = data.strokeJoin;
251 | if (data.dashPattern)
252 | vector.dashPattern = data.dashPattern;
253 | if (data.strokeMiterLimit)
254 | vector.strokeMiterLimit = data.strokeMiterLimit;
255 | if (data.cornerRadius !== undefined)
256 | vector.cornerRadius = data.cornerRadius;
257 | if (data.cornerSmoothing !== undefined)
258 | vector.cornerSmoothing = data.cornerSmoothing;
259 | if (data.opacity !== undefined)
260 | vector.opacity = data.opacity;
261 | if (data.blendMode)
262 | vector.blendMode = data.blendMode;
263 | if (data.isMask !== undefined)
264 | vector.isMask = data.isMask;
265 | if (data.effects)
266 | vector.effects = data.effects;
267 | if (data.constraints)
268 | vector.constraints = data.constraints;
269 | if (data.layoutAlign)
270 | vector.layoutAlign = data.layoutAlign;
271 | if (data.layoutGrow !== undefined)
272 | vector.layoutGrow = data.layoutGrow;
273 | if (data.layoutPositioning)
274 | vector.layoutPositioning = data.layoutPositioning;
275 | if (data.rotation !== undefined)
276 | vector.rotation = data.rotation;
277 | if (data.layoutSizingHorizontal)
278 | vector.layoutSizingHorizontal = data.layoutSizingHorizontal;
279 | if (data.layoutSizingVertical)
280 | vector.layoutSizingVertical = data.layoutSizingVertical;
281 | console.log("Vector created successfully:", vector);
282 | } catch (error) {
283 | console.error("Error creating vector:", error);
284 | }
285 | return vector;
286 | }
287 |
288 | // src/plugin/creators/containerCreators.ts
289 | function createFrameFromData(data) {
290 | const frame = figma.createFrame();
291 | frame.resize(data.width || 100, data.height || 100);
292 | if (data.fills) {
293 | frame.fills = data.fills;
294 | } else if (data.fill) {
295 | if (typeof data.fill === "string") {
296 | frame.fills = [createSolidPaint(data.fill)];
297 | } else {
298 | frame.fills = [data.fill];
299 | }
300 | }
301 | if (data.layoutMode)
302 | frame.layoutMode = data.layoutMode;
303 | if (data.primaryAxisSizingMode)
304 | frame.primaryAxisSizingMode = data.primaryAxisSizingMode;
305 | if (data.counterAxisSizingMode)
306 | frame.counterAxisSizingMode = data.counterAxisSizingMode;
307 | if (data.primaryAxisAlignItems)
308 | frame.primaryAxisAlignItems = data.primaryAxisAlignItems;
309 | if (data.counterAxisAlignItems)
310 | frame.counterAxisAlignItems = data.counterAxisAlignItems;
311 | if (data.paddingLeft !== undefined)
312 | frame.paddingLeft = data.paddingLeft;
313 | if (data.paddingRight !== undefined)
314 | frame.paddingRight = data.paddingRight;
315 | if (data.paddingTop !== undefined)
316 | frame.paddingTop = data.paddingTop;
317 | if (data.paddingBottom !== undefined)
318 | frame.paddingBottom = data.paddingBottom;
319 | if (data.itemSpacing !== undefined)
320 | frame.itemSpacing = data.itemSpacing;
321 | if (data.cornerRadius !== undefined)
322 | frame.cornerRadius = data.cornerRadius;
323 | if (data.topLeftRadius !== undefined)
324 | frame.topLeftRadius = data.topLeftRadius;
325 | if (data.topRightRadius !== undefined)
326 | frame.topRightRadius = data.topRightRadius;
327 | if (data.bottomLeftRadius !== undefined)
328 | frame.bottomLeftRadius = data.bottomLeftRadius;
329 | if (data.bottomRightRadius !== undefined)
330 | frame.bottomRightRadius = data.bottomRightRadius;
331 | return frame;
332 | }
333 | function createComponentFromData(data) {
334 | const component = figma.createComponent();
335 | component.resize(data.width || 100, data.height || 100);
336 | if (data.fills) {
337 | component.fills = data.fills;
338 | } else if (data.fill) {
339 | if (typeof data.fill === "string") {
340 | component.fills = [createSolidPaint(data.fill)];
341 | } else {
342 | component.fills = [data.fill];
343 | }
344 | }
345 | if (data.layoutMode)
346 | component.layoutMode = data.layoutMode;
347 | if (data.primaryAxisSizingMode)
348 | component.primaryAxisSizingMode = data.primaryAxisSizingMode;
349 | if (data.counterAxisSizingMode)
350 | component.counterAxisSizingMode = data.counterAxisSizingMode;
351 | if (data.primaryAxisAlignItems)
352 | component.primaryAxisAlignItems = data.primaryAxisAlignItems;
353 | if (data.counterAxisAlignItems)
354 | component.counterAxisAlignItems = data.counterAxisAlignItems;
355 | if (data.paddingLeft !== undefined)
356 | component.paddingLeft = data.paddingLeft;
357 | if (data.paddingRight !== undefined)
358 | component.paddingRight = data.paddingRight;
359 | if (data.paddingTop !== undefined)
360 | component.paddingTop = data.paddingTop;
361 | if (data.paddingBottom !== undefined)
362 | component.paddingBottom = data.paddingBottom;
363 | if (data.itemSpacing !== undefined)
364 | component.itemSpacing = data.itemSpacing;
365 | if (data.description)
366 | component.description = data.description;
367 | return component;
368 | }
369 | function createGroupFromData(data, children) {
370 | const group = figma.group(children, figma.currentPage);
371 | applyCommonProperties(group, data);
372 | return group;
373 | }
374 | function createInstanceFromData(data) {
375 | if (!data.componentId) {
376 | console.error("Cannot create instance: componentId is required");
377 | return null;
378 | }
379 | const component = figma.getNodeById(data.componentId);
380 | if (!component || component.type !== "COMPONENT") {
381 | console.error(`Cannot create instance: component with id ${data.componentId} not found`);
382 | return null;
383 | }
384 | const instance = component.createInstance();
385 | applyCommonProperties(instance, data);
386 | if (data.componentProperties) {
387 | for (const [key, value] of Object.entries(data.componentProperties)) {
388 | if (key in instance.componentProperties) {
389 | const prop = instance.componentProperties[key];
390 | if (prop.type === "BOOLEAN") {
391 | instance.setProperties({ [key]: !!value });
392 | } else if (prop.type === "TEXT") {
393 | instance.setProperties({ [key]: String(value) });
394 | } else if (prop.type === "INSTANCE_SWAP") {
395 | instance.setProperties({ [key]: String(value) });
396 | } else if (prop.type === "VARIANT") {
397 | instance.setProperties({ [key]: String(value) });
398 | }
399 | }
400 | }
401 | }
402 | return instance;
403 | }
404 | function createSectionFromData(data) {
405 | const section = figma.createSection();
406 | if (data.name)
407 | section.name = data.name;
408 | if (data.sectionContentsHidden !== undefined)
409 | section.sectionContentsHidden = data.sectionContentsHidden;
410 | if (data.x !== undefined)
411 | section.x = data.x;
412 | if (data.y !== undefined)
413 | section.y = data.y;
414 | return section;
415 | }
416 |
417 | // src/plugin/creators/textCreator.ts
418 | async function createTextFromData(data) {
419 | const text = figma.createText();
420 | const fontFamily = data.fontFamily || data.fontName.family || "Inter";
421 | const fontStyle = data.fontStyle || data.fontName.style || "Regular";
422 | try {
423 | await figma.loadFontAsync({ family: fontFamily, style: fontStyle });
424 | } catch (error) {
425 | console.warn(`Failed to load font ${fontFamily} ${fontStyle}. Falling back to Inter Regular.`);
426 | await figma.loadFontAsync({ family: "Inter", style: "Regular" });
427 | }
428 | text.characters = data.text || data.characters || "Text";
429 | if (data.x !== undefined)
430 | text.x = data.x;
431 | if (data.y !== undefined)
432 | text.y = data.y;
433 | if (data.fontSize)
434 | text.fontSize = data.fontSize;
435 | if (data.width)
436 | text.resize(data.width, text.height);
437 | if (data.fontName)
438 | text.fontName = data.fontName;
439 | if (data.textAlignHorizontal)
440 | text.textAlignHorizontal = data.textAlignHorizontal;
441 | if (data.textAlignVertical)
442 | text.textAlignVertical = data.textAlignVertical;
443 | if (data.textAutoResize)
444 | text.textAutoResize = data.textAutoResize;
445 | if (data.textTruncation)
446 | text.textTruncation = data.textTruncation;
447 | if (data.maxLines !== undefined)
448 | text.maxLines = data.maxLines;
449 | if (data.paragraphIndent)
450 | text.paragraphIndent = data.paragraphIndent;
451 | if (data.paragraphSpacing)
452 | text.paragraphSpacing = data.paragraphSpacing;
453 | if (data.listSpacing)
454 | text.listSpacing = data.listSpacing;
455 | if (data.hangingPunctuation !== undefined)
456 | text.hangingPunctuation = data.hangingPunctuation;
457 | if (data.hangingList !== undefined)
458 | text.hangingList = data.hangingList;
459 | if (data.autoRename !== undefined)
460 | text.autoRename = data.autoRename;
461 | if (data.letterSpacing)
462 | text.letterSpacing = data.letterSpacing;
463 | if (data.lineHeight)
464 | text.lineHeight = data.lineHeight;
465 | if (data.leadingTrim)
466 | text.leadingTrim = data.leadingTrim;
467 | if (data.textCase)
468 | text.textCase = data.textCase;
469 | if (data.textDecoration)
470 | text.textDecoration = data.textDecoration;
471 | if (data.textStyleId)
472 | text.textStyleId = data.textStyleId;
473 | if (data.textDecorationStyle)
474 | text.textDecorationStyle = data.textDecorationStyle;
475 | if (data.textDecorationOffset)
476 | text.textDecorationOffset = data.textDecorationOffset;
477 | if (data.textDecorationThickness)
478 | text.textDecorationThickness = data.textDecorationThickness;
479 | if (data.textDecorationColor)
480 | text.textDecorationColor = data.textDecorationColor;
481 | if (data.textDecorationSkipInk !== undefined)
482 | text.textDecorationSkipInk = data.textDecorationSkipInk;
483 | if (data.fills) {
484 | text.fills = data.fills;
485 | } else if (data.fill) {
486 | if (typeof data.fill === "string") {
487 | text.fills = [createSolidPaint(data.fill)];
488 | } else {
489 | text.fills = [data.fill];
490 | }
491 | }
492 | if (data.hyperlink) {
493 | text.hyperlink = data.hyperlink;
494 | }
495 | if (data.layoutAlign)
496 | text.layoutAlign = data.layoutAlign;
497 | if (data.layoutGrow !== undefined)
498 | text.layoutGrow = data.layoutGrow;
499 | if (data.layoutSizingHorizontal)
500 | text.layoutSizingHorizontal = data.layoutSizingHorizontal;
501 | if (data.layoutSizingVertical)
502 | text.layoutSizingVertical = data.layoutSizingVertical;
503 | if (data.rangeStyles && Array.isArray(data.rangeStyles)) {
504 | applyTextRangeStyles(text, data.rangeStyles);
505 | }
506 | if (data.name)
507 | text.name = data.name;
508 | if (data.visible !== undefined)
509 | text.visible = data.visible;
510 | if (data.locked !== undefined)
511 | text.locked = data.locked;
512 | if (data.opacity !== undefined)
513 | text.opacity = data.opacity;
514 | if (data.blendMode)
515 | text.blendMode = data.blendMode;
516 | if (data.effects)
517 | text.effects = data.effects;
518 | if (data.effectStyleId)
519 | text.effectStyleId = data.effectStyleId;
520 | if (data.exportSettings)
521 | text.exportSettings = data.exportSettings;
522 | if (data.constraints)
523 | text.constraints = data.constraints;
524 | return text;
525 | }
526 | function applyTextRangeStyles(textNode, ranges) {
527 | for (const range of ranges) {
528 | for (const [property, value] of Object.entries(range.style)) {
529 | if (property === "fills") {
530 | textNode.setRangeFills(range.start, range.end, value);
531 | } else if (property === "fillStyleId") {
532 | textNode.setRangeFillStyleId(range.start, range.end, value);
533 | } else if (property === "fontName") {
534 | textNode.setRangeFontName(range.start, range.end, value);
535 | } else if (property === "fontSize") {
536 | textNode.setRangeFontSize(range.start, range.end, value);
537 | } else if (property === "textCase") {
538 | textNode.setRangeTextCase(range.start, range.end, value);
539 | } else if (property === "textDecoration") {
540 | textNode.setRangeTextDecoration(range.start, range.end, value);
541 | } else if (property === "textDecorationStyle") {
542 | textNode.setRangeTextDecorationStyle(range.start, range.end, value);
543 | } else if (property === "textDecorationOffset") {
544 | textNode.setRangeTextDecorationOffset(range.start, range.end, value);
545 | } else if (property === "textDecorationThickness") {
546 | textNode.setRangeTextDecorationThickness(range.start, range.end, value);
547 | } else if (property === "textDecorationColor") {
548 | textNode.setRangeTextDecorationColor(range.start, range.end, value);
549 | } else if (property === "textDecorationSkipInk") {
550 | textNode.setRangeTextDecorationSkipInk(range.start, range.end, value);
551 | } else if (property === "letterSpacing") {
552 | textNode.setRangeLetterSpacing(range.start, range.end, value);
553 | } else if (property === "lineHeight") {
554 | textNode.setRangeLineHeight(range.start, range.end, value);
555 | } else if (property === "hyperlink") {
556 | textNode.setRangeHyperlink(range.start, range.end, value);
557 | } else if (property === "textStyleId") {
558 | textNode.setRangeTextStyleId(range.start, range.end, value);
559 | } else if (property === "indentation") {
560 | textNode.setRangeIndentation(range.start, range.end, value);
561 | } else if (property === "paragraphIndent") {
562 | textNode.setRangeParagraphIndent(range.start, range.end, value);
563 | } else if (property === "paragraphSpacing") {
564 | textNode.setRangeParagraphSpacing(range.start, range.end, value);
565 | } else if (property === "listOptions") {
566 | textNode.setRangeListOptions(range.start, range.end, value);
567 | } else if (property === "listSpacing") {
568 | textNode.setRangeListSpacing(range.start, range.end, value);
569 | }
570 | }
571 | }
572 | }
573 |
574 | // src/plugin/creators/specialCreators.ts
575 | function createBooleanOperationFromData(data) {
576 | if (!data.children || !Array.isArray(data.children) || data.children.length < 2) {
577 | console.error("Boolean operation requires at least 2 child nodes");
578 | return null;
579 | }
580 | let childNodes = [];
581 | try {
582 | for (const childData of data.children) {
583 | const node = figma.createRectangle();
584 | childNodes.push(node);
585 | }
586 | const booleanOperation = figma.createBooleanOperation();
587 | if (data.booleanOperation) {
588 | booleanOperation.booleanOperation = data.booleanOperation;
589 | }
590 | applyCommonProperties(booleanOperation, data);
591 | return booleanOperation;
592 | } catch (error) {
593 | console.error("Failed to create boolean operation:", error);
594 | childNodes.forEach((node) => node.remove());
595 | return null;
596 | }
597 | }
598 | function createConnectorFromData(data) {
599 | const connector = figma.createConnector();
600 | if (data.connectorStart)
601 | connector.connectorStart = data.connectorStart;
602 | if (data.connectorEnd)
603 | connector.connectorEnd = data.connectorEnd;
604 | if (data.connectorStartStrokeCap)
605 | connector.connectorStartStrokeCap = data.connectorStartStrokeCap;
606 | if (data.connectorEndStrokeCap)
607 | connector.connectorEndStrokeCap = data.connectorEndStrokeCap;
608 | if (data.connectorLineType)
609 | connector.connectorLineType = data.connectorLineType;
610 | if (data.strokes)
611 | connector.strokes = data.strokes;
612 | if (data.strokeWeight)
613 | connector.strokeWeight = data.strokeWeight;
614 | applyCommonProperties(connector, data);
615 | return connector;
616 | }
617 | function createShapeWithTextFromData(data) {
618 | if (!("createShapeWithText" in figma)) {
619 | console.error("ShapeWithText creation is not supported in this Figma version");
620 | return null;
621 | }
622 | try {
623 | const shapeWithText = figma.createShapeWithText();
624 | if (data.shapeType)
625 | shapeWithText.shapeType = data.shapeType;
626 | if (data.text || data.characters) {
627 | shapeWithText.text.characters = data.text || data.characters;
628 | }
629 | try {
630 | if (data.fontSize)
631 | shapeWithText.text.fontSize = data.fontSize;
632 | if (data.fontName)
633 | shapeWithText.text.fontName = data.fontName;
634 | if (data.textAlignHorizontal && "textAlignHorizontal" in shapeWithText.text) {
635 | shapeWithText.text.textAlignHorizontal = data.textAlignHorizontal;
636 | }
637 | if (data.textAlignVertical && "textAlignVertical" in shapeWithText.text) {
638 | shapeWithText.text.textAlignVertical = data.textAlignVertical;
639 | }
640 | } catch (e) {
641 | console.warn("Some text properties could not be set on ShapeWithText:", e);
642 | }
643 | if (data.fills)
644 | shapeWithText.fills = data.fills;
645 | if (data.strokes)
646 | shapeWithText.strokes = data.strokes;
647 | applyCommonProperties(shapeWithText, data);
648 | return shapeWithText;
649 | } catch (error) {
650 | console.error("Failed to create shape with text:", error);
651 | return null;
652 | }
653 | }
654 | function createCodeBlockFromData(data) {
655 | const codeBlock = figma.createCodeBlock();
656 | if (data.code)
657 | codeBlock.code = data.code;
658 | if (data.codeLanguage)
659 | codeBlock.codeLanguage = data.codeLanguage;
660 | applyCommonProperties(codeBlock, data);
661 | return codeBlock;
662 | }
663 | function createTableFromData(data) {
664 | const table = figma.createTable(data.numRows || 2, data.numColumns || 2);
665 | if (data.fills && "fills" in table) {
666 | table.fills = data.fills;
667 | }
668 | if (data.cells && Array.isArray(data.cells)) {
669 | for (const cellData of data.cells) {
670 | if (cellData.rowIndex !== undefined && cellData.columnIndex !== undefined) {
671 | try {
672 | let cell;
673 | if ("cellAt" in table) {
674 | cell = table.cellAt(cellData.rowIndex, cellData.columnIndex);
675 | } else if ("getCellAt" in table) {
676 | cell = table.getCellAt(cellData.rowIndex, cellData.columnIndex);
677 | }
678 | if (cell) {
679 | if (cellData.text && cell.text)
680 | cell.text.characters = cellData.text;
681 | if (cellData.fills && "fills" in cell)
682 | cell.fills = cellData.fills;
683 | if (cellData.rowSpan && "rowSpan" in cell)
684 | cell.rowSpan = cellData.rowSpan;
685 | if (cellData.columnSpan && "columnSpan" in cell)
686 | cell.columnSpan = cellData.columnSpan;
687 | }
688 | } catch (e) {
689 | console.warn(`Could not set properties for cell at ${cellData.rowIndex}, ${cellData.columnIndex}:`, e);
690 | }
691 | }
692 | }
693 | }
694 | applyCommonProperties(table, data);
695 | return table;
696 | }
697 | function createWidgetFromData(data) {
698 | if (!("createWidget" in figma)) {
699 | console.error("Widget creation is not supported in this Figma version");
700 | return null;
701 | }
702 | if (!data.widgetId) {
703 | console.error("Widget creation requires a widgetId");
704 | return null;
705 | }
706 | try {
707 | const widget = figma.createWidget(data.widgetId);
708 | if (data.widgetData)
709 | widget.widgetData = JSON.stringify(data.widgetData);
710 | if (data.width && data.height && "resize" in widget)
711 | widget.resize(data.width, data.height);
712 | applyCommonProperties(widget, data);
713 | return widget;
714 | } catch (error) {
715 | console.error("Failed to create widget:", error);
716 | return null;
717 | }
718 | }
719 | function createMediaFromData(data) {
720 | if (!("createMedia" in figma)) {
721 | console.error("Media creation is not supported in this Figma version");
722 | return null;
723 | }
724 | if (!data.hash) {
725 | console.error("Media creation requires a valid media hash");
726 | return null;
727 | }
728 | try {
729 | const media = figma.createMedia(data.hash);
730 | applyCommonProperties(media, data);
731 | return media;
732 | } catch (error) {
733 | console.error("Failed to create media:", error);
734 | return null;
735 | }
736 | }
737 |
738 | // src/plugin/creators/imageCreators.ts
739 | function createImageFromData(data) {
740 | try {
741 | if (!data.hash) {
742 | console.error("Image creation requires an image hash");
743 | return null;
744 | }
745 | const image = figma.createImage(data.hash);
746 | const rect = figma.createRectangle();
747 | if (data.width && data.height) {
748 | rect.resize(data.width, data.height);
749 | }
750 | rect.fills = [{
751 | type: "IMAGE",
752 | scaleMode: data.scaleMode || "FILL",
753 | imageHash: image.hash
754 | }];
755 | applyCommonProperties(rect, data);
756 | return rect;
757 | } catch (error) {
758 | console.error("Failed to create image:", error);
759 | return null;
760 | }
761 | }
762 | async function createImageFromBytesAsync(data) {
763 | try {
764 | if (!data.bytes && !data.file) {
765 | console.error("Image creation requires image bytes or file");
766 | return null;
767 | }
768 | let image;
769 | if (data.bytes) {
770 | image = await figma.createImageAsync(data.bytes);
771 | } else if (data.file) {
772 | image = await figma.createImageAsync(data.file);
773 | } else {
774 | return null;
775 | }
776 | const rect = figma.createRectangle();
777 | if (data.width && data.height) {
778 | rect.resize(data.width, data.height);
779 | }
780 | rect.fills = [{
781 | type: "IMAGE",
782 | scaleMode: data.scaleMode || "FILL",
783 | imageHash: image.hash
784 | }];
785 | applyCommonProperties(rect, data);
786 | return rect;
787 | } catch (error) {
788 | console.error("Failed to create image asynchronously:", error);
789 | return null;
790 | }
791 | }
792 | function createGifFromData(data) {
793 | console.error("createGif API is not directly available or implemented");
794 | return null;
795 | }
796 | async function createVideoFromDataAsync(data) {
797 | if (!("createVideoAsync" in figma)) {
798 | console.error("Video creation is not supported in this Figma version");
799 | return null;
800 | }
801 | try {
802 | if (!data.bytes) {
803 | console.error("Video creation requires video bytes");
804 | return null;
805 | }
806 | const video = await figma.createVideoAsync(data.bytes);
807 | applyCommonProperties(video, data);
808 | return video;
809 | } catch (error) {
810 | console.error("Failed to create video:", error);
811 | return null;
812 | }
813 | }
814 | async function createLinkPreviewFromDataAsync(data) {
815 | if (!("createLinkPreviewAsync" in figma)) {
816 | console.error("Link preview creation is not supported in this Figma version");
817 | return null;
818 | }
819 | try {
820 | if (!data.url) {
821 | console.error("Link preview creation requires a URL");
822 | return null;
823 | }
824 | const linkPreview = await figma.createLinkPreviewAsync(data.url);
825 | applyCommonProperties(linkPreview, data);
826 | return linkPreview;
827 | } catch (error) {
828 | console.error("Failed to create link preview:", error);
829 | return null;
830 | }
831 | }
832 |
833 | // src/plugin/creators/sliceCreators.ts
834 | function createSliceFromData(data) {
835 | const slice = figma.createSlice();
836 | if (data.width && data.height) {
837 | slice.resize(data.width, data.height);
838 | }
839 | if (data.x !== undefined)
840 | slice.x = data.x;
841 | if (data.y !== undefined)
842 | slice.y = data.y;
843 | if (data.exportSettings && Array.isArray(data.exportSettings)) {
844 | slice.exportSettings = data.exportSettings;
845 | }
846 | if (data.name)
847 | slice.name = data.name;
848 | if (data.visible !== undefined)
849 | slice.visible = data.visible;
850 | return slice;
851 | }
852 | function createPageFromData(data) {
853 | const page = figma.createPage();
854 | if (data.name)
855 | page.name = data.name;
856 | if (data.backgrounds)
857 | page.backgrounds = data.backgrounds;
858 | return page;
859 | }
860 | function createPageDividerFromData(data) {
861 | if (!("createPageDivider" in figma)) {
862 | console.error("createPageDivider is not supported in this Figma version");
863 | return null;
864 | }
865 | try {
866 | const pageDivider = figma.createPageDivider();
867 | if (data.name)
868 | pageDivider.name = data.name;
869 | return pageDivider;
870 | } catch (error) {
871 | console.error("Failed to create page divider:", error);
872 | return null;
873 | }
874 | }
875 | function createSlideFromData(data) {
876 | if (!("createSlide" in figma)) {
877 | console.error("createSlide is not supported in this Figma version");
878 | return null;
879 | }
880 | try {
881 | const slide = figma.createSlide();
882 | if (data.name)
883 | slide.name = data.name;
884 | applyCommonProperties(slide, data);
885 | return slide;
886 | } catch (error) {
887 | console.error("Failed to create slide:", error);
888 | return null;
889 | }
890 | }
891 | function createSlideRowFromData(data) {
892 | if (!("createSlideRow" in figma)) {
893 | console.error("createSlideRow is not supported in this Figma version");
894 | return null;
895 | }
896 | try {
897 | const slideRow = figma.createSlideRow();
898 | if (data.name)
899 | slideRow.name = data.name;
900 | applyCommonProperties(slideRow, data);
901 | return slideRow;
902 | } catch (error) {
903 | console.error("Failed to create slide row:", error);
904 | return null;
905 | }
906 | }
907 |
908 | // src/plugin/creators/componentCreators.ts
909 | function createComponentFromNodeData(data) {
910 | if (!data.sourceNode) {
911 | console.error("createComponentFromNode requires a sourceNode");
912 | return null;
913 | }
914 | try {
915 | let sourceNode;
916 | if (typeof data.sourceNode === "string") {
917 | sourceNode = figma.getNodeById(data.sourceNode);
918 | if (!sourceNode || !("type" in sourceNode)) {
919 | console.error(`Node with ID ${data.sourceNode} not found or is not a valid node`);
920 | return null;
921 | }
922 | } else {
923 | sourceNode = data.sourceNode;
924 | }
925 | const component = figma.createComponentFromNode(sourceNode);
926 | if (data.description)
927 | component.description = data.description;
928 | applyCommonProperties(component, data);
929 | return component;
930 | } catch (error) {
931 | console.error("Failed to create component from node:", error);
932 | return null;
933 | }
934 | }
935 | function createComponentSetFromData(data) {
936 | try {
937 | if (!data.components || !Array.isArray(data.components) || data.components.length === 0) {
938 | console.error("Component set creation requires component nodes");
939 | return null;
940 | }
941 | const componentNodes = [];
942 | for (const component of data.components) {
943 | let node;
944 | if (typeof component === "string") {
945 | node = figma.getNodeById(component);
946 | } else {
947 | node = component;
948 | }
949 | if (node && node.type === "COMPONENT") {
950 | componentNodes.push(node);
951 | }
952 | }
953 | if (componentNodes.length === 0) {
954 | console.error("No valid component nodes provided");
955 | return null;
956 | }
957 | const componentSet = figma.combineAsVariants(componentNodes, figma.currentPage);
958 | if (data.name)
959 | componentSet.name = data.name;
960 | applyCommonProperties(componentSet, data);
961 | return componentSet;
962 | } catch (error) {
963 | console.error("Failed to create component set:", error);
964 | return null;
965 | }
966 | }
967 |
968 | // src/plugin/creators/elementCreator.ts
969 | async function createElementFromData(data) {
970 | if (!data || !data.type) {
971 | console.error("Invalid element data: missing type");
972 | return null;
973 | }
974 | let element = null;
975 | try {
976 | switch (data.type.toLowerCase()) {
977 | case "rectangle":
978 | element = createRectangleFromData(data);
979 | break;
980 | case "ellipse":
981 | case "circle":
982 | element = createEllipseFromData(data);
983 | break;
984 | case "polygon":
985 | element = createPolygonFromData(data);
986 | break;
987 | case "star":
988 | element = createStarFromData(data);
989 | break;
990 | case "line":
991 | element = createLineFromData(data);
992 | break;
993 | case "vector":
994 | element = createVectorFromData(data);
995 | break;
996 | case "frame":
997 | element = createFrameFromData(data);
998 | break;
999 | case "component":
1000 | element = createComponentFromData(data);
1001 | break;
1002 | case "componentfromnode":
1003 | element = createComponentFromNodeData(data);
1004 | break;
1005 | case "componentset":
1006 | element = createComponentSetFromData(data);
1007 | break;
1008 | case "instance":
1009 | element = createInstanceFromData(data);
1010 | break;
1011 | case "section":
1012 | element = createSectionFromData(data);
1013 | break;
1014 | case "text":
1015 | element = await createTextFromData(data);
1016 | break;
1017 | case "boolean":
1018 | case "booleanoperation":
1019 | element = createBooleanOperationFromData(data);
1020 | break;
1021 | case "connector":
1022 | element = createConnectorFromData(data);
1023 | break;
1024 | case "shapewithtext":
1025 | element = createShapeWithTextFromData(data);
1026 | break;
1027 | case "codeblock":
1028 | element = createCodeBlockFromData(data);
1029 | break;
1030 | case "table":
1031 | element = createTableFromData(data);
1032 | break;
1033 | case "widget":
1034 | element = createWidgetFromData(data);
1035 | break;
1036 | case "media":
1037 | element = createMediaFromData(data);
1038 | break;
1039 | case "image":
1040 | if (data.bytes || data.file) {
1041 | element = await createImageFromBytesAsync(data);
1042 | } else {
1043 | element = createImageFromData(data);
1044 | }
1045 | break;
1046 | case "gif":
1047 | element = createGifFromData(data);
1048 | break;
1049 | case "video":
1050 | element = await createVideoFromDataAsync(data);
1051 | break;
1052 | case "linkpreview":
1053 | element = await createLinkPreviewFromDataAsync(data);
1054 | break;
1055 | case "slice":
1056 | element = createSliceFromData(data);
1057 | break;
1058 | case "page":
1059 | const page = createPageFromData(data);
1060 | console.log(`Created page: ${page.name}`);
1061 | return null;
1062 | case "pagedivider":
1063 | element = createPageDividerFromData(data);
1064 | break;
1065 | case "slide":
1066 | element = createSlideFromData(data);
1067 | break;
1068 | case "sliderow":
1069 | element = createSlideRowFromData(data);
1070 | break;
1071 | case "group":
1072 | if (!data.children || !Array.isArray(data.children) || data.children.length < 1) {
1073 | console.error("Cannot create group: children array is required");
1074 | return null;
1075 | }
1076 | const childNodes = [];
1077 | for (const childData of data.children) {
1078 | const child = await createElementFromData(childData);
1079 | if (child)
1080 | childNodes.push(child);
1081 | }
1082 | if (childNodes.length > 0) {
1083 | element = createGroupFromData(data, childNodes);
1084 | } else {
1085 | console.error("Cannot create group: no valid children were created");
1086 | return null;
1087 | }
1088 | break;
1089 | default:
1090 | console.error(`Unsupported element type: ${data.type}`);
1091 | return null;
1092 | }
1093 | if (element) {
1094 | applyCommonProperties(element, data);
1095 | if (data.select !== false) {
1096 | selectAndFocusNodes(element);
1097 | }
1098 | }
1099 | return element;
1100 | } catch (error) {
1101 | console.error(`Error creating element: ${error instanceof Error ? error.message : "Unknown error"}`);
1102 | return null;
1103 | }
1104 | }
1105 | async function createElementsFromDataArray(dataArray) {
1106 | const createdNodes = [];
1107 | for (const data of dataArray) {
1108 | const node = await createElementFromData(data);
1109 | if (node)
1110 | createdNodes.push(node);
1111 | }
1112 | if (createdNodes.length > 0) {
1113 | selectAndFocusNodes(createdNodes);
1114 | }
1115 | return createdNodes;
1116 | }
1117 |
1118 | // src/plugin/code.ts
1119 | figma.showUI(__html__, { width: 320, height: 500 });
1120 | console.log("Figma MCP Plugin loaded");
1121 | var elementCreators = {
1122 | "create-rectangle": createRectangleFromData,
1123 | "create-circle": createEllipseFromData,
1124 | "create-ellipse": createEllipseFromData,
1125 | "create-polygon": createPolygonFromData,
1126 | "create-line": createLineFromData,
1127 | "create-text": createTextFromData,
1128 | "create-star": createStarFromData,
1129 | "create-vector": createVectorFromData,
1130 | "create-arc": (params) => {
1131 | const ellipse = createEllipseFromData(params);
1132 | if (params.arcData || params.startAngle !== undefined && params.endAngle !== undefined) {
1133 | ellipse.arcData = {
1134 | startingAngle: params.startAngle || params.arcData.startingAngle || 0,
1135 | endingAngle: params.endAngle || params.arcData.endingAngle || 360,
1136 | innerRadius: params.innerRadius || params.arcData.innerRadius || 0
1137 | };
1138 | }
1139 | return ellipse;
1140 | }
1141 | };
1142 | async function createElement(type, params) {
1143 | console.log(`Creating ${type} with params:`, params);
1144 | const creator = elementCreators[type];
1145 | if (!creator) {
1146 | console.error(`Unknown element type: ${type}`);
1147 | return null;
1148 | }
1149 | try {
1150 | const element = await Promise.resolve(creator(params));
1151 | if (element && params) {
1152 | if (params.x !== undefined)
1153 | element.x = params.x;
1154 | if (params.y !== undefined)
1155 | element.y = params.y;
1156 | }
1157 | if (element) {
1158 | selectAndFocusNodes(element);
1159 | }
1160 | return element;
1161 | } catch (error) {
1162 | console.error(`Error creating ${type}:`, error);
1163 | return null;
1164 | }
1165 | }
1166 | figma.ui.onmessage = async function(msg) {
1167 | console.log("Received message from UI:", msg);
1168 | if (elementCreators[msg.type]) {
1169 | await createElement(msg.type, msg);
1170 | } else if (msg.type === "create-element") {
1171 | console.log("Creating element with data:", msg.data);
1172 | createElementFromData(msg.data);
1173 | } else if (msg.type === "create-elements") {
1174 | console.log("Creating multiple elements with data:", msg.data);
1175 | createElementsFromDataArray(msg.data);
1176 | } else if (msg.type === "mcp-command") {
1177 | console.log("Received MCP command:", msg.command, "with params:", msg.params);
1178 | handleMcpCommand(msg.command, msg.params);
1179 | } else if (msg.type === "cancel") {
1180 | console.log("Closing plugin");
1181 | figma.closePlugin();
1182 | } else {
1183 | console.log("Unknown message type:", msg.type);
1184 | }
1185 | };
1186 | async function handleMcpCommand(command, params) {
1187 | let result = null;
1188 | try {
1189 | const pluginCommand = command.replace(/_/g, "-");
1190 | switch (pluginCommand) {
1191 | case "create-rectangle":
1192 | case "create-circle":
1193 | case "create-polygon":
1194 | case "create-line":
1195 | case "create-arc":
1196 | case "create-vector":
1197 | console.log(`MCP command: Creating ${pluginCommand.substring(7)} with params:`, params);
1198 | result = await createElement(pluginCommand, params);
1199 | break;
1200 | case "create-text":
1201 | console.log("MCP command: Creating text with params:", params);
1202 | result = await createElement(pluginCommand, params);
1203 | break;
1204 | case "create-element":
1205 | console.log("MCP command: Creating element with params:", params);
1206 | result = await createElementFromData(params);
1207 | break;
1208 | case "create-elements":
1209 | console.log("MCP command: Creating multiple elements with params:", params);
1210 | result = await createElementsFromDataArray(params);
1211 | break;
1212 | case "get-selection":
1213 | console.log("MCP command: Getting current selection");
1214 | result = figma.currentPage.selection;
1215 | break;
1216 | case "get-elements":
1217 | console.log("MCP command: Getting elements with params:", params);
1218 | const page = params.page_id ? figma.getNodeById(params.page_id) : figma.currentPage;
1219 | if (!page || page.type !== "PAGE") {
1220 | throw new Error("Invalid page ID or node is not a page");
1221 | }
1222 | const nodeType = params.type || "ALL";
1223 | const limit = params.limit || 100;
1224 | const includeHidden = params.include_hidden || false;
1225 | if (nodeType === "ALL") {
1226 | result = includeHidden ? page.children.slice(0, limit) : page.children.filter((node2) => node2.visible).slice(0, limit);
1227 | } else {
1228 | result = page.findAll((node2) => {
1229 | const typeMatch = node2.type === nodeType;
1230 | const visibilityMatch = includeHidden || node2.visible;
1231 | return typeMatch && visibilityMatch;
1232 | }).slice(0, limit);
1233 | }
1234 | break;
1235 | case "get-element":
1236 | console.log("MCP command: Getting element with ID:", params.node_id);
1237 | const node = figma.getNodeById(params.node_id);
1238 | if (!node) {
1239 | throw new Error("Element not found with ID: " + params.node_id);
1240 | }
1241 | if (!["DOCUMENT", "PAGE"].includes(node.type)) {
1242 | if (params.include_children && "children" in node) {
1243 | result = [node, ...node.children || []];
1244 | } else {
1245 | result = node;
1246 | }
1247 | } else if (node.type === "PAGE") {
1248 | result = node;
1249 | } else {
1250 | throw new Error("Unsupported node type: " + node.type);
1251 | }
1252 | break;
1253 | case "get-pages":
1254 | console.log("MCP command: Getting all pages");
1255 | result = figma.root.children;
1256 | break;
1257 | case "get-page":
1258 | console.log("MCP command: Getting page with ID:", params.page_id);
1259 | if (!params.page_id) {
1260 | console.log("No page_id provided, using current page");
1261 | result = figma.currentPage;
1262 | } else {
1263 | const pageNode = figma.getNodeById(params.page_id);
1264 | if (!pageNode || pageNode.type !== "PAGE")
1265 | throw new Error("Invalid page ID or node is not a page");
1266 | result = pageNode;
1267 | }
1268 | break;
1269 | case "create-page":
1270 | console.log("MCP command: Creating new page with name:", params.name);
1271 | const newPage = figma.createPage();
1272 | newPage.name = params.name || "New Page";
1273 | result = newPage;
1274 | break;
1275 | case "switch-page":
1276 | console.log("MCP command: Switching to page with ID:", params.id);
1277 | if (!params.id)
1278 | throw new Error("Page ID is required");
1279 | const switchPageNode = figma.getNodeById(params.id);
1280 | if (!switchPageNode || switchPageNode.type !== "PAGE")
1281 | throw new Error("Invalid page ID");
1282 | figma.currentPage = switchPageNode;
1283 | result = switchPageNode;
1284 | break;
1285 | case "modify-rectangle":
1286 | console.log("MCP command: Modifying rectangle with ID:", params.id);
1287 | if (!params.id)
1288 | throw new Error("Rectangle ID is required");
1289 | const modifyNode = figma.getNodeById(params.id);
1290 | if (!modifyNode || modifyNode.type !== "RECTANGLE")
1291 | throw new Error("Invalid rectangle ID");
1292 | const rect = modifyNode;
1293 | if (params.x !== undefined)
1294 | rect.x = params.x;
1295 | if (params.y !== undefined)
1296 | rect.y = params.y;
1297 | if (params.width !== undefined && params.height !== undefined)
1298 | rect.resize(params.width, params.height);
1299 | if (params.cornerRadius !== undefined)
1300 | rect.cornerRadius = params.cornerRadius;
1301 | if (params.color)
1302 | rect.fills = [{ type: "SOLID", color: hexToRgb(params.color) }];
1303 | result = rect;
1304 | break;
1305 | default:
1306 | console.log("Unknown MCP command:", command);
1307 | throw new Error("Unknown command: " + command);
1308 | }
1309 | let resultForBuilder = null;
1310 | if (result === null) {
1311 | resultForBuilder = null;
1312 | } else if (Array.isArray(result)) {
1313 | resultForBuilder = result;
1314 | } else if ("type" in result && result.type === "PAGE") {
1315 | resultForBuilder = result;
1316 | } else {
1317 | resultForBuilder = result;
1318 | }
1319 | const resultObject = buildResultObject(resultForBuilder);
1320 | console.log("Command result:", resultObject);
1321 | figma.ui.postMessage({
1322 | type: "mcp-response",
1323 | success: true,
1324 | command,
1325 | result: resultObject
1326 | });
1327 | console.log("Response sent to UI");
1328 | return resultObject;
1329 | } catch (error) {
1330 | console.error("Error handling MCP command:", error);
1331 | figma.ui.postMessage({
1332 | type: "mcp-response",
1333 | success: false,
1334 | command,
1335 | error: error instanceof Error ? error.message : "Unknown error"
1336 | });
1337 | console.log("Error response sent to UI");
1338 | throw error;
1339 | }
1340 | }
1341 |
```