@muloka/figma-console-mcp 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +7 -3
  2. package/dist/core/websocket-connector.d.ts +2 -1
  3. package/dist/core/websocket-connector.d.ts.map +1 -1
  4. package/dist/core/websocket-connector.js +17 -2
  5. package/dist/core/websocket-connector.js.map +1 -1
  6. package/dist/core/websocket-server.d.ts +8 -1
  7. package/dist/core/websocket-server.d.ts.map +1 -1
  8. package/dist/core/websocket-server.js +19 -3
  9. package/dist/core/websocket-server.js.map +1 -1
  10. package/dist/core/write-tools.d.ts +3 -1
  11. package/dist/core/write-tools.d.ts.map +1 -1
  12. package/dist/core/write-tools.js +98 -23
  13. package/dist/core/write-tools.js.map +1 -1
  14. package/dist/local.d.ts +2 -1
  15. package/dist/local.d.ts.map +1 -1
  16. package/dist/local.js +5 -4
  17. package/dist/local.js.map +1 -1
  18. package/figma-desktop-bridge/code.js +57 -27
  19. package/figma-desktop-bridge/ui.html +10 -2
  20. package/package.json +1 -1
  21. package/dist/apps/design-system-dashboard/mcp-app.html +0 -521
  22. package/dist/apps/token-browser/mcp-app.html +0 -410
  23. package/dist/cloudflare/apps/design-system-dashboard/scoring/accessibility.js +0 -277
  24. package/dist/cloudflare/apps/design-system-dashboard/scoring/component-metadata.js +0 -357
  25. package/dist/cloudflare/apps/design-system-dashboard/scoring/consistency.js +0 -341
  26. package/dist/cloudflare/apps/design-system-dashboard/scoring/coverage.js +0 -230
  27. package/dist/cloudflare/apps/design-system-dashboard/scoring/engine.js +0 -92
  28. package/dist/cloudflare/apps/design-system-dashboard/scoring/naming-semantics.js +0 -308
  29. package/dist/cloudflare/apps/design-system-dashboard/scoring/token-architecture.js +0 -349
  30. package/dist/cloudflare/apps/design-system-dashboard/scoring/types.js +0 -40
  31. package/dist/cloudflare/apps/design-system-dashboard/server.js +0 -159
  32. package/dist/cloudflare/apps/token-browser/server.js +0 -136
  33. package/dist/cloudflare/browser-manager.js +0 -157
  34. package/dist/cloudflare/core/accessibility-tools.js +0 -361
  35. package/dist/cloudflare/core/annotation-tools.js +0 -230
  36. package/dist/cloudflare/core/cloud-websocket-connector.js +0 -419
  37. package/dist/cloudflare/core/cloud-websocket-relay.js +0 -198
  38. package/dist/cloudflare/core/comment-tools.js +0 -292
  39. package/dist/cloudflare/core/config.js +0 -153
  40. package/dist/cloudflare/core/console-monitor.js +0 -427
  41. package/dist/cloudflare/core/deep-component-tools.js +0 -128
  42. package/dist/cloudflare/core/design-code-tools.js +0 -2793
  43. package/dist/cloudflare/core/design-system-manifest.js +0 -265
  44. package/dist/cloudflare/core/design-system-tools.js +0 -882
  45. package/dist/cloudflare/core/diagnose-tool.js +0 -100
  46. package/dist/cloudflare/core/diff/changelog-formatter.js +0 -275
  47. package/dist/cloudflare/core/diff/diff-engine.js +0 -334
  48. package/dist/cloudflare/core/diff/property-compare.js +0 -36
  49. package/dist/cloudflare/core/diff/version-cache.js +0 -74
  50. package/dist/cloudflare/core/enrichment/enrichment-service.js +0 -374
  51. package/dist/cloudflare/core/enrichment/index.js +0 -7
  52. package/dist/cloudflare/core/enrichment/relationship-mapper.js +0 -351
  53. package/dist/cloudflare/core/enrichment/style-resolver.js +0 -346
  54. package/dist/cloudflare/core/figjam-tools.js +0 -547
  55. package/dist/cloudflare/core/figma-api.js +0 -509
  56. package/dist/cloudflare/core/figma-connector.js +0 -7
  57. package/dist/cloudflare/core/figma-reconstruction-spec.js +0 -402
  58. package/dist/cloudflare/core/figma-style-extractor.js +0 -311
  59. package/dist/cloudflare/core/figma-tools.js +0 -3353
  60. package/dist/cloudflare/core/identity.js +0 -96
  61. package/dist/cloudflare/core/library-tools.js +0 -580
  62. package/dist/cloudflare/core/logger.js +0 -53
  63. package/dist/cloudflare/core/port-discovery.js +0 -935
  64. package/dist/cloudflare/core/resolve-package-root.js +0 -11
  65. package/dist/cloudflare/core/slides-tools.js +0 -714
  66. package/dist/cloudflare/core/slot-tools.js +0 -334
  67. package/dist/cloudflare/core/snippet-injector.js +0 -96
  68. package/dist/cloudflare/core/tokens/alias-resolver.js +0 -205
  69. package/dist/cloudflare/core/tokens/config.js +0 -294
  70. package/dist/cloudflare/core/tokens/dialect.js +0 -232
  71. package/dist/cloudflare/core/tokens/figma-converter.js +0 -323
  72. package/dist/cloudflare/core/tokens/formatters/css-vars.js +0 -338
  73. package/dist/cloudflare/core/tokens/formatters/dtcg.js +0 -376
  74. package/dist/cloudflare/core/tokens/formatters/index.js +0 -45
  75. package/dist/cloudflare/core/tokens/formatters/json.js +0 -205
  76. package/dist/cloudflare/core/tokens/formatters/less.js +0 -4
  77. package/dist/cloudflare/core/tokens/formatters/scss.js +0 -258
  78. package/dist/cloudflare/core/tokens/formatters/stubs.js +0 -13
  79. package/dist/cloudflare/core/tokens/formatters/style-dictionary-v3.js +0 -213
  80. package/dist/cloudflare/core/tokens/formatters/tailwind-v3.js +0 -237
  81. package/dist/cloudflare/core/tokens/formatters/tailwind-v4.js +0 -335
  82. package/dist/cloudflare/core/tokens/formatters/tokens-studio.js +0 -256
  83. package/dist/cloudflare/core/tokens/formatters/ts-module.js +0 -198
  84. package/dist/cloudflare/core/tokens/index.js +0 -16
  85. package/dist/cloudflare/core/tokens/parsers/css-vars.js +0 -4
  86. package/dist/cloudflare/core/tokens/parsers/dtcg.js +0 -280
  87. package/dist/cloudflare/core/tokens/parsers/index.js +0 -138
  88. package/dist/cloudflare/core/tokens/parsers/json.js +0 -7
  89. package/dist/cloudflare/core/tokens/parsers/scss.js +0 -4
  90. package/dist/cloudflare/core/tokens/parsers/stubs.js +0 -20
  91. package/dist/cloudflare/core/tokens/parsers/style-dictionary-v3.js +0 -4
  92. package/dist/cloudflare/core/tokens/parsers/tailwind-v3.js +0 -4
  93. package/dist/cloudflare/core/tokens/parsers/tailwind-v4.js +0 -4
  94. package/dist/cloudflare/core/tokens/parsers/tokens-studio.js +0 -4
  95. package/dist/cloudflare/core/tokens/schemas.js +0 -152
  96. package/dist/cloudflare/core/tokens/transforms/color.js +0 -12
  97. package/dist/cloudflare/core/tokens/transforms/index.js +0 -29
  98. package/dist/cloudflare/core/tokens/transforms/size.js +0 -7
  99. package/dist/cloudflare/core/tokens/types.js +0 -18
  100. package/dist/cloudflare/core/tokens-tools.js +0 -1788
  101. package/dist/cloudflare/core/types/design-code.js +0 -4
  102. package/dist/cloudflare/core/types/enriched.js +0 -5
  103. package/dist/cloudflare/core/types/index.js +0 -4
  104. package/dist/cloudflare/core/variable-resolver.js +0 -85
  105. package/dist/cloudflare/core/version-tools.js +0 -1199
  106. package/dist/cloudflare/core/websocket-connector.js +0 -436
  107. package/dist/cloudflare/core/websocket-server.js +0 -1009
  108. package/dist/cloudflare/core/write-tools.js +0 -2506
  109. package/dist/cloudflare/index.js +0 -3010
