@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.
Files changed (68) hide show
  1. package/README.md +13 -12
  2. package/dist/cloudflare/core/annotation-tools.js +230 -0
  3. package/dist/cloudflare/core/cloud-websocket-connector.js +93 -0
  4. package/dist/cloudflare/core/deep-component-tools.js +128 -0
  5. package/dist/cloudflare/core/design-code-tools.js +65 -7
  6. package/dist/cloudflare/core/enrichment/enrichment-service.js +108 -12
  7. package/dist/cloudflare/core/figjam-tools.js +485 -0
  8. package/dist/cloudflare/core/figma-api.js +7 -4
  9. package/dist/cloudflare/core/figma-desktop-connector.js +108 -0
  10. package/dist/cloudflare/core/figma-tools.js +445 -55
  11. package/dist/cloudflare/core/port-discovery.js +88 -0
  12. package/dist/cloudflare/core/resolve-package-root.js +11 -0
  13. package/dist/cloudflare/core/slides-tools.js +607 -0
  14. package/dist/cloudflare/core/websocket-connector.js +93 -0
  15. package/dist/cloudflare/core/websocket-server.js +18 -9
  16. package/dist/cloudflare/index.js +164 -41
  17. package/dist/core/annotation-tools.d.ts +14 -0
  18. package/dist/core/annotation-tools.d.ts.map +1 -0
  19. package/dist/core/annotation-tools.js +231 -0
  20. package/dist/core/annotation-tools.js.map +1 -0
  21. package/dist/core/deep-component-tools.d.ts +14 -0
  22. package/dist/core/deep-component-tools.d.ts.map +1 -0
  23. package/dist/core/deep-component-tools.js +129 -0
  24. package/dist/core/deep-component-tools.js.map +1 -0
  25. package/dist/core/design-code-tools.d.ts.map +1 -1
  26. package/dist/core/design-code-tools.js +65 -7
  27. package/dist/core/design-code-tools.js.map +1 -1
  28. package/dist/core/enrichment/enrichment-service.d.ts.map +1 -1
  29. package/dist/core/enrichment/enrichment-service.js +108 -12
  30. package/dist/core/enrichment/enrichment-service.js.map +1 -1
  31. package/dist/core/figma-api.d.ts +1 -1
  32. package/dist/core/figma-api.d.ts.map +1 -1
  33. package/dist/core/figma-api.js +7 -4
  34. package/dist/core/figma-api.js.map +1 -1
  35. package/dist/core/figma-connector.d.ts +5 -0
  36. package/dist/core/figma-connector.d.ts.map +1 -1
  37. package/dist/core/figma-desktop-connector.d.ts +20 -0
  38. package/dist/core/figma-desktop-connector.d.ts.map +1 -1
  39. package/dist/core/figma-desktop-connector.js +83 -0
  40. package/dist/core/figma-desktop-connector.js.map +1 -1
  41. package/dist/core/figma-tools.d.ts.map +1 -1
  42. package/dist/core/figma-tools.js +355 -26
  43. package/dist/core/figma-tools.js.map +1 -1
  44. package/dist/core/port-discovery.d.ts +21 -0
  45. package/dist/core/port-discovery.d.ts.map +1 -1
  46. package/dist/core/port-discovery.js +88 -0
  47. package/dist/core/port-discovery.js.map +1 -1
  48. package/dist/core/resolve-package-root.d.ts +2 -0
  49. package/dist/core/resolve-package-root.d.ts.map +1 -0
  50. package/dist/core/resolve-package-root.js +12 -0
  51. package/dist/core/resolve-package-root.js.map +1 -0
  52. package/dist/core/types/design-code.d.ts +1 -0
  53. package/dist/core/types/design-code.d.ts.map +1 -1
  54. package/dist/core/websocket-connector.d.ts +5 -0
  55. package/dist/core/websocket-connector.d.ts.map +1 -1
  56. package/dist/core/websocket-connector.js +18 -0
  57. package/dist/core/websocket-connector.js.map +1 -1
  58. package/dist/core/websocket-server.d.ts.map +1 -1
  59. package/dist/core/websocket-server.js +7 -9
  60. package/dist/core/websocket-server.js.map +1 -1
  61. package/dist/local.d.ts +6 -0
  62. package/dist/local.d.ts.map +1 -1
  63. package/dist/local.js +58 -1
  64. package/dist/local.js.map +1 -1
  65. package/figma-desktop-bridge/code.js +906 -4
  66. package/figma-desktop-bridge/ui-full.html +80 -0
  67. package/figma-desktop-bridge/ui.html +82 -0
  68. package/package.json +1 -1
