figma-console-mcp 1.16.0 β 1.17.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.
- package/README.md +37 -9
- package/dist/cloudflare/core/cloud-websocket-connector.js +75 -0
- package/dist/cloudflare/core/figjam-tools.js +485 -0
- package/dist/cloudflare/core/figma-desktop-connector.js +25 -0
- package/dist/cloudflare/core/figma-tools.js +4 -1
- package/dist/cloudflare/core/slides-tools.js +607 -0
- package/dist/cloudflare/core/websocket-connector.js +75 -0
- package/dist/cloudflare/core/websocket-server.js +11 -0
- package/dist/cloudflare/index.js +29 -7
- package/dist/core/figma-connector.d.ts +54 -0
- package/dist/core/figma-connector.d.ts.map +1 -1
- package/dist/core/figma-desktop-connector.d.ts +15 -0
- package/dist/core/figma-desktop-connector.d.ts.map +1 -1
- package/dist/core/figma-desktop-connector.js +16 -0
- package/dist/core/figma-desktop-connector.js.map +1 -1
- package/dist/core/figma-tools.d.ts.map +1 -1
- package/dist/core/figma-tools.js +4 -1
- package/dist/core/figma-tools.js.map +1 -1
- package/dist/core/slides-tools.d.ts +8 -0
- package/dist/core/slides-tools.d.ts.map +1 -0
- package/dist/core/slides-tools.js +608 -0
- package/dist/core/slides-tools.js.map +1 -0
- package/dist/core/websocket-connector.d.ts +54 -0
- package/dist/core/websocket-connector.d.ts.map +1 -1
- package/dist/core/websocket-connector.js +48 -0
- package/dist/core/websocket-connector.js.map +1 -1
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +3 -0
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/code.js +575 -5
- package/figma-desktop-bridge/manifest.json +1 -1
- package/figma-desktop-bridge/ui.html +93 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
> **Your design system as an API.** Model Context Protocol server that bridges design and developmentβgiving AI assistants complete access to Figma for **extraction**, **creation**, and **debugging**.
|
|
10
10
|
|
|
11
|
-
> **π FigJam
|
|
11
|
+
> **π FigJam + Slides β AI Across All Figma Products:** 24 new tools bring AI to FigJam boards and Figma Slides. Create stickies, flowcharts, and tables on whiteboards. Manage entire presentations β slides, transitions, content, and reordering. [FigJam Guide β](docs/figjam.md) | [Slides Guide β](docs/slides.md)
|
|
12
12
|
|
|
13
13
|
## What is this?
|
|
14
14
|
|
|
@@ -51,9 +51,9 @@ Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
|
|
|
51
51
|
| Real-time monitoring (console, selection) | β
| β | β |
|
|
52
52
|
| Desktop Bridge plugin | β
| β
| β |
|
|
53
53
|
| Requires Node.js | Yes | **No** | No |
|
|
54
|
-
| **Total tools available** | **
|
|
54
|
+
| **Total tools available** | **78+** | **43** | **22** |
|
|
55
55
|
|
|
56
|
-
> **Bottom line:** Remote SSE is **read-only** with ~38% of the tools. **Cloud Mode** unlocks write access from web AI clients without Node.js. NPX/Local Git gives the full
|
|
56
|
+
> **Bottom line:** Remote SSE is **read-only** with ~38% of the tools. **Cloud Mode** unlocks write access from web AI clients without Node.js. NPX/Local Git gives the full 78+ tools with real-time monitoring.
|
|
57
57
|
|
|
58
58
|
---
|
|
59
59
|
|
|
@@ -61,7 +61,7 @@ Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
|
|
|
61
61
|
|
|
62
62
|
**Best for:** Designers who want full AI-assisted design capabilities.
|
|
63
63
|
|
|
64
|
-
**What you get:** All
|
|
64
|
+
**What you get:** All 78+ tools including design creation, variable management, and component instantiation.
|
|
65
65
|
|
|
66
66
|
#### Prerequisites
|
|
67
67
|
|
|
@@ -155,7 +155,7 @@ Create a simple frame with a blue background
|
|
|
155
155
|
|
|
156
156
|
**Best for:** Developers who want to modify source code or contribute to the project.
|
|
157
157
|
|
|
158
|
-
**What you get:** Same
|
|
158
|
+
**What you get:** Same 78+ tools as NPX, plus full source code access.
|
|
159
159
|
|
|
160
160
|
#### Quick Setup
|
|
161
161
|
|
|
@@ -301,7 +301,7 @@ AI Client β Cloud MCP Server β Durable Object Relay β Desktop Bridge Plugi
|
|
|
301
301
|
| Feature | NPX (Recommended) | Cloud Mode | Local Git | Remote SSE |
|
|
302
302
|
|---------|-------------------|------------|-----------|------------|
|
|
303
303
|
| **Setup time** | ~10 minutes | ~5 minutes | ~15 minutes | ~2 minutes |
|
|
304
|
-
| **Total tools** | **
|
|
304
|
+
| **Total tools** | **78+** | **43** | **78+** | **22** (read-only) |
|
|
305
305
|
| **Design creation** | β
| β
| β
| β |
|
|
306
306
|
| **Variable management** | β
| β
| β
| β |
|
|
307
307
|
| **Component instantiation** | β
| β
| β
| β |
|
|
@@ -316,7 +316,7 @@ AI Client β Cloud MCP Server β Durable Object Relay β Desktop Bridge Plugi
|
|
|
316
316
|
| **Automatic updates** | β
(`@latest`) | β
| Manual (`git pull`) | β
|
|
|
317
317
|
| **Source code access** | β | β | β
| β |
|
|
318
318
|
|
|
319
|
-
> **Key insight:** Remote SSE is read-only. Cloud Mode adds write access for web AI clients without Node.js. NPX/Local Git give the full
|
|
319
|
+
> **Key insight:** Remote SSE is read-only. Cloud Mode adds write access for web AI clients without Node.js. NPX/Local Git give the full 78+ tools.
|
|
320
320
|
|
|
321
321
|
**π [Complete Feature Comparison](docs/mode-comparison.md)**
|
|
322
322
|
|
|
@@ -440,6 +440,23 @@ When you first use design system tools:
|
|
|
440
440
|
- `figjam_get_board_contents` - Read all content from a FigJam board
|
|
441
441
|
- `figjam_get_connections` - Read the connection graph (flowcharts, relationships)
|
|
442
442
|
|
|
443
|
+
### ποΈ Slides Presentation Tools (Local Mode + Cloud Mode)
|
|
444
|
+
- `figma_list_slides` - List all slides with IDs, positions, and skip status
|
|
445
|
+
- `figma_get_slide_content` - Get the full content tree of a slide
|
|
446
|
+
- `figma_get_slide_grid` - Get the 2D grid layout of the presentation
|
|
447
|
+
- `figma_get_slide_transition` - Read transition settings for a slide
|
|
448
|
+
- `figma_get_focused_slide` - Get the currently focused slide
|
|
449
|
+
- `figma_create_slide` - Create a new blank slide
|
|
450
|
+
- `figma_delete_slide` - Delete a slide from the presentation
|
|
451
|
+
- `figma_duplicate_slide` - Clone an existing slide
|
|
452
|
+
- `figma_reorder_slides` - Reorder slides via new 2D grid layout
|
|
453
|
+
- `figma_set_slide_transition` - Set transition effects (22 styles, 8 curves)
|
|
454
|
+
- `figma_skip_slide` - Toggle whether a slide is skipped in presentation mode
|
|
455
|
+
- `figma_add_text_to_slide` - Add text to a specific slide
|
|
456
|
+
- `figma_add_shape_to_slide` - Add rectangle or ellipse shapes with color
|
|
457
|
+
- `figma_set_slides_view_mode` - Toggle grid vs. single-slide view
|
|
458
|
+
- `figma_focus_slide` - Navigate to a specific slide
|
|
459
|
+
|
|
443
460
|
**π [Detailed Tool Documentation](docs/TOOLS.md)**
|
|
444
461
|
|
|
445
462
|
---
|
|
@@ -502,6 +519,16 @@ Generate an affinity map from these meeting notes
|
|
|
502
519
|
Create a comparison table of our three platform options
|
|
503
520
|
```
|
|
504
521
|
|
|
522
|
+
### Slides Presentations
|
|
523
|
+
```
|
|
524
|
+
List all slides and tell me which ones are skipped
|
|
525
|
+
Add a new slide with the title "Thank You" in 72px text
|
|
526
|
+
Set a DISSOLVE transition on the first slide with 0.5 second duration
|
|
527
|
+
Duplicate slide 5 for an A/B comparison
|
|
528
|
+
Skip slides 8 and 9 β they're not ready for the client presentation
|
|
529
|
+
Reorder my slides so the conclusion comes before Q&A
|
|
530
|
+
```
|
|
531
|
+
|
|
505
532
|
### Visual Debugging
|
|
506
533
|
```
|
|
507
534
|
Take a screenshot of the current Figma canvas
|
|
@@ -621,7 +648,7 @@ The **Figma Desktop Bridge** plugin is the recommended way to connect Figma to t
|
|
|
621
648
|
- The MCP server communicates via **WebSocket** through the Desktop Bridge plugin
|
|
622
649
|
- The server tries port 9223 first, then automatically falls back through ports 9224β9232 if needed
|
|
623
650
|
- The plugin scans all ports in the range and connects to every active server it finds
|
|
624
|
-
- All
|
|
651
|
+
- All 78+ tools work through the WebSocket transport
|
|
625
652
|
|
|
626
653
|
**Multiple files:** The WebSocket server supports multiple simultaneous plugin connections β one per open Figma file. Each connection is tracked by file key with independent state (selection, document changes, console logs).
|
|
627
654
|
|
|
@@ -758,9 +785,10 @@ The architecture supports adding new apps with minimal boilerplate β each app
|
|
|
758
785
|
|
|
759
786
|
## π€οΈ Roadmap
|
|
760
787
|
|
|
761
|
-
**Current Status:** v1.
|
|
788
|
+
**Current Status:** v1.17.0 (Stable) - Production-ready with FigJam + Slides support, Cloud Write Relay, Design System Kit, WebSocket-only connectivity, smart multi-file tracking, 78+ tools, Comments API, and MCP Apps
|
|
762
789
|
|
|
763
790
|
**Recent Releases:**
|
|
791
|
+
- [x] **v1.17.0** - Figma Slides Support: 15 new tools for managing presentations β slides, transitions, content, reordering, and navigation. Inspired by Toni Haidamous (PR #11).
|
|
764
792
|
- [x] **v1.16.0** - FigJam Support: 9 new tools for creating and reading FigJam boards β stickies, flowcharts, tables, code blocks, and connection graphs. Community-contributed by klgral and lukemoderwell.
|
|
765
793
|
- [x] **v1.12.0** - Cloud Write Relay: web AI clients (Claude.ai, v0, Replit, Lovable) can create and modify Figma designs via cloud relay pairing β no Node.js required
|
|
766
794
|
- [x] **v1.11.2** - Screenshot fix: `figma_take_screenshot` works without explicit `nodeId` in WebSocket mode
|
|
@@ -244,6 +244,81 @@ export class CloudWebSocketConnector {
|
|
|
244
244
|
return this.sendCommand('LINT_DESIGN', params, 120000);
|
|
245
245
|
}
|
|
246
246
|
// ============================================================================
|
|
247
|
+
// FigJam operations
|
|
248
|
+
// ============================================================================
|
|
249
|
+
async createSticky(params) {
|
|
250
|
+
return this.sendCommand('CREATE_STICKY', params);
|
|
251
|
+
}
|
|
252
|
+
async createStickies(params) {
|
|
253
|
+
return this.sendCommand('CREATE_STICKIES', params, 30000);
|
|
254
|
+
}
|
|
255
|
+
async createConnector(params) {
|
|
256
|
+
return this.sendCommand('CREATE_CONNECTOR', params);
|
|
257
|
+
}
|
|
258
|
+
async createShapeWithText(params) {
|
|
259
|
+
return this.sendCommand('CREATE_SHAPE_WITH_TEXT', params);
|
|
260
|
+
}
|
|
261
|
+
async createTable(params) {
|
|
262
|
+
return this.sendCommand('CREATE_TABLE', params, 30000);
|
|
263
|
+
}
|
|
264
|
+
async createCodeBlock(params) {
|
|
265
|
+
return this.sendCommand('CREATE_CODE_BLOCK', params);
|
|
266
|
+
}
|
|
267
|
+
async getBoardContents(params) {
|
|
268
|
+
return this.sendCommand('GET_BOARD_CONTENTS', params, 30000);
|
|
269
|
+
}
|
|
270
|
+
async getConnections() {
|
|
271
|
+
return this.sendCommand('GET_CONNECTIONS', {}, 15000);
|
|
272
|
+
}
|
|
273
|
+
// ============================================================================
|
|
274
|
+
// Slides operations
|
|
275
|
+
// ============================================================================
|
|
276
|
+
async listSlides() {
|
|
277
|
+
return this.sendCommand('LIST_SLIDES', {}, 10000);
|
|
278
|
+
}
|
|
279
|
+
async getSlideContent(params) {
|
|
280
|
+
return this.sendCommand('GET_SLIDE_CONTENT', params, 10000);
|
|
281
|
+
}
|
|
282
|
+
async createSlide(params) {
|
|
283
|
+
return this.sendCommand('CREATE_SLIDE', params, 10000);
|
|
284
|
+
}
|
|
285
|
+
async deleteSlide(params) {
|
|
286
|
+
return this.sendCommand('DELETE_SLIDE', params, 5000);
|
|
287
|
+
}
|
|
288
|
+
async duplicateSlide(params) {
|
|
289
|
+
return this.sendCommand('DUPLICATE_SLIDE', params, 5000);
|
|
290
|
+
}
|
|
291
|
+
async getSlideGrid() {
|
|
292
|
+
return this.sendCommand('GET_SLIDE_GRID', {}, 10000);
|
|
293
|
+
}
|
|
294
|
+
async reorderSlides(params) {
|
|
295
|
+
return this.sendCommand('REORDER_SLIDES', params, 15000);
|
|
296
|
+
}
|
|
297
|
+
async setSlideTransition(params) {
|
|
298
|
+
return this.sendCommand('SET_SLIDE_TRANSITION', params, 5000);
|
|
299
|
+
}
|
|
300
|
+
async getSlideTransition(params) {
|
|
301
|
+
return this.sendCommand('GET_SLIDE_TRANSITION', params, 5000);
|
|
302
|
+
}
|
|
303
|
+
async setSlidesViewMode(params) {
|
|
304
|
+
return this.sendCommand('SET_SLIDES_VIEW_MODE', params, 5000);
|
|
305
|
+
}
|
|
306
|
+
async getFocusedSlide() {
|
|
307
|
+
return this.sendCommand('GET_FOCUSED_SLIDE', {}, 5000);
|
|
308
|
+
}
|
|
309
|
+
async focusSlide(params) {
|
|
310
|
+
return this.sendCommand('FOCUS_SLIDE', params, 5000);
|
|
311
|
+
}
|
|
312
|
+
async skipSlide(params) {
|
|
313
|
+
return this.sendCommand('SKIP_SLIDE', params, 5000);
|
|
314
|
+
}
|
|
315
|
+
async addTextToSlide(params) {
|
|
316
|
+
return this.sendCommand('ADD_TEXT_TO_SLIDE', params, 10000);
|
|
317
|
+
}
|
|
318
|
+
async addShapeToSlide(params) {
|
|
319
|
+
return this.sendCommand('ADD_SHAPE_TO_SLIDE', params, 5000);
|
|
320
|
+
}
|
|
321
|
+
// ============================================================================
|
|
247
322
|
// Cache management (no-op for cloud relay)
|
|
248
323
|
// ============================================================================
|
|
249
324
|
clearFrameCache() {
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createChildLogger } from "./logger.js";
|
|
3
|
+
const logger = createChildLogger({ component: "figjam-tools" });
|
|
4
|
+
/** Valid sticky note colors */
|
|
5
|
+
const STICKY_COLORS = [
|
|
6
|
+
"YELLOW",
|
|
7
|
+
"BLUE",
|
|
8
|
+
"GREEN",
|
|
9
|
+
"PINK",
|
|
10
|
+
"ORANGE",
|
|
11
|
+
"PURPLE",
|
|
12
|
+
"RED",
|
|
13
|
+
"LIGHT_GRAY",
|
|
14
|
+
"GRAY",
|
|
15
|
+
];
|
|
16
|
+
/** Valid FigJam shape types */
|
|
17
|
+
const SHAPE_TYPES = [
|
|
18
|
+
"ROUNDED_RECTANGLE",
|
|
19
|
+
"DIAMOND",
|
|
20
|
+
"ELLIPSE",
|
|
21
|
+
"TRIANGLE_UP",
|
|
22
|
+
"TRIANGLE_DOWN",
|
|
23
|
+
"PARALLELOGRAM_RIGHT",
|
|
24
|
+
"PARALLELOGRAM_LEFT",
|
|
25
|
+
"ENG_DATABASE",
|
|
26
|
+
"ENG_QUEUE",
|
|
27
|
+
"ENG_FILE",
|
|
28
|
+
"ENG_FOLDER",
|
|
29
|
+
];
|
|
30
|
+
/** Valid FigJam node types for board content filtering */
|
|
31
|
+
const FIGJAM_NODE_TYPES = [
|
|
32
|
+
"STICKY",
|
|
33
|
+
"SHAPE_WITH_TEXT",
|
|
34
|
+
"CONNECTOR",
|
|
35
|
+
"TABLE",
|
|
36
|
+
"CODE_BLOCK",
|
|
37
|
+
"SECTION",
|
|
38
|
+
"FRAME",
|
|
39
|
+
"TEXT",
|
|
40
|
+
];
|
|
41
|
+
/** Maximum items for batch operations to prevent DoS / plugin timeouts */
|
|
42
|
+
const MAX_BATCH_SIZE = 200;
|
|
43
|
+
/** Maximum table dimensions */
|
|
44
|
+
const MAX_TABLE_ROWS = 100;
|
|
45
|
+
const MAX_TABLE_COLUMNS = 50;
|
|
46
|
+
/** Maximum text length per field */
|
|
47
|
+
const MAX_TEXT_LENGTH = 5000;
|
|
48
|
+
/** Maximum code block length */
|
|
49
|
+
const MAX_CODE_LENGTH = 50000;
|
|
50
|
+
/** Maximum node IDs for arrangement */
|
|
51
|
+
const MAX_ARRANGE_NODES = 500;
|
|
52
|
+
/** Maximum nodes to return from board content reads */
|
|
53
|
+
const MAX_READ_NODES = 1000;
|
|
54
|
+
/**
|
|
55
|
+
* Register FigJam-specific tools.
|
|
56
|
+
* These tools only work when the connected file is a FigJam board (editorType === 'figjam').
|
|
57
|
+
* Used by both local mode (src/local.ts) and cloud mode (src/index.ts).
|
|
58
|
+
*/
|
|
59
|
+
export function registerFigJamTools(server, getDesktopConnector) {
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// STICKY NOTE TOOLS
|
|
62
|
+
// ============================================================================
|
|
63
|
+
server.tool("figjam_create_sticky", `Create a sticky note on a FigJam board. Only works in FigJam files.
|
|
64
|
+
|
|
65
|
+
**Colors:** YELLOW, BLUE, GREEN, PINK, ORANGE, PURPLE, RED, LIGHT_GRAY, GRAY (default: YELLOW)`, {
|
|
66
|
+
text: z
|
|
67
|
+
.string()
|
|
68
|
+
.max(MAX_TEXT_LENGTH)
|
|
69
|
+
.describe("Text content for the sticky note"),
|
|
70
|
+
color: z
|
|
71
|
+
.enum(STICKY_COLORS)
|
|
72
|
+
.optional()
|
|
73
|
+
.describe("Sticky color"),
|
|
74
|
+
x: z.number().optional().describe("X position on canvas"),
|
|
75
|
+
y: z.number().optional().describe("Y position on canvas"),
|
|
76
|
+
}, async ({ text, color, x, y }) => {
|
|
77
|
+
try {
|
|
78
|
+
const connector = await getDesktopConnector();
|
|
79
|
+
const result = await connector.createSticky({ text, color, x, y });
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
logger.error({ error }, "figjam_create_sticky failed");
|
|
86
|
+
return {
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: JSON.stringify({
|
|
91
|
+
error: error instanceof Error ? error.message : String(error),
|
|
92
|
+
hint: "This tool only works in FigJam files. Make sure the Desktop Bridge plugin is running in a FigJam board.",
|
|
93
|
+
}),
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
isError: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
server.tool("figjam_create_stickies", `Batch create multiple sticky notes on a FigJam board (max ${MAX_BATCH_SIZE}). Use this to populate boards from structured data (meeting notes, brainstorm ideas, etc.).
|
|
101
|
+
|
|
102
|
+
**Colors:** YELLOW, BLUE, GREEN, PINK, ORANGE, PURPLE, RED, LIGHT_GRAY, GRAY`, {
|
|
103
|
+
stickies: z
|
|
104
|
+
.array(z.object({
|
|
105
|
+
text: z.string().max(MAX_TEXT_LENGTH).describe("Text content"),
|
|
106
|
+
color: z.enum(STICKY_COLORS).optional().describe("Sticky color"),
|
|
107
|
+
x: z.number().optional().describe("X position"),
|
|
108
|
+
y: z.number().optional().describe("Y position"),
|
|
109
|
+
}))
|
|
110
|
+
.max(MAX_BATCH_SIZE)
|
|
111
|
+
.describe(`Array of sticky note specifications (max ${MAX_BATCH_SIZE})`),
|
|
112
|
+
}, async ({ stickies }) => {
|
|
113
|
+
try {
|
|
114
|
+
const connector = await getDesktopConnector();
|
|
115
|
+
const result = await connector.createStickies({ stickies });
|
|
116
|
+
return {
|
|
117
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
logger.error({ error }, "figjam_create_stickies failed");
|
|
122
|
+
return {
|
|
123
|
+
content: [
|
|
124
|
+
{
|
|
125
|
+
type: "text",
|
|
126
|
+
text: JSON.stringify({
|
|
127
|
+
error: error instanceof Error ? error.message : String(error),
|
|
128
|
+
hint: "This tool only works in FigJam files.",
|
|
129
|
+
}),
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
isError: true,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// CONNECTOR TOOL
|
|
138
|
+
// ============================================================================
|
|
139
|
+
server.tool("figjam_create_connector", `Connect two nodes with a connector line in FigJam. Use to create flowcharts, diagrams, and relationship maps.
|
|
140
|
+
|
|
141
|
+
Nodes must exist on the board (stickies, shapes, etc.). Use their node IDs from creation results.`, {
|
|
142
|
+
startNodeId: z.string().describe("Node ID of the start element"),
|
|
143
|
+
endNodeId: z.string().describe("Node ID of the end element"),
|
|
144
|
+
label: z
|
|
145
|
+
.string()
|
|
146
|
+
.max(MAX_TEXT_LENGTH)
|
|
147
|
+
.optional()
|
|
148
|
+
.describe("Optional text label on the connector"),
|
|
149
|
+
}, async ({ startNodeId, endNodeId, label }) => {
|
|
150
|
+
try {
|
|
151
|
+
const connector = await getDesktopConnector();
|
|
152
|
+
const result = await connector.createConnector({
|
|
153
|
+
startNodeId,
|
|
154
|
+
endNodeId,
|
|
155
|
+
label,
|
|
156
|
+
});
|
|
157
|
+
return {
|
|
158
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
logger.error({ error }, "figjam_create_connector failed");
|
|
163
|
+
return {
|
|
164
|
+
content: [
|
|
165
|
+
{
|
|
166
|
+
type: "text",
|
|
167
|
+
text: JSON.stringify({
|
|
168
|
+
error: error instanceof Error ? error.message : String(error),
|
|
169
|
+
hint: "This tool only works in FigJam files. Both start and end nodes must exist.",
|
|
170
|
+
}),
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
isError: true,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
// ============================================================================
|
|
178
|
+
// SHAPE WITH TEXT TOOL
|
|
179
|
+
// ============================================================================
|
|
180
|
+
server.tool("figjam_create_shape_with_text", `Create a labeled shape on a FigJam board. Use for flowchart nodes, process diagrams, and visual organization.
|
|
181
|
+
|
|
182
|
+
**Shape types:** ROUNDED_RECTANGLE (default), DIAMOND, ELLIPSE, TRIANGLE_UP, TRIANGLE_DOWN, PARALLELOGRAM_RIGHT, PARALLELOGRAM_LEFT, ENG_DATABASE, ENG_QUEUE, ENG_FILE, ENG_FOLDER`, {
|
|
183
|
+
text: z
|
|
184
|
+
.string()
|
|
185
|
+
.max(MAX_TEXT_LENGTH)
|
|
186
|
+
.optional()
|
|
187
|
+
.describe("Text label for the shape"),
|
|
188
|
+
shapeType: z
|
|
189
|
+
.enum(SHAPE_TYPES)
|
|
190
|
+
.optional()
|
|
191
|
+
.describe("Shape type"),
|
|
192
|
+
x: z.number().optional().describe("X position on canvas"),
|
|
193
|
+
y: z.number().optional().describe("Y position on canvas"),
|
|
194
|
+
}, async ({ text, shapeType, x, y }) => {
|
|
195
|
+
try {
|
|
196
|
+
const connector = await getDesktopConnector();
|
|
197
|
+
const result = await connector.createShapeWithText({
|
|
198
|
+
text,
|
|
199
|
+
shapeType,
|
|
200
|
+
x,
|
|
201
|
+
y,
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
logger.error({ error }, "figjam_create_shape_with_text failed");
|
|
209
|
+
return {
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
type: "text",
|
|
213
|
+
text: JSON.stringify({
|
|
214
|
+
error: error instanceof Error ? error.message : String(error),
|
|
215
|
+
hint: "This tool only works in FigJam files.",
|
|
216
|
+
}),
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
isError: true,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
// ============================================================================
|
|
224
|
+
// TABLE TOOL
|
|
225
|
+
// ============================================================================
|
|
226
|
+
server.tool("figjam_create_table", `Create a table on a FigJam board with optional cell data. Use for structured data display, comparison matrices, and organized information.
|
|
227
|
+
|
|
228
|
+
**Data format:** 2D array of strings, e.g. [["Header1", "Header2"], ["Row1Col1", "Row1Col2"]]`, {
|
|
229
|
+
rows: z
|
|
230
|
+
.number()
|
|
231
|
+
.min(1)
|
|
232
|
+
.max(MAX_TABLE_ROWS)
|
|
233
|
+
.describe(`Number of rows (1-${MAX_TABLE_ROWS})`),
|
|
234
|
+
columns: z
|
|
235
|
+
.number()
|
|
236
|
+
.min(1)
|
|
237
|
+
.max(MAX_TABLE_COLUMNS)
|
|
238
|
+
.describe(`Number of columns (1-${MAX_TABLE_COLUMNS})`),
|
|
239
|
+
data: z
|
|
240
|
+
.array(z.array(z.string().max(MAX_TEXT_LENGTH)))
|
|
241
|
+
.optional()
|
|
242
|
+
.describe("2D array of cell text content (row-major order)"),
|
|
243
|
+
x: z.number().optional().describe("X position on canvas"),
|
|
244
|
+
y: z.number().optional().describe("Y position on canvas"),
|
|
245
|
+
}, async ({ rows, columns, data, x, y }) => {
|
|
246
|
+
try {
|
|
247
|
+
const connector = await getDesktopConnector();
|
|
248
|
+
const result = await connector.createTable({
|
|
249
|
+
rows,
|
|
250
|
+
columns,
|
|
251
|
+
data,
|
|
252
|
+
x,
|
|
253
|
+
y,
|
|
254
|
+
});
|
|
255
|
+
return {
|
|
256
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
logger.error({ error }, "figjam_create_table failed");
|
|
261
|
+
return {
|
|
262
|
+
content: [
|
|
263
|
+
{
|
|
264
|
+
type: "text",
|
|
265
|
+
text: JSON.stringify({
|
|
266
|
+
error: error instanceof Error ? error.message : String(error),
|
|
267
|
+
hint: "This tool only works in FigJam files.",
|
|
268
|
+
}),
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
isError: true,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
// ============================================================================
|
|
276
|
+
// CODE BLOCK TOOL
|
|
277
|
+
// ============================================================================
|
|
278
|
+
server.tool("figjam_create_code_block", `Create a code block on a FigJam board. Use for sharing code snippets, config examples, or technical documentation in collaborative boards.`, {
|
|
279
|
+
code: z.string().max(MAX_CODE_LENGTH).describe("The code content"),
|
|
280
|
+
language: z
|
|
281
|
+
.string()
|
|
282
|
+
.optional()
|
|
283
|
+
.describe("Programming language (e.g., 'JAVASCRIPT', 'PYTHON', 'TYPESCRIPT', 'JSON', 'HTML', 'CSS')"),
|
|
284
|
+
x: z.number().optional().describe("X position on canvas"),
|
|
285
|
+
y: z.number().optional().describe("Y position on canvas"),
|
|
286
|
+
}, async ({ code, language, x, y }) => {
|
|
287
|
+
try {
|
|
288
|
+
const connector = await getDesktopConnector();
|
|
289
|
+
const result = await connector.createCodeBlock({
|
|
290
|
+
code,
|
|
291
|
+
language,
|
|
292
|
+
x,
|
|
293
|
+
y,
|
|
294
|
+
});
|
|
295
|
+
return {
|
|
296
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
logger.error({ error }, "figjam_create_code_block failed");
|
|
301
|
+
return {
|
|
302
|
+
content: [
|
|
303
|
+
{
|
|
304
|
+
type: "text",
|
|
305
|
+
text: JSON.stringify({
|
|
306
|
+
error: error instanceof Error ? error.message : String(error),
|
|
307
|
+
hint: "This tool only works in FigJam files.",
|
|
308
|
+
}),
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
isError: true,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
// ============================================================================
|
|
316
|
+
// LAYOUT HELPER TOOL
|
|
317
|
+
// ============================================================================
|
|
318
|
+
server.tool("figjam_auto_arrange", `Arrange nodes on a FigJam board in a grid, horizontal row, or vertical column layout. Use after batch-creating elements to organize them neatly.`, {
|
|
319
|
+
nodeIds: z
|
|
320
|
+
.array(z.string())
|
|
321
|
+
.max(MAX_ARRANGE_NODES)
|
|
322
|
+
.describe(`Array of node IDs to arrange (max ${MAX_ARRANGE_NODES})`),
|
|
323
|
+
layout: z
|
|
324
|
+
.enum(["grid", "horizontal", "vertical"])
|
|
325
|
+
.optional()
|
|
326
|
+
.default("grid")
|
|
327
|
+
.describe("Layout type: grid, horizontal, or vertical"),
|
|
328
|
+
spacing: z
|
|
329
|
+
.number()
|
|
330
|
+
.optional()
|
|
331
|
+
.default(40)
|
|
332
|
+
.describe("Spacing between nodes in pixels"),
|
|
333
|
+
columns: z
|
|
334
|
+
.number()
|
|
335
|
+
.optional()
|
|
336
|
+
.describe("Number of columns for grid layout (defaults to sqrt of node count)"),
|
|
337
|
+
}, async ({ nodeIds, layout, spacing, columns }) => {
|
|
338
|
+
try {
|
|
339
|
+
const connector = await getDesktopConnector();
|
|
340
|
+
// Compute grid columns safely on the server side β no string interpolation
|
|
341
|
+
const gridCols = columns || Math.ceil(Math.sqrt(nodeIds.length));
|
|
342
|
+
// Pass all parameters as a JSON object to avoid code injection.
|
|
343
|
+
// The plugin code reads from the params object, not interpolated strings.
|
|
344
|
+
const paramsJson = JSON.stringify({
|
|
345
|
+
nodeIds,
|
|
346
|
+
layout,
|
|
347
|
+
spacing,
|
|
348
|
+
gridCols,
|
|
349
|
+
});
|
|
350
|
+
// Use JSON.stringify to produce a properly-escaped double-quoted JS string literal.
|
|
351
|
+
// This handles all control characters including \u2028/\u2029 that manual
|
|
352
|
+
// single-quote escaping would miss.
|
|
353
|
+
const code = `
|
|
354
|
+
const params = JSON.parse(${JSON.stringify(paramsJson)});
|
|
355
|
+
const nodes = [];
|
|
356
|
+
for (const id of params.nodeIds) {
|
|
357
|
+
const node = await figma.getNodeByIdAsync(id);
|
|
358
|
+
if (node) nodes.push(node);
|
|
359
|
+
}
|
|
360
|
+
if (nodes.length === 0) throw new Error('No valid nodes found');
|
|
361
|
+
|
|
362
|
+
let x = nodes[0].x;
|
|
363
|
+
let y = nodes[0].y;
|
|
364
|
+
const startX = x;
|
|
365
|
+
let maxRowHeight = 0;
|
|
366
|
+
|
|
367
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
368
|
+
const node = nodes[i];
|
|
369
|
+
if (params.layout === 'horizontal') {
|
|
370
|
+
node.x = x;
|
|
371
|
+
node.y = y;
|
|
372
|
+
x += node.width + params.spacing;
|
|
373
|
+
} else if (params.layout === 'vertical') {
|
|
374
|
+
node.x = x;
|
|
375
|
+
node.y = y;
|
|
376
|
+
y += node.height + params.spacing;
|
|
377
|
+
} else {
|
|
378
|
+
const col = i % params.gridCols;
|
|
379
|
+
if (col === 0 && i > 0) {
|
|
380
|
+
y += maxRowHeight + params.spacing;
|
|
381
|
+
maxRowHeight = 0;
|
|
382
|
+
x = startX;
|
|
383
|
+
}
|
|
384
|
+
node.x = x;
|
|
385
|
+
node.y = y;
|
|
386
|
+
maxRowHeight = Math.max(maxRowHeight, node.height);
|
|
387
|
+
x += node.width + params.spacing;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return { arranged: nodes.length, layout: params.layout };
|
|
391
|
+
`;
|
|
392
|
+
const result = await connector.executeCodeViaUI(code, 10000);
|
|
393
|
+
return {
|
|
394
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
logger.error({ error }, "figjam_auto_arrange failed");
|
|
399
|
+
return {
|
|
400
|
+
content: [
|
|
401
|
+
{
|
|
402
|
+
type: "text",
|
|
403
|
+
text: JSON.stringify({
|
|
404
|
+
error: error instanceof Error ? error.message : String(error),
|
|
405
|
+
hint: "Make sure all node IDs are valid and the Desktop Bridge plugin is running.",
|
|
406
|
+
}),
|
|
407
|
+
},
|
|
408
|
+
],
|
|
409
|
+
isError: true,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
// ============================================================================
|
|
414
|
+
// READ TOOLS β Query existing FigJam board content
|
|
415
|
+
// ============================================================================
|
|
416
|
+
server.tool("figjam_get_board_contents", `Read all content from a FigJam board. Returns stickies, shapes, connectors, tables, code blocks, and sections with their text content and positions.
|
|
417
|
+
|
|
418
|
+
Use this to understand what's on a board before modifying it, or to extract structured data from collaborative sessions.
|
|
419
|
+
|
|
420
|
+
**Filters:** Pass nodeTypes to limit results (e.g., ["STICKY"] for only stickies). Omit for everything.`, {
|
|
421
|
+
nodeTypes: z
|
|
422
|
+
.array(z.enum(FIGJAM_NODE_TYPES))
|
|
423
|
+
.optional()
|
|
424
|
+
.describe("Filter by node types. Omit for all."),
|
|
425
|
+
maxNodes: z
|
|
426
|
+
.number()
|
|
427
|
+
.min(1)
|
|
428
|
+
.max(MAX_READ_NODES)
|
|
429
|
+
.optional()
|
|
430
|
+
.default(500)
|
|
431
|
+
.describe(`Maximum nodes to return (1-${MAX_READ_NODES}, default: 500)`),
|
|
432
|
+
}, async ({ nodeTypes, maxNodes }) => {
|
|
433
|
+
try {
|
|
434
|
+
const connector = await getDesktopConnector();
|
|
435
|
+
const result = await connector.getBoardContents({
|
|
436
|
+
nodeTypes,
|
|
437
|
+
maxNodes,
|
|
438
|
+
});
|
|
439
|
+
return {
|
|
440
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
logger.error({ error }, "figjam_get_board_contents failed");
|
|
445
|
+
return {
|
|
446
|
+
content: [
|
|
447
|
+
{
|
|
448
|
+
type: "text",
|
|
449
|
+
text: JSON.stringify({
|
|
450
|
+
error: error instanceof Error ? error.message : String(error),
|
|
451
|
+
hint: "This tool only works in FigJam files. Make sure the Desktop Bridge plugin is running in a FigJam board.",
|
|
452
|
+
}),
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
isError: true,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
server.tool("figjam_get_connections", `Read the connection graph from a FigJam board. Returns all connectors with their start/end node references and labels.
|
|
460
|
+
|
|
461
|
+
Use this to understand relationships, flowcharts, and diagrams. Returns edges as {startNodeId, endNodeId, label} plus a summary of connected nodes.`, {}, async () => {
|
|
462
|
+
try {
|
|
463
|
+
const connector = await getDesktopConnector();
|
|
464
|
+
const result = await connector.getConnections();
|
|
465
|
+
return {
|
|
466
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
logger.error({ error }, "figjam_get_connections failed");
|
|
471
|
+
return {
|
|
472
|
+
content: [
|
|
473
|
+
{
|
|
474
|
+
type: "text",
|
|
475
|
+
text: JSON.stringify({
|
|
476
|
+
error: error instanceof Error ? error.message : String(error),
|
|
477
|
+
hint: "This tool only works in FigJam files. Make sure the Desktop Bridge plugin is running in a FigJam board.",
|
|
478
|
+
}),
|
|
479
|
+
},
|
|
480
|
+
],
|
|
481
|
+
isError: true,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
}
|