@@ -1,714 +0,0 @@
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_text_styles", `Get all local text styles in the current file. Returns style IDs, names, font info, and sizes. Use these IDs when setting textStyleId on text nodes via figma_execute.`, {}, async () => {
188
- try {
189
- const connector = await getDesktopConnector();
190
- const result = await connector.getTextStyles();
191
- return {
192
- content: [
193
- { type: "text", text: JSON.stringify(result) },
194
- ],
195
- };
196
- }
197
- catch (error) {
198
- logger.error({ error }, "figma_get_text_styles failed");
199
- return {
200
- content: [
201
- {
202
- type: "text",
203
- text: JSON.stringify({
204
- error: error instanceof Error ? error.message : String(error),
205
- }),
206
- },
207
- ],
208
- isError: true,
209
- };
210
- }
211
- });
212
- server.tool("figma_get_focused_slide", `Get the slide currently focused in single-slide view.`, {}, async () => {
213
- try {
214
- const connector = await getDesktopConnector();
215
- const result = await connector.getFocusedSlide();
216
- return {
217
- content: [
218
- { type: "text", text: JSON.stringify(result) },
219
- ],
220
- };
221
- }
222
- catch (error) {
223
- logger.error({ error }, "figma_get_focused_slide failed");
224
- return {
225
- content: [
226
- {
227
- type: "text",
228
- text: JSON.stringify({
229
- error: error instanceof Error ? error.message : String(error),
230
- hint: "This tool only works in Figma Slides files.",
231
- }),
232
- },
233
- ],
234
- isError: true,
235
- };
236
- }
237
- });
238
- // ============================================================================
239
- // WRITE TOOLS — Create and modify slides
240
- // ============================================================================
241
- server.tool("figma_create_slide", `Create a new blank slide in the Figma Slides presentation. Optionally specify grid position.`, {
242
- row: z
243
- .number()
244
- .int()
245
- .min(0)
246
- .optional()
247
- .describe("Row index in the slide grid (0-based)"),
248
- col: z
249
- .number()
250
- .int()
251
- .min(0)
252
- .optional()
253
- .describe("Column index in the slide grid (0-based)"),
254
- }, async ({ row, col }) => {
255
- try {
256
- const connector = await getDesktopConnector();
257
- const result = await connector.createSlide({ row, col });
258
- return {
259
- content: [
260
- { type: "text", text: JSON.stringify(result) },
261
- ],
262
- };
263
- }
264
- catch (error) {
265
- logger.error({ error }, "figma_create_slide failed");
266
- return {
267
- content: [
268
- {
269
- type: "text",
270
- text: JSON.stringify({
271
- error: error instanceof Error ? error.message : String(error),
272
- hint: "This tool only works in Figma Slides files.",
273
- }),
274
- },
275
- ],
276
- isError: true,
277
- };
278
- }
279
- });
280
- server.tool("figma_delete_slide", `Delete a slide from the presentation. WARNING: This is a destructive operation (can be undone with Figma's undo).`, {
281
- slideId: z
282
- .string()
283
- .max(50)
284
- .describe("The node ID of the slide to delete"),
285
- }, async ({ slideId }) => {
286
- try {
287
- const connector = await getDesktopConnector();
288
- const result = await connector.deleteSlide({ slideId });
289
- return {
290
- content: [
291
- { type: "text", text: JSON.stringify(result) },
292
- ],
293
- };
294
- }
295
- catch (error) {
296
- logger.error({ error }, "figma_delete_slide failed");
297
- return {
298
- content: [
299
- {
300
- type: "text",
301
- text: JSON.stringify({
302
- error: error instanceof Error ? error.message : String(error),
303
- hint: "This tool only works in Figma Slides files.",
304
- }),
305
- },
306
- ],
307
- isError: true,
308
- };
309
- }
310
- });
311
- server.tool("figma_duplicate_slide", `Duplicate an existing slide. The clone is placed adjacent to the original.`, {
312
- slideId: z
313
- .string()
314
- .max(50)
315
- .describe("The node ID of the slide to duplicate"),
316
- }, async ({ slideId }) => {
317
- try {
318
- const connector = await getDesktopConnector();
319
- const result = await connector.duplicateSlide({ slideId });
320
- return {
321
- content: [
322
- { type: "text", text: JSON.stringify(result) },
323
- ],
324
- };
325
- }
326
- catch (error) {
327
- logger.error({ error }, "figma_duplicate_slide failed");
328
- return {
329
- content: [
330
- {
331
- type: "text",
332
- text: JSON.stringify({
333
- error: error instanceof Error ? error.message : String(error),
334
- hint: "This tool only works in Figma Slides files.",
335
- }),
336
- },
337
- ],
338
- isError: true,
339
- };
340
- }
341
- });
342
- 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.`, {
343
- grid: z
344
- .array(z.array(z.string().max(50)).max(MAX_GRID_ROW))
345
- .max(MAX_GRID_ROWS)
346
- .describe("2D array of slide IDs representing the new order, e.g. [['1:2','1:3'],['1:4']]"),
347
- }, async ({ grid }) => {
348
- try {
349
- const connector = await getDesktopConnector();
350
- const result = await connector.reorderSlides({ grid });
351
- return {
352
- content: [
353
- { type: "text", text: JSON.stringify(result) },
354
- ],
355
- };
356
- }
357
- catch (error) {
358
- logger.error({ error }, "figma_reorder_slides failed");
359
- return {
360
- content: [
361
- {
362
- type: "text",
363
- text: JSON.stringify({
364
- error: error instanceof Error ? error.message : String(error),
365
- hint: "This tool only works in Figma Slides files.",
366
- }),
367
- },
368
- ],
369
- isError: true,
370
- };
371
- }
372
- });
373
- server.tool("figma_set_slide_transition", `Set the transition effect for a slide (style, duration, easing curve). Triggers on click by default.`, {
374
- slideId: z
375
- .string()
376
- .max(50)
377
- .describe("The node ID of the slide"),
378
- style: z
379
- .enum(TRANSITION_STYLES)
380
- .describe("Transition style"),
381
- duration: z
382
- .number()
383
- .min(0.01)
384
- .max(10)
385
- .optional()
386
- .default(0.4)
387
- .describe("Duration in seconds (0.01 to 10)"),
388
- curve: z
389
- .enum(EASING_CURVES)
390
- .optional()
391
- .default("EASE_IN_AND_OUT")
392
- .describe("Easing curve"),
393
- }, async ({ slideId, style, duration, curve }) => {
394
- try {
395
- const connector = await getDesktopConnector();
396
- const result = await connector.setSlideTransition({
397
- slideId,
398
- style,
399
- duration,
400
- curve,
401
- });
402
- return {
403
- content: [
404
- { type: "text", text: JSON.stringify(result) },
405
- ],
406
- };
407
- }
408
- catch (error) {
409
- logger.error({ error }, "figma_set_slide_transition failed");
410
- return {
411
- content: [
412
- {
413
- type: "text",
414
- text: JSON.stringify({
415
- error: error instanceof Error ? error.message : String(error),
416
- hint: "This tool only works in Figma Slides files.",
417
- }),
418
- },
419
- ],
420
- isError: true,
421
- };
422
- }
423
- });
424
- server.tool("figma_skip_slide", `Toggle whether a slide is skipped during presentation mode.`, {
425
- slideId: z
426
- .string()
427
- .max(50)
428
- .describe("The node ID of the slide"),
429
- skip: z
430
- .boolean()
431
- .describe("True to skip the slide, false to include it"),
432
- }, async ({ slideId, skip }) => {
433
- try {
434
- const connector = await getDesktopConnector();
435
- const result = await connector.skipSlide({ slideId, skip });
436
- return {
437
- content: [
438
- { type: "text", text: JSON.stringify(result) },
439
- ],
440
- };
441
- }
442
- catch (error) {
443
- logger.error({ error }, "figma_skip_slide failed");
444
- return {
445
- content: [
446
- {
447
- type: "text",
448
- text: JSON.stringify({
449
- error: error instanceof Error ? error.message : String(error),
450
- hint: "This tool only works in Figma Slides files.",
451
- }),
452
- },
453
- ],
454
- isError: true,
455
- };
456
- }
457
- });
458
- server.tool("figma_add_text_to_slide", `Add a new text element to a specific slide. Supports custom fonts, colors, alignment, and text formatting.`, {
459
- slideId: z
460
- .string()
461
- .max(50)
462
- .describe("The node ID of the slide"),
463
- text: z
464
- .string()
465
- .max(MAX_TEXT_LENGTH)
466
- .describe("The text content"),
467
- x: z.number().optional().default(100).describe("X position"),
468
- y: z.number().optional().default(100).describe("Y position"),
469
- fontSize: z
470
- .number()
471
- .min(1)
472
- .max(MAX_FONT_SIZE)
473
- .optional()
474
- .default(24)
475
- .describe("Font size in pixels"),
476
- fontFamily: z
477
- .string()
478
- .optional()
479
- .default("Inter")
480
- .describe("Font family (e.g., 'Manrope', 'IBM Plex Sans')"),
481
- fontStyle: z
482
- .string()
483
- .optional()
484
- .default("Regular")
485
- .describe("Font style/weight (e.g., 'Bold', 'SemiBold', 'Medium')"),
486
- color: z
487
- .string()
488
- .regex(/^#[0-9a-fA-F]{6}$/)
489
- .optional()
490
- .describe("Text fill color as hex (e.g., '#FFFFFF')"),
491
- textAlign: z
492
- .enum(["LEFT", "CENTER", "RIGHT"])
493
- .optional()
494
- .describe("Horizontal text alignment"),
495
- width: z
496
- .number()
497
- .min(1)
498
- .max(MAX_DIMENSION)
499
- .optional()
500
- .describe("Text box width for wrapping. When set, enables text wrapping."),
501
- lineHeight: z
502
- .number()
503
- .min(1)
504
- .max(500)
505
- .optional()
506
- .describe("Line height in pixels"),
507
- letterSpacing: z
508
- .number()
509
- .optional()
510
- .describe("Letter spacing in pixels"),
511
- textCase: z
512
- .enum(["ORIGINAL", "UPPER", "LOWER", "TITLE"])
513
- .optional()
514
- .describe("Text case transformation"),
515
- }, async ({ slideId, text, x, y, fontSize, fontFamily, fontStyle, color, textAlign, width, lineHeight, letterSpacing, textCase }) => {
516
- try {
517
- const connector = await getDesktopConnector();
518
- const result = await connector.addTextToSlide({
519
- slideId,
520
- text,
521
- x,
522
- y,
523
- fontSize,
524
- fontFamily,
525
- fontStyle,
526
- color,
527
- textAlign,
528
- width,
529
- lineHeight,
530
- letterSpacing,
531
- textCase,
532
- });
533
- return {
534
- content: [
535
- { type: "text", text: JSON.stringify(result) },
536
- ],
537
- };
538
- }
539
- catch (error) {
540
- logger.error({ error }, "figma_add_text_to_slide failed");
541
- return {
542
- content: [
543
- {
544
- type: "text",
545
- text: JSON.stringify({
546
- error: error instanceof Error ? error.message : String(error),
547
- hint: "This tool only works in Figma Slides files.",
548
- }),
549
- },
550
- ],
551
- isError: true,
552
- };
553
- }
554
- });
555
- server.tool("figma_add_shape_to_slide", `Add a rectangle or ellipse shape to a specific slide with optional fill color.`, {
556
- slideId: z
557
- .string()
558
- .max(50)
559
- .describe("The node ID of the slide"),
560
- shapeType: z
561
- .enum(SLIDE_SHAPE_TYPES)
562
- .describe("Shape type"),
563
- x: z.number().describe("X position"),
564
- y: z.number().describe("Y position"),
565
- width: z
566
- .number()
567
- .min(1)
568
- .max(MAX_DIMENSION)
569
- .describe("Width in pixels"),
570
- height: z
571
- .number()
572
- .min(1)
573
- .max(MAX_DIMENSION)
574
- .describe("Height in pixels"),
575
- fillColor: z
576
- .string()
577
- .regex(/^#[0-9a-fA-F]{6}$/, "Must be a 6-digit hex color like '#FF5733'")
578
- .optional()
579
- .default("#CCCCCC")
580
- .describe("Hex color e.g. '#FF5733'"),
581
- }, async ({ slideId, shapeType, x, y, width, height, fillColor }) => {
582
- try {
583
- const connector = await getDesktopConnector();
584
- const result = await connector.addShapeToSlide({
585
- slideId,
586
- shapeType,
587
- x,
588
- y,
589
- width,
590
- height,
591
- fillColor,
592
- });
593
- return {
594
- content: [
595
- { type: "text", text: JSON.stringify(result) },
596
- ],
597
- };
598
- }
599
- catch (error) {
600
- logger.error({ error }, "figma_add_shape_to_slide failed");
601
- return {
602
- content: [
603
- {
604
- type: "text",
605
- text: JSON.stringify({
606
- error: error instanceof Error ? error.message : String(error),
607
- hint: "This tool only works in Figma Slides files.",
608
- }),
609
- },
610
- ],
611
- isError: true,
612
- };
613
- }
614
- });
615
- server.tool("figma_set_slide_background", `Set the background color of a slide. Creates or updates a full-slide background rectangle.`, {
616
- slideId: z
617
- .string()
618
- .max(50)
619
- .describe("The node ID of the slide"),
620
- color: z
621
- .string()
622
- .regex(/^#[0-9a-fA-F]{6}$/, "Must be a 6-digit hex color like '#181818'")
623
- .describe("Background color as hex (e.g., '#181818')"),
624
- }, async ({ slideId, color }) => {
625
- try {
626
- const connector = await getDesktopConnector();
627
- const result = await connector.setSlideBackground({ slideId, color });
628
- return {
629
- content: [
630
- { type: "text", text: JSON.stringify(result) },
631
- ],
632
- };
633
- }
634
- catch (error) {
635
- logger.error({ error }, "figma_set_slide_background failed");
636
- return {
637
- content: [
638
- {
639
- type: "text",
640
- text: JSON.stringify({
641
- error: error instanceof Error ? error.message : String(error),
642
- hint: "This tool only works in Figma Slides files.",
643
- }),
644
- },
645
- ],
646
- isError: true,
647
- };
648
- }
649
- });
650
- // ============================================================================
651
- // NAVIGATION TOOLS — Control slide view
652
- // ============================================================================
653
- server.tool("figma_set_slides_view_mode", `Toggle the Figma Slides viewport between grid view and single-slide view.`, {
654
- mode: z
655
- .enum(VIEW_MODES)
656
- .describe("View mode"),
657
- }, async ({ mode }) => {
658
- try {
659
- const connector = await getDesktopConnector();
660
- const result = await connector.setSlidesViewMode({ mode });
661
- return {
662
- content: [
663
- { type: "text", text: JSON.stringify(result) },
664
- ],
665
- };
666
- }
667
- catch (error) {
668
- logger.error({ error }, "figma_set_slides_view_mode failed");
669
- return {
670
- content: [
671
- {
672
- type: "text",
673
- text: JSON.stringify({
674
- error: error instanceof Error ? error.message : String(error),
675
- hint: "This tool only works in Figma Slides files.",
676
- }),
677
- },
678
- ],
679
- isError: true,
680
- };
681
- }
682
- });
683
- server.tool("figma_focus_slide", `Navigate to and focus a specific slide in single-slide view.`, {
684
- slideId: z
685
- .string()
686
- .max(50)
687
- .describe("The node ID of the slide to focus"),
688
- }, async ({ slideId }) => {
689
- try {
690
- const connector = await getDesktopConnector();
691
- const result = await connector.focusSlide({ slideId });
692
- return {
693
- content: [
694
- { type: "text", text: JSON.stringify(result) },
695
- ],
696
- };
697
- }
698
- catch (error) {
699
- logger.error({ error }, "figma_focus_slide failed");
700
- return {
701
- content: [
702
- {
703
- type: "text",
704
- text: JSON.stringify({
705
- error: error instanceof Error ? error.message : String(error),
706
- hint: "This tool only works in Figma Slides files.",
707
- }),
708
- },
709
- ],
710
- isError: true,
711
- };
712
- }
713
- });
714
- }