@@ -0,0 +1,607 @@
1
+ import { z } from "zod";
2
+ import { createChildLogger } from "./logger.js";
3
+ const logger = createChildLogger({ component: "slides-tools" });
4
+ /** Maximum text length for slide content */
5
+ const MAX_TEXT_LENGTH = 10000;
6
+ /** Maximum font size */
7
+ const MAX_FONT_SIZE = 1000;
8
+ /** Maximum shape dimension */
9
+ const MAX_DIMENSION = 10000;
10
+ /** Maximum slides per grid row */
11
+ const MAX_GRID_ROW = 50;
12
+ /** Maximum grid rows */
13
+ const MAX_GRID_ROWS = 50;
14
+ /** Valid transition styles */
15
+ const TRANSITION_STYLES = [
16
+ "NONE",
17
+ "DISSOLVE",
18
+ "SLIDE_FROM_LEFT",
19
+ "SLIDE_FROM_RIGHT",
20
+ "SLIDE_FROM_TOP",
21
+ "SLIDE_FROM_BOTTOM",
22
+ "PUSH_FROM_LEFT",
23
+ "PUSH_FROM_RIGHT",
24
+ "PUSH_FROM_TOP",
25
+ "PUSH_FROM_BOTTOM",
26
+ "MOVE_FROM_LEFT",
27
+ "MOVE_FROM_RIGHT",
28
+ "MOVE_FROM_TOP",
29
+ "MOVE_FROM_BOTTOM",
30
+ "SLIDE_OUT_TO_LEFT",
31
+ "SLIDE_OUT_TO_RIGHT",
32
+ "SLIDE_OUT_TO_TOP",
33
+ "SLIDE_OUT_TO_BOTTOM",
34
+ "MOVE_OUT_TO_LEFT",
35
+ "MOVE_OUT_TO_RIGHT",
36
+ "MOVE_OUT_TO_TOP",
37
+ "MOVE_OUT_TO_BOTTOM",
38
+ "SMART_ANIMATE",
39
+ ];
40
+ /** Valid easing curves */
41
+ /** Valid easing curves (Figma Slides uses a different set from the general Plugin API) */
42
+ const EASING_CURVES = [
43
+ "LINEAR",
44
+ "EASE_IN",
45
+ "EASE_OUT",
46
+ "EASE_IN_AND_OUT",
47
+ "GENTLE",
48
+ "QUICK",
49
+ "BOUNCY",
50
+ "SLOW",
51
+ ];
52
+ /** Valid shape types for slides */
53
+ const SLIDE_SHAPE_TYPES = ["RECTANGLE", "ELLIPSE"];
54
+ /** Valid view modes */
55
+ const VIEW_MODES = ["grid", "single-slide"];
56
+ /**
57
+ * Register Figma Slides tools.
58
+ * These tools only work when the connected file is a Figma Slides presentation (editorType === 'slides').
59
+ * Used by both local mode (src/local.ts) and cloud mode (src/index.ts).
60
+ */
61
+ export function registerSlidesTools(server, getDesktopConnector) {
62
+ // ============================================================================
63
+ // READ TOOLS — Query slide data
64
+ // ============================================================================
65
+ server.tool("figma_list_slides", `List all slides in the current Figma Slides presentation with their IDs, names, grid positions, and skip status. Only works in Slides files.`, {}, async () => {
66
+ try {
67
+ const connector = await getDesktopConnector();
68
+ const result = await connector.listSlides();
69
+ return {
70
+ content: [
71
+ {
72
+ type: "text",
73
+ text: JSON.stringify({
74
+ slides: result.data || result.result,
75
+ count: Array.isArray(result.data || result.result)
76
+ ? (result.data || result.result).length
77
+ : 0,
78
+ }),
79
+ },
80
+ ],
81
+ };
82
+ }
83
+ catch (error) {
84
+ logger.error({ error }, "figma_list_slides failed");
85
+ return {
86
+ content: [
87
+ {
88
+ type: "text",
89
+ text: JSON.stringify({
90
+ error: error instanceof Error ? error.message : String(error),
91
+ hint: "This tool only works in Figma Slides files. Make sure the Desktop Bridge plugin is running in a Slides presentation.",
92
+ }),
93
+ },
94
+ ],
95
+ isError: true,
96
+ };
97
+ }
98
+ });
99
+ server.tool("figma_get_slide_content", `Get the content tree of a specific slide including all text, shapes, and frames. Returns node hierarchy with properties.`, {
100
+ slideId: z
101
+ .string()
102
+ .max(50)
103
+ .describe("The node ID of the slide, e.g. '1:23'"),
104
+ }, async ({ slideId }) => {
105
+ try {
106
+ const connector = await getDesktopConnector();
107
+ const result = await connector.getSlideContent({ slideId });
108
+ return {
109
+ content: [
110
+ { type: "text", text: JSON.stringify(result) },
111
+ ],
112
+ };
113
+ }
114
+ catch (error) {
115
+ logger.error({ error }, "figma_get_slide_content failed");
116
+ return {
117
+ content: [
118
+ {
119
+ type: "text",
120
+ text: JSON.stringify({
121
+ error: error instanceof Error ? error.message : String(error),
122
+ hint: "This tool only works in Figma Slides files.",
123
+ }),
124
+ },
125
+ ],
126
+ isError: true,
127
+ };
128
+ }
129
+ });
130
+ server.tool("figma_get_slide_grid", `Get the 2D slide grid layout showing how slides are organized in rows and columns.`, {}, async () => {
131
+ try {
132
+ const connector = await getDesktopConnector();
133
+ const result = await connector.getSlideGrid();
134
+ return {
135
+ content: [
136
+ { type: "text", text: JSON.stringify(result) },
137
+ ],
138
+ };
139
+ }
140
+ catch (error) {
141
+ logger.error({ error }, "figma_get_slide_grid failed");
142
+ return {
143
+ content: [
144
+ {
145
+ type: "text",
146
+ text: JSON.stringify({
147
+ error: error instanceof Error ? error.message : String(error),
148
+ hint: "This tool only works in Figma Slides files.",
149
+ }),
150
+ },
151
+ ],
152
+ isError: true,
153
+ };
154
+ }
155
+ });
156
+ server.tool("figma_get_slide_transition", `Get the current transition settings for a slide (style, duration, easing curve, timing).`, {
157
+ slideId: z
158
+ .string()
159
+ .max(50)
160
+ .describe("The node ID of the slide"),
161
+ }, async ({ slideId }) => {
162
+ try {
163
+ const connector = await getDesktopConnector();
164
+ const result = await connector.getSlideTransition({ slideId });
165
+ return {
166
+ content: [
167
+ { type: "text", text: JSON.stringify(result) },
168
+ ],
169
+ };
170
+ }
171
+ catch (error) {
172
+ logger.error({ error }, "figma_get_slide_transition failed");
173
+ return {
174
+ content: [
175
+ {
176
+ type: "text",
177
+ text: JSON.stringify({
178
+ error: error instanceof Error ? error.message : String(error),
179
+ hint: "This tool only works in Figma Slides files.",
180
+ }),
181
+ },
182
+ ],
183
+ isError: true,
184
+ };
185
+ }
186
+ });
187
+ server.tool("figma_get_focused_slide", `Get the slide currently focused in single-slide view.`, {}, async () => {
188
+ try {
189
+ const connector = await getDesktopConnector();
190
+ const result = await connector.getFocusedSlide();
191
+ return {
192
+ content: [
193
+ { type: "text", text: JSON.stringify(result) },
194
+ ],
195
+ };
196
+ }
197
+ catch (error) {
198
+ logger.error({ error }, "figma_get_focused_slide failed");
199
+ return {
200
+ content: [
201
+ {
202
+ type: "text",
203
+ text: JSON.stringify({
204
+ error: error instanceof Error ? error.message : String(error),
205
+ hint: "This tool only works in Figma Slides files.",
206
+ }),
207
+ },
208
+ ],
209
+ isError: true,
210
+ };
211
+ }
212
+ });
213
+ // ============================================================================
214
+ // WRITE TOOLS — Create and modify slides
215
+ // ============================================================================
216
+ server.tool("figma_create_slide", `Create a new blank slide in the Figma Slides presentation. Optionally specify grid position.`, {
217
+ row: z
218
+ .number()
219
+ .int()
220
+ .min(0)
221
+ .optional()
222
+ .describe("Row index in the slide grid (0-based)"),
223
+ col: z
224
+ .number()
225
+ .int()
226
+ .min(0)
227
+ .optional()
228
+ .describe("Column index in the slide grid (0-based)"),
229
+ }, async ({ row, col }) => {
230
+ try {
231
+ const connector = await getDesktopConnector();
232
+ const result = await connector.createSlide({ row, col });
233
+ return {
234
+ content: [
235
+ { type: "text", text: JSON.stringify(result) },
236
+ ],
237
+ };
238
+ }
239
+ catch (error) {
240
+ logger.error({ error }, "figma_create_slide failed");
241
+ return {
242
+ content: [
243
+ {
244
+ type: "text",
245
+ text: JSON.stringify({
246
+ error: error instanceof Error ? error.message : String(error),
247
+ hint: "This tool only works in Figma Slides files.",
248
+ }),
249
+ },
250
+ ],
251
+ isError: true,
252
+ };
253
+ }
254
+ });
255
+ server.tool("figma_delete_slide", `Delete a slide from the presentation. WARNING: This is a destructive operation (can be undone with Figma's undo).`, {
256
+ slideId: z
257
+ .string()
258
+ .max(50)
259
+ .describe("The node ID of the slide to delete"),
260
+ }, async ({ slideId }) => {
261
+ try {
262
+ const connector = await getDesktopConnector();
263
+ const result = await connector.deleteSlide({ slideId });
264
+ return {
265
+ content: [
266
+ { type: "text", text: JSON.stringify(result) },
267
+ ],
268
+ };
269
+ }
270
+ catch (error) {
271
+ logger.error({ error }, "figma_delete_slide failed");
272
+ return {
273
+ content: [
274
+ {
275
+ type: "text",
276
+ text: JSON.stringify({
277
+ error: error instanceof Error ? error.message : String(error),
278
+ hint: "This tool only works in Figma Slides files.",
279
+ }),
280
+ },
281
+ ],
282
+ isError: true,
283
+ };
284
+ }
285
+ });
286
+ server.tool("figma_duplicate_slide", `Duplicate an existing slide. The clone is placed adjacent to the original.`, {
287
+ slideId: z
288
+ .string()
289
+ .max(50)
290
+ .describe("The node ID of the slide to duplicate"),
291
+ }, async ({ slideId }) => {
292
+ try {
293
+ const connector = await getDesktopConnector();
294
+ const result = await connector.duplicateSlide({ slideId });
295
+ return {
296
+ content: [
297
+ { type: "text", text: JSON.stringify(result) },
298
+ ],
299
+ };
300
+ }
301
+ catch (error) {
302
+ logger.error({ error }, "figma_duplicate_slide failed");
303
+ return {
304
+ content: [
305
+ {
306
+ type: "text",
307
+ text: JSON.stringify({
308
+ error: error instanceof Error ? error.message : String(error),
309
+ hint: "This tool only works in Figma Slides files.",
310
+ }),
311
+ },
312
+ ],
313
+ isError: true,
314
+ };
315
+ }
316
+ });
317
+ server.tool("figma_reorder_slides", `Reorder slides by providing a new 2D array of slide IDs. Each inner array represents a row in the grid. WARNING: This is a destructive operation.`, {
318
+ grid: z
319
+ .array(z.array(z.string().max(50)).max(MAX_GRID_ROW))
320
+ .max(MAX_GRID_ROWS)
321
+ .describe("2D array of slide IDs representing the new order, e.g. [['1:2','1:3'],['1:4']]"),
322
+ }, async ({ grid }) => {
323
+ try {
324
+ const connector = await getDesktopConnector();
325
+ const result = await connector.reorderSlides({ grid });
326
+ return {
327
+ content: [
328
+ { type: "text", text: JSON.stringify(result) },
329
+ ],
330
+ };
331
+ }
332
+ catch (error) {
333
+ logger.error({ error }, "figma_reorder_slides failed");
334
+ return {
335
+ content: [
336
+ {
337
+ type: "text",
338
+ text: JSON.stringify({
339
+ error: error instanceof Error ? error.message : String(error),
340
+ hint: "This tool only works in Figma Slides files.",
341
+ }),
342
+ },
343
+ ],
344
+ isError: true,
345
+ };
346
+ }
347
+ });
348
+ server.tool("figma_set_slide_transition", `Set the transition effect for a slide (style, duration, easing curve). Triggers on click by default.`, {
349
+ slideId: z
350
+ .string()
351
+ .max(50)
352
+ .describe("The node ID of the slide"),
353
+ style: z
354
+ .enum(TRANSITION_STYLES)
355
+ .describe("Transition style"),
356
+ duration: z
357
+ .number()
358
+ .min(0.01)
359
+ .max(10)
360
+ .optional()
361
+ .default(0.4)
362
+ .describe("Duration in seconds (0.01 to 10)"),
363
+ curve: z
364
+ .enum(EASING_CURVES)
365
+ .optional()
366
+ .default("EASE_IN_AND_OUT")
367
+ .describe("Easing curve"),
368
+ }, async ({ slideId, style, duration, curve }) => {
369
+ try {
370
+ const connector = await getDesktopConnector();
371
+ const result = await connector.setSlideTransition({
372
+ slideId,
373
+ style,
374
+ duration,
375
+ curve,
376
+ });
377
+ return {
378
+ content: [
379
+ { type: "text", text: JSON.stringify(result) },
380
+ ],
381
+ };
382
+ }
383
+ catch (error) {
384
+ logger.error({ error }, "figma_set_slide_transition failed");
385
+ return {
386
+ content: [
387
+ {
388
+ type: "text",
389
+ text: JSON.stringify({
390
+ error: error instanceof Error ? error.message : String(error),
391
+ hint: "This tool only works in Figma Slides files.",
392
+ }),
393
+ },
394
+ ],
395
+ isError: true,
396
+ };
397
+ }
398
+ });
399
+ server.tool("figma_skip_slide", `Toggle whether a slide is skipped during presentation mode.`, {
400
+ slideId: z
401
+ .string()
402
+ .max(50)
403
+ .describe("The node ID of the slide"),
404
+ skip: z
405
+ .boolean()
406
+ .describe("True to skip the slide, false to include it"),
407
+ }, async ({ slideId, skip }) => {
408
+ try {
409
+ const connector = await getDesktopConnector();
410
+ const result = await connector.skipSlide({ slideId, skip });
411
+ return {
412
+ content: [
413
+ { type: "text", text: JSON.stringify(result) },
414
+ ],
415
+ };
416
+ }
417
+ catch (error) {
418
+ logger.error({ error }, "figma_skip_slide failed");
419
+ return {
420
+ content: [
421
+ {
422
+ type: "text",
423
+ text: JSON.stringify({
424
+ error: error instanceof Error ? error.message : String(error),
425
+ hint: "This tool only works in Figma Slides files.",
426
+ }),
427
+ },
428
+ ],
429
+ isError: true,
430
+ };
431
+ }
432
+ });
433
+ server.tool("figma_add_text_to_slide", `Add a new text element to a specific slide. Uses Inter font by default.`, {
434
+ slideId: z
435
+ .string()
436
+ .max(50)
437
+ .describe("The node ID of the slide"),
438
+ text: z
439
+ .string()
440
+ .max(MAX_TEXT_LENGTH)
441
+ .describe("The text content"),
442
+ x: z.number().optional().default(100).describe("X position"),
443
+ y: z.number().optional().default(100).describe("Y position"),
444
+ fontSize: z
445
+ .number()
446
+ .min(1)
447
+ .max(MAX_FONT_SIZE)
448
+ .optional()
449
+ .default(24)
450
+ .describe("Font size in pixels"),
451
+ }, async ({ slideId, text, x, y, fontSize }) => {
452
+ try {
453
+ const connector = await getDesktopConnector();
454
+ const result = await connector.addTextToSlide({
455
+ slideId,
456
+ text,
457
+ x,
458
+ y,
459
+ fontSize,
460
+ });
461
+ return {
462
+ content: [
463
+ { type: "text", text: JSON.stringify(result) },
464
+ ],
465
+ };
466
+ }
467
+ catch (error) {
468
+ logger.error({ error }, "figma_add_text_to_slide failed");
469
+ return {
470
+ content: [
471
+ {
472
+ type: "text",
473
+ text: JSON.stringify({
474
+ error: error instanceof Error ? error.message : String(error),
475
+ hint: "This tool only works in Figma Slides files.",
476
+ }),
477
+ },
478
+ ],
479
+ isError: true,
480
+ };
481
+ }
482
+ });
483
+ server.tool("figma_add_shape_to_slide", `Add a rectangle or ellipse shape to a specific slide with optional fill color.`, {
484
+ slideId: z
485
+ .string()
486
+ .max(50)
487
+ .describe("The node ID of the slide"),
488
+ shapeType: z
489
+ .enum(SLIDE_SHAPE_TYPES)
490
+ .describe("Shape type"),
491
+ x: z.number().describe("X position"),
492
+ y: z.number().describe("Y position"),
493
+ width: z
494
+ .number()
495
+ .min(1)
496
+ .max(MAX_DIMENSION)
497
+ .describe("Width in pixels"),
498
+ height: z
499
+ .number()
500
+ .min(1)
501
+ .max(MAX_DIMENSION)
502
+ .describe("Height in pixels"),
503
+ fillColor: z
504
+ .string()
505
+ .regex(/^#[0-9a-fA-F]{6}$/, "Must be a 6-digit hex color like '#FF5733'")
506
+ .optional()
507
+ .default("#CCCCCC")
508
+ .describe("Hex color e.g. '#FF5733'"),
509
+ }, async ({ slideId, shapeType, x, y, width, height, fillColor }) => {
510
+ try {
511
+ const connector = await getDesktopConnector();
512
+ const result = await connector.addShapeToSlide({
513
+ slideId,
514
+ shapeType,
515
+ x,
516
+ y,
517
+ width,
518
+ height,
519
+ fillColor,
520
+ });
521
+ return {
522
+ content: [
523
+ { type: "text", text: JSON.stringify(result) },
524
+ ],
525
+ };
526
+ }
527
+ catch (error) {
528
+ logger.error({ error }, "figma_add_shape_to_slide failed");
529
+ return {
530
+ content: [
531
+ {
532
+ type: "text",
533
+ text: JSON.stringify({
534
+ error: error instanceof Error ? error.message : String(error),
535
+ hint: "This tool only works in Figma Slides files.",
536
+ }),
537
+ },
538
+ ],
539
+ isError: true,
540
+ };
541
+ }
542
+ });
543
+ // ============================================================================
544
+ // NAVIGATION TOOLS — Control slide view
545
+ // ============================================================================
546
+ server.tool("figma_set_slides_view_mode", `Toggle the Figma Slides viewport between grid view and single-slide view.`, {
547
+ mode: z
548
+ .enum(VIEW_MODES)
549
+ .describe("View mode"),
550
+ }, async ({ mode }) => {
551
+ try {
552
+ const connector = await getDesktopConnector();
553
+ const result = await connector.setSlidesViewMode({ mode });
554
+ return {
555
+ content: [
556
+ { type: "text", text: JSON.stringify(result) },
557
+ ],
558
+ };
559
+ }
560
+ catch (error) {
561
+ logger.error({ error }, "figma_set_slides_view_mode failed");
562
+ return {
563
+ content: [
564
+ {
565
+ type: "text",
566
+ text: JSON.stringify({
567
+ error: error instanceof Error ? error.message : String(error),
568
+ hint: "This tool only works in Figma Slides files.",
569
+ }),
570
+ },
571
+ ],
572
+ isError: true,
573
+ };
574
+ }
575
+ });
576
+ server.tool("figma_focus_slide", `Navigate to and focus a specific slide in single-slide view.`, {
577
+ slideId: z
578
+ .string()
579
+ .max(50)
580
+ .describe("The node ID of the slide to focus"),
581
+ }, async ({ slideId }) => {
582
+ try {
583
+ const connector = await getDesktopConnector();
584
+ const result = await connector.focusSlide({ slideId });
585
+ return {
586
+ content: [
587
+ { type: "text", text: JSON.stringify(result) },
588
+ ],
589
+ };
590
+ }
591
+ catch (error) {
592
+ logger.error({ error }, "figma_focus_slide failed");
593
+ return {
594
+ content: [
595
+ {
596
+ type: "text",
597
+ text: JSON.stringify({
598
+ error: error instanceof Error ? error.message : String(error),
599
+ hint: "This tool only works in Figma Slides files.",
600
+ }),
601
+ },
602
+ ],
603
+ isError: true,
604
+ };
605
+ }
606
+ });
607
+ }