figma-console-mcp 1.15.3 → 1.15.5
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/dist/cloudflare/core/port-discovery.js +71 -0
- package/dist/cloudflare/core/websocket-server.js +150 -20
- package/dist/cloudflare/core/write-tools.js +10 -2
- package/dist/cloudflare/index.js +6 -6
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +35 -10
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/code.js +30 -2
- package/figma-desktop-bridge/ui.html +1151 -174
- package/package.json +1 -1
|
@@ -265,6 +265,77 @@ export function cleanupStalePortFiles() {
|
|
|
265
265
|
}
|
|
266
266
|
return cleaned;
|
|
267
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Deep scan for orphaned MCP server processes that hold ports but have no port files.
|
|
270
|
+
* These are processes left behind by Claude Desktop when tabs close without proper cleanup.
|
|
271
|
+
*
|
|
272
|
+
* Uses lsof (macOS/Linux) to find PIDs listening on each port in the range,
|
|
273
|
+
* then verifies they're figma-console-mcp before terminating.
|
|
274
|
+
*
|
|
275
|
+
* Call AFTER cleanupStalePortFiles() — that handles the port-file-based cleanup first,
|
|
276
|
+
* then this catches any remaining ghosts.
|
|
277
|
+
*/
|
|
278
|
+
export function cleanupOrphanedProcesses(preferredPort = DEFAULT_WS_PORT) {
|
|
279
|
+
// Only supported on macOS/Linux (lsof)
|
|
280
|
+
if (process.platform === 'win32')
|
|
281
|
+
return 0;
|
|
282
|
+
let cleaned = 0;
|
|
283
|
+
const myPid = process.pid;
|
|
284
|
+
const ports = getPortRange(preferredPort);
|
|
285
|
+
// Collect PIDs that have valid port files (known-good servers)
|
|
286
|
+
const knownPids = new Set();
|
|
287
|
+
for (const port of ports) {
|
|
288
|
+
const data = readPortFile(port);
|
|
289
|
+
if (data)
|
|
290
|
+
knownPids.add(data.pid);
|
|
291
|
+
}
|
|
292
|
+
knownPids.add(myPid); // Never kill ourselves
|
|
293
|
+
for (const port of ports) {
|
|
294
|
+
try {
|
|
295
|
+
// Find PIDs listening on this port via lsof
|
|
296
|
+
const { execSync } = require('child_process');
|
|
297
|
+
const output = execSync(`lsof -i :${port} -sTCP:LISTEN -t 2>/dev/null`, {
|
|
298
|
+
encoding: 'utf-8',
|
|
299
|
+
timeout: 3000,
|
|
300
|
+
}).trim();
|
|
301
|
+
if (!output)
|
|
302
|
+
continue;
|
|
303
|
+
const pids = output.split('\n').map(Number).filter(Boolean);
|
|
304
|
+
for (const pid of pids) {
|
|
305
|
+
if (knownPids.has(pid))
|
|
306
|
+
continue; // Skip known-good servers
|
|
307
|
+
// Verify this is actually a figma-console-mcp process before killing
|
|
308
|
+
try {
|
|
309
|
+
const cmdline = execSync(`ps -p ${pid} -o command= 2>/dev/null`, {
|
|
310
|
+
encoding: 'utf-8',
|
|
311
|
+
timeout: 2000,
|
|
312
|
+
}).trim();
|
|
313
|
+
if (cmdline.includes('figma-console-mcp') || cmdline.includes('figma_console_mcp') || cmdline.includes('local.js')) {
|
|
314
|
+
logger.info({ port, pid, command: cmdline.substring(0, 120) }, 'Terminating orphaned MCP server (no port file, holding port)');
|
|
315
|
+
terminateProcess(pid);
|
|
316
|
+
cleaned++;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// Can't read process info — skip to be safe
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
// lsof failed for this port — skip
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (cleaned > 0) {
|
|
329
|
+
// Give terminated processes a moment to release their ports
|
|
330
|
+
try {
|
|
331
|
+
const { execSync } = require('child_process');
|
|
332
|
+
execSync('sleep 0.5', { timeout: 2000 });
|
|
333
|
+
}
|
|
334
|
+
catch { /* non-critical */ }
|
|
335
|
+
logger.info({ cleaned }, `Cleaned up ${cleaned} orphaned MCP server process(es)`);
|
|
336
|
+
}
|
|
337
|
+
return cleaned;
|
|
338
|
+
}
|
|
268
339
|
/**
|
|
269
340
|
* Register process exit handlers to clean up port advertisement file.
|
|
270
341
|
* Should be called once after the port is successfully bound.
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { WebSocketServer as WSServer, WebSocket } from 'ws';
|
|
16
16
|
import { EventEmitter } from 'events';
|
|
17
|
-
import {
|
|
17
|
+
import { createServer as createHttpServer } from 'http';
|
|
18
|
+
import { readFileSync, existsSync } from 'fs';
|
|
18
19
|
import { join } from 'path';
|
|
19
20
|
import { createChildLogger } from './logger.js';
|
|
20
21
|
// Read version from package.json
|
|
@@ -27,11 +28,37 @@ try {
|
|
|
27
28
|
catch {
|
|
28
29
|
// Non-critical — version will show as 0.0.0
|
|
29
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Load the full plugin UI HTML content that gets served to the bootloader.
|
|
33
|
+
* Falls back to a minimal error page if the file isn't found.
|
|
34
|
+
*/
|
|
35
|
+
function loadPluginUIContent() {
|
|
36
|
+
const candidates = [
|
|
37
|
+
// ESM runtime: dist/core/ → ../../figma-desktop-bridge/
|
|
38
|
+
typeof __dirname !== 'undefined'
|
|
39
|
+
? join(__dirname, '..', '..', 'figma-desktop-bridge', 'ui-full.html')
|
|
40
|
+
: join(process.cwd(), 'figma-desktop-bridge', 'ui-full.html'),
|
|
41
|
+
// Direct from project root
|
|
42
|
+
join(process.cwd(), 'figma-desktop-bridge', 'ui-full.html'),
|
|
43
|
+
];
|
|
44
|
+
for (const path of candidates) {
|
|
45
|
+
try {
|
|
46
|
+
if (existsSync(path)) {
|
|
47
|
+
return readFileSync(path, 'utf-8');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// try next candidate
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return '<html><body><p>Plugin UI not found. Please reinstall figma-console-mcp.</p></body></html>';
|
|
55
|
+
}
|
|
30
56
|
const logger = createChildLogger({ component: 'websocket-server' });
|
|
31
57
|
export class FigmaWebSocketServer extends EventEmitter {
|
|
32
58
|
constructor(options) {
|
|
33
59
|
super();
|
|
34
60
|
this.wss = null;
|
|
61
|
+
this.httpServer = null;
|
|
35
62
|
/** Named clients indexed by fileKey — each represents a connected Figma file */
|
|
36
63
|
this.clients = new Map();
|
|
37
64
|
/** Clients awaiting FILE_INFO identification, mapped to their pending timeout */
|
|
@@ -44,20 +71,74 @@ export class FigmaWebSocketServer extends EventEmitter {
|
|
|
44
71
|
this._startedAt = Date.now();
|
|
45
72
|
this.consoleBufferSize = 1000;
|
|
46
73
|
this.documentChangeBufferSize = 200;
|
|
74
|
+
/** Cached plugin UI HTML content — loaded once and served to bootloader requests */
|
|
75
|
+
this._pluginUIContent = null;
|
|
47
76
|
this.options = options;
|
|
48
77
|
this._startedAt = Date.now();
|
|
49
78
|
}
|
|
50
79
|
/**
|
|
51
|
-
*
|
|
80
|
+
* Handle HTTP requests on the same port as WebSocket.
|
|
81
|
+
* Serves plugin UI content for the bootloader and health checks.
|
|
82
|
+
*/
|
|
83
|
+
handleHttpRequest(req, res) {
|
|
84
|
+
// CORS headers for Figma plugin iframe (sandboxed, origin: null)
|
|
85
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
86
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
87
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
88
|
+
if (req.method === 'OPTIONS') {
|
|
89
|
+
res.writeHead(204);
|
|
90
|
+
res.end();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const url = req.url || '/';
|
|
94
|
+
// Plugin UI endpoint — bootloader redirects here
|
|
95
|
+
if (url === '/plugin/ui' || url === '/plugin/ui/') {
|
|
96
|
+
if (!this._pluginUIContent) {
|
|
97
|
+
this._pluginUIContent = loadPluginUIContent();
|
|
98
|
+
}
|
|
99
|
+
res.writeHead(200, {
|
|
100
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
101
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
102
|
+
});
|
|
103
|
+
res.end(this._pluginUIContent);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Health/version endpoint
|
|
107
|
+
if (url === '/health' || url === '/') {
|
|
108
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
109
|
+
res.end(JSON.stringify({
|
|
110
|
+
status: 'ok',
|
|
111
|
+
version: SERVER_VERSION,
|
|
112
|
+
clients: this.clients.size,
|
|
113
|
+
uptime: Math.floor((Date.now() - this._startedAt) / 1000),
|
|
114
|
+
}));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// 404 for anything else
|
|
118
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
119
|
+
res.end('Not Found');
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Start the HTTP + WebSocket server.
|
|
123
|
+
* HTTP serves the plugin UI content; WebSocket handles plugin communication.
|
|
52
124
|
*/
|
|
53
125
|
async start() {
|
|
54
126
|
if (this._isStarted)
|
|
55
127
|
return;
|
|
56
128
|
return new Promise((resolve, reject) => {
|
|
129
|
+
let rejected = false;
|
|
130
|
+
const rejectOnce = (error) => {
|
|
131
|
+
if (!rejected) {
|
|
132
|
+
rejected = true;
|
|
133
|
+
reject(error);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
57
136
|
try {
|
|
137
|
+
// Create HTTP server first — handles plugin UI requests
|
|
138
|
+
this.httpServer = createHttpServer((req, res) => this.handleHttpRequest(req, res));
|
|
139
|
+
// Attach WebSocket server to the HTTP server (shares the same port)
|
|
58
140
|
this.wss = new WSServer({
|
|
59
|
-
|
|
60
|
-
host: this.options.host || 'localhost',
|
|
141
|
+
server: this.httpServer,
|
|
61
142
|
maxPayload: 100 * 1024 * 1024, // 100MB — screenshots and large component data can be big
|
|
62
143
|
verifyClient: (info, callback) => {
|
|
63
144
|
// Mitigate Cross-Site WebSocket Hijacking (CSWSH):
|
|
@@ -76,18 +157,38 @@ export class FigmaWebSocketServer extends EventEmitter {
|
|
|
76
157
|
}
|
|
77
158
|
},
|
|
78
159
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
});
|
|
84
|
-
this.wss.on('error', (error) => {
|
|
160
|
+
// Error handler for startup failures (EADDRINUSE, etc.)
|
|
161
|
+
// Must be on BOTH httpServer and wss — the WSS re-emits HTTP server errors
|
|
162
|
+
// and throws if no listener is attached.
|
|
163
|
+
const onStartupError = (error) => {
|
|
85
164
|
if (!this._isStarted) {
|
|
86
|
-
|
|
165
|
+
try {
|
|
166
|
+
if (this.wss) {
|
|
167
|
+
this.wss.close();
|
|
168
|
+
this.wss = null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch { /* ignore */ }
|
|
172
|
+
try {
|
|
173
|
+
if (this.httpServer) {
|
|
174
|
+
this.httpServer.close();
|
|
175
|
+
this.httpServer = null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch { /* ignore */ }
|
|
179
|
+
rejectOnce(error);
|
|
87
180
|
}
|
|
88
181
|
else {
|
|
89
|
-
logger.error({ error }, 'WebSocket server error');
|
|
182
|
+
logger.error({ error }, 'HTTP/WebSocket server error');
|
|
90
183
|
}
|
|
184
|
+
};
|
|
185
|
+
this.httpServer.on('error', onStartupError);
|
|
186
|
+
this.wss.on('error', onStartupError);
|
|
187
|
+
// Start listening on the HTTP server (which also handles WS upgrades)
|
|
188
|
+
this.httpServer.listen(this.options.port, this.options.host || 'localhost', () => {
|
|
189
|
+
this._isStarted = true;
|
|
190
|
+
logger.info({ port: this.options.port, host: this.options.host || 'localhost' }, 'WebSocket bridge server started (with HTTP plugin UI endpoint)');
|
|
191
|
+
resolve();
|
|
91
192
|
});
|
|
92
193
|
this.wss.on('connection', (ws) => {
|
|
93
194
|
// Add to pending until FILE_INFO identifies the file
|
|
@@ -146,7 +247,7 @@ export class FigmaWebSocketServer extends EventEmitter {
|
|
|
146
247
|
});
|
|
147
248
|
}
|
|
148
249
|
catch (error) {
|
|
149
|
-
|
|
250
|
+
rejectOnce(error);
|
|
150
251
|
}
|
|
151
252
|
});
|
|
152
253
|
}
|
|
@@ -177,6 +278,22 @@ export class FigmaWebSocketServer extends EventEmitter {
|
|
|
177
278
|
}
|
|
178
279
|
return;
|
|
179
280
|
}
|
|
281
|
+
// Bootloader request: send the full plugin UI HTML
|
|
282
|
+
if (message.type === 'GET_PLUGIN_UI') {
|
|
283
|
+
if (!this._pluginUIContent) {
|
|
284
|
+
this._pluginUIContent = loadPluginUIContent();
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
ws.send(JSON.stringify({
|
|
288
|
+
type: 'PLUGIN_UI_CONTENT',
|
|
289
|
+
html: this._pluginUIContent,
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
// Non-critical — bootloader will show error
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
180
297
|
// Unsolicited data from plugin (FILE_INFO, events, forwarded data)
|
|
181
298
|
if (message.type) {
|
|
182
299
|
// FILE_INFO promotes pending clients to named clients
|
|
@@ -434,11 +551,19 @@ export class FigmaWebSocketServer extends EventEmitter {
|
|
|
434
551
|
* Returns the actual port — critical when using port 0 for OS-assigned ports.
|
|
435
552
|
*/
|
|
436
553
|
address() {
|
|
554
|
+
// Use the HTTP server's address (which is the actual listening socket)
|
|
555
|
+
if (this.httpServer) {
|
|
556
|
+
const addr = this.httpServer.address();
|
|
557
|
+
if (typeof addr === 'string' || !addr)
|
|
558
|
+
return null;
|
|
559
|
+
return addr;
|
|
560
|
+
}
|
|
561
|
+
// Fallback for backward compat
|
|
437
562
|
if (!this.wss)
|
|
438
563
|
return null;
|
|
439
564
|
const addr = this.wss.address();
|
|
440
565
|
if (typeof addr === 'string')
|
|
441
|
-
return null;
|
|
566
|
+
return null;
|
|
442
567
|
return addr;
|
|
443
568
|
}
|
|
444
569
|
// ============================================================================
|
|
@@ -632,15 +757,20 @@ export class FigmaWebSocketServer extends EventEmitter {
|
|
|
632
757
|
}
|
|
633
758
|
this.clients.clear();
|
|
634
759
|
this._activeFileKey = null;
|
|
760
|
+
// Close WS server first (handles WebSocket connections)
|
|
635
761
|
if (this.wss) {
|
|
636
|
-
|
|
637
|
-
this.wss.close(() =>
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
762
|
+
await new Promise((resolve) => {
|
|
763
|
+
this.wss.close(() => resolve());
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
// Then close HTTP server (releases the port)
|
|
767
|
+
if (this.httpServer) {
|
|
768
|
+
await new Promise((resolve) => {
|
|
769
|
+
this.httpServer.close(() => resolve());
|
|
642
770
|
});
|
|
771
|
+
this.httpServer = null;
|
|
643
772
|
}
|
|
644
773
|
this._isStarted = false;
|
|
774
|
+
logger.info('WebSocket bridge server stopped');
|
|
645
775
|
}
|
|
646
776
|
}
|
|
@@ -17,7 +17,15 @@ export function registerWriteTools(server, getDesktopConnector) {
|
|
|
17
17
|
|
|
18
18
|
**VALIDATION:** After creating/modifying visuals: screenshot with figma_capture_screenshot, check alignment/spacing/proportions, iterate up to 3x.
|
|
19
19
|
|
|
20
|
-
**PLACEMENT:** Always create components inside a Section or Frame, never on blank canvas. Use parent.insertChild(0, bg) for z-ordering backgrounds behind content
|
|
20
|
+
**PLACEMENT:** Always create components inside a Section or Frame, never on blank canvas. Use parent.insertChild(0, bg) for z-ordering backgrounds behind content.
|
|
21
|
+
|
|
22
|
+
**HOUSEKEEPING (MANDATORY):**
|
|
23
|
+
Before creating: screenshot the target page to see existing content and find clear space.
|
|
24
|
+
When creating: place inside a named Section, positioned BELOW or AWAY from existing content. Never overlap.
|
|
25
|
+
After creating: screenshot to verify clean placement and no overlaps.
|
|
26
|
+
On failure/retry: DELETE any partial artifacts (empty frames, orphaned layers, blank pages) before retrying. Use node.remove() to clean up.
|
|
27
|
+
Pages: NEVER create a new page if one with that name already exists — use the existing one. If you created a blank page during a failed attempt, delete it.
|
|
28
|
+
Layers: If your code creates helper frames, placeholder nodes, or intermediate layers that aren't part of the final result, remove them.`, {
|
|
21
29
|
code: z
|
|
22
30
|
.string()
|
|
23
31
|
.describe("JavaScript code to execute. Has access to the 'figma' global object. " +
|
|
@@ -1476,7 +1484,7 @@ After instantiating components, use figma_take_screenshot to verify the result l
|
|
|
1476
1484
|
}
|
|
1477
1485
|
});
|
|
1478
1486
|
// Tool: Create Child Node
|
|
1479
|
-
server.tool("figma_create_child", "Create a new child node inside a parent container.
|
|
1487
|
+
server.tool("figma_create_child", "Create a new child node inside a parent container. Always place inside an existing Section or Frame — never on a bare page. If no suitable parent exists, create a Section first. Clean up any empty or orphaned nodes if the operation fails.", {
|
|
1480
1488
|
parentId: z.string().describe("The parent node ID"),
|
|
1481
1489
|
nodeType: z
|
|
1482
1490
|
.enum(["RECTANGLE", "ELLIPSE", "FRAME", "TEXT", "LINE"])
|
package/dist/cloudflare/index.js
CHANGED
|
@@ -38,7 +38,7 @@ export class FigmaConsoleMCPv3 extends McpAgent {
|
|
|
38
38
|
super(...arguments);
|
|
39
39
|
this.server = new McpServer({
|
|
40
40
|
name: "Figma Console MCP",
|
|
41
|
-
version: "1.
|
|
41
|
+
version: "1.15.5",
|
|
42
42
|
});
|
|
43
43
|
this.browserManager = null;
|
|
44
44
|
this.consoleMonitor = null;
|
|
@@ -950,7 +950,7 @@ export default {
|
|
|
950
950
|
});
|
|
951
951
|
const statelessServer = new McpServer({
|
|
952
952
|
name: "Figma Console MCP",
|
|
953
|
-
version: "1.
|
|
953
|
+
version: "1.15.5",
|
|
954
954
|
});
|
|
955
955
|
// ================================================================
|
|
956
956
|
// Cloud Write Relay — Pairing Tool (stateless /mcp path)
|
|
@@ -1579,7 +1579,7 @@ export default {
|
|
|
1579
1579
|
return new Response(JSON.stringify({
|
|
1580
1580
|
status: "healthy",
|
|
1581
1581
|
service: "Figma Console MCP",
|
|
1582
|
-
version: "1.
|
|
1582
|
+
version: "1.15.5",
|
|
1583
1583
|
endpoints: {
|
|
1584
1584
|
mcp: ["/sse", "/mcp"],
|
|
1585
1585
|
oauth_mcp_spec: ["/.well-known/oauth-authorization-server", "/authorize", "/token", "/oauth/register"],
|
|
@@ -1625,13 +1625,13 @@ export default {
|
|
|
1625
1625
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1626
1626
|
<title>Figma Console MCP - The Most Comprehensive MCP Server for Figma</title>
|
|
1627
1627
|
<link rel="icon" type="image/svg+xml" href="https://docs.figma-console-mcp.southleft.com/favicon.svg">
|
|
1628
|
-
<meta name="description" content="Turn your Figma design system into a living API.
|
|
1628
|
+
<meta name="description" content="Turn your Figma design system into a living API. 63+ tools give AI assistants deep access to design tokens, component specs, variables, and programmatic design creation.">
|
|
1629
1629
|
|
|
1630
1630
|
<!-- Open Graph -->
|
|
1631
1631
|
<meta property="og:type" content="website">
|
|
1632
1632
|
<meta property="og:url" content="https://figma-console-mcp.southleft.com">
|
|
1633
1633
|
<meta property="og:title" content="Figma Console MCP - Turn Your Design System Into a Living API">
|
|
1634
|
-
<meta property="og:description" content="The most comprehensive MCP server for Figma.
|
|
1634
|
+
<meta property="og:description" content="The most comprehensive MCP server for Figma. 63+ tools give AI assistants deep access to design tokens, components, variables, and programmatic design creation.">
|
|
1635
1635
|
<meta property="og:image" content="https://docs.figma-console-mcp.southleft.com/images/og-image.jpg">
|
|
1636
1636
|
<meta property="og:image:width" content="1200">
|
|
1637
1637
|
<meta property="og:image:height" content="630">
|
|
@@ -1639,7 +1639,7 @@ export default {
|
|
|
1639
1639
|
<!-- Twitter -->
|
|
1640
1640
|
<meta name="twitter:card" content="summary_large_image">
|
|
1641
1641
|
<meta name="twitter:title" content="Figma Console MCP - Turn Your Design System Into a Living API">
|
|
1642
|
-
<meta name="twitter:description" content="The most comprehensive MCP server for Figma.
|
|
1642
|
+
<meta name="twitter:description" content="The most comprehensive MCP server for Figma. 63+ tools give AI assistants deep access to design tokens, components, variables, and programmatic design creation.">
|
|
1643
1643
|
<meta name="twitter:image" content="https://docs.figma-console-mcp.southleft.com/images/og-image.jpg">
|
|
1644
1644
|
|
|
1645
1645
|
<meta name="theme-color" content="#0D9488">
|
package/dist/local.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../src/local.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;GAYG;AAiFH;;;GAGG;AACH,cAAM,oBAAoB;IACzB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,cAAc,CAA+C;IACrE,uGAAuG;IACvG,OAAO,CAAC,YAAY,CAAuB;IAC3C,6DAA6D;IAC7D,OAAO,CAAC,eAAe,CAA2B;IAClD,8EAA8E;IAC9E,OAAO,CAAC,gBAAgB,CAA+C;IACvE,OAAO,CAAC,MAAM,CAAe;IAI7B,OAAO,CAAC,cAAc,CAMR;;IA2Ed;;OAEG;YACW,WAAW;IA0BzB;;;OAGG;YACW,mBAAmB;IA6CjC;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAiBzB;;OAEG;YACW,iBAAiB;IAqB/B,wDAAwD;IACxD,OAAO,CAAC,gBAAgB,CAAuB;IAE/C;;;OAGG;IACH,OAAO,CAAC,aAAa;IAgBrB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAuB1B;;OAEG;YACW,iBAAiB;IA+I/B;;OAEG;IACH,OAAO,CAAC,aAAa;
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../src/local.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;GAYG;AAiFH;;;GAGG;AACH,cAAM,oBAAoB;IACzB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,cAAc,CAA+C;IACrE,uGAAuG;IACvG,OAAO,CAAC,YAAY,CAAuB;IAC3C,6DAA6D;IAC7D,OAAO,CAAC,eAAe,CAA2B;IAClD,8EAA8E;IAC9E,OAAO,CAAC,gBAAgB,CAA+C;IACvE,OAAO,CAAC,MAAM,CAAe;IAI7B,OAAO,CAAC,cAAc,CAMR;;IA2Ed;;OAEG;YACW,WAAW;IA0BzB;;;OAGG;YACW,mBAAmB;IA6CjC;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAiBzB;;OAEG;YACW,iBAAiB;IAqB/B,wDAAwD;IACxD,OAAO,CAAC,gBAAgB,CAAuB;IAE/C;;;OAGG;IACH,OAAO,CAAC,aAAa;IAgBrB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAuB1B;;OAEG;YACW,iBAAiB;IA+I/B;;OAEG;IACH,OAAO,CAAC,aAAa;IAk/KrB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgK5B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAgC/B;AA4ED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
|
package/dist/local.js
CHANGED
|
@@ -1122,7 +1122,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1122
1122
|
: this.wsStartupError?.code === "EADDRINUSE"
|
|
1123
1123
|
? `❌ All WebSocket ports ${this.wsPreferredPort}-${this.wsPreferredPort + 9} are in use`
|
|
1124
1124
|
: this.wsActualPort !== null && this.wsActualPort !== this.wsPreferredPort
|
|
1125
|
-
? `❌ WebSocket server running on port ${this.wsActualPort} (fallback) but no plugin connected.
|
|
1125
|
+
? `❌ WebSocket server running on port ${this.wsActualPort} (fallback) but no plugin connected. Restart the Desktop Bridge plugin in Figma to reconnect.`
|
|
1126
1126
|
: "❌ No connection to Figma Desktop",
|
|
1127
1127
|
setupInstructions: !setupValid
|
|
1128
1128
|
? this.wsStartupError?.code === "EADDRINUSE"
|
|
@@ -1138,7 +1138,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
|
|
|
1138
1138
|
? this.wsStartupError?.code === "EADDRINUSE"
|
|
1139
1139
|
? `All WebSocket ports in range ${this.wsPreferredPort}-${this.wsPreferredPort + 9} are in use — most likely multiple Claude Desktop tabs or terminal sessions are running the Figma Console MCP server. Ask the user to close some sessions and restart.`
|
|
1140
1140
|
: this.wsActualPort !== null && this.wsActualPort !== this.wsPreferredPort
|
|
1141
|
-
? `Server is running on fallback port ${this.wsActualPort} (port ${this.wsPreferredPort} was taken by another instance). The Desktop Bridge plugin is not connected
|
|
1141
|
+
? `Server is running on fallback port ${this.wsActualPort} (port ${this.wsPreferredPort} was taken by another instance). The Desktop Bridge plugin is not connected. TELL THE USER: Close and reopen the Desktop Bridge plugin in Figma to reconnect. The plugin's bootloader will automatically scan all ports in the range.`
|
|
1142
1142
|
: `No connection to Figma Desktop. Open the Desktop Bridge plugin in Figma to connect.${this.getPluginPath() ? ' Plugin manifest: ' + this.getPluginPath() : ''}`
|
|
1143
1143
|
: activeTransport === "websocket"
|
|
1144
1144
|
? `Connected via WebSocket Bridge to "${currentFileName || "unknown file"}" on port ${this.wsActualPort}. All design tools and console monitoring tools are available. Console logs are captured from the plugin sandbox (code.js). IMPORTANT: Always verify the file name before destructive operations when multiple files have the plugin open.`
|
|
@@ -3114,6 +3114,11 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
|
|
|
3114
3114
|
**For local components:** Pass BOTH componentKey AND nodeId together. Most local/unpublished components require nodeId.
|
|
3115
3115
|
**For library components:** Pass just the componentKey from figma_get_library_components or figma_search_components (with libraryFileKey). The component will be imported from the published library automatically.
|
|
3116
3116
|
|
|
3117
|
+
**CRITICAL: Use VARIANT keys, not COMPONENT_SET keys!**
|
|
3118
|
+
When importing from a published library, use the key of a specific variant (type: "COMPONENT"), NOT the parent component set key (type: "COMPONENT_SET"). Component set keys will fail with importComponentByKeyAsync. In figma_get_library_components results, look inside the "variants" array for individual variant keys.
|
|
3119
|
+
|
|
3120
|
+
**Font loading:** Library components may use fonts not loaded in the current file. If instantiation fails with a font error, load the required fonts first via figma_execute before retrying (e.g., \`await figma.loadFontAsync({ family: "Geist", style: "Regular" })\`).
|
|
3121
|
+
|
|
3117
3122
|
**IMPORTANT: Always re-search before instantiating!**
|
|
3118
3123
|
NodeIds are session-specific and may be stale from previous conversations. ALWAYS search for components at the start of each design session to get current, valid identifiers.
|
|
3119
3124
|
|
|
@@ -3202,8 +3207,12 @@ After instantiating components, use figma_take_screenshot to verify the result l
|
|
|
3202
3207
|
|
|
3203
3208
|
**WORKFLOW:**
|
|
3204
3209
|
1. Call this tool with the library file's URL or file key
|
|
3205
|
-
2. Browse the returned components (with
|
|
3206
|
-
3. Use figma_instantiate_component with the
|
|
3210
|
+
2. Browse the returned components — results include COMPONENT_SET (with variants array) and standalone COMPONENT types
|
|
3211
|
+
3. Use figma_instantiate_component with a VARIANT key (from the variants array inside a COMPONENT_SET result, NOT the component set key itself)
|
|
3212
|
+
|
|
3213
|
+
**SEARCH NOTE:** The query filter matches both component names AND descriptions. If you get unexpected results (e.g., "Accordion" when searching "Button"), verify the result name matches what you need — it may have matched on a description mention.
|
|
3214
|
+
|
|
3215
|
+
**MULTI-FILE TIP:** If you need to find a specific component and REST API search returns too many results, you can switch to the library file via figma_navigate, use figma_execute to find the exact component and its variant key, then switch back.
|
|
3207
3216
|
|
|
3208
3217
|
**NOTE:** Requires FIGMA_ACCESS_TOKEN to be set (uses the Figma REST API to read the library file).`, {
|
|
3209
3218
|
libraryFileUrl: z
|
|
@@ -3272,13 +3281,19 @@ After instantiating components, use figma_take_screenshot to verify the result l
|
|
|
3272
3281
|
// Use REST API to get published components from the library file
|
|
3273
3282
|
const api = await this.getFigmaAPI();
|
|
3274
3283
|
// Fetch both components and component sets in parallel
|
|
3284
|
+
// Surface errors instead of swallowing them — token/scope issues need to be visible
|
|
3285
|
+
const apiErrors = [];
|
|
3275
3286
|
const [componentsResponse, componentSetsResponse] = await Promise.all([
|
|
3276
3287
|
api.getComponents(fileKey).catch((err) => {
|
|
3288
|
+
const msg = err.message || String(err);
|
|
3277
3289
|
logger.warn({ error: err }, "Failed to fetch components from library");
|
|
3290
|
+
apiErrors.push(`Components API: ${msg}`);
|
|
3278
3291
|
return { meta: { components: [] } };
|
|
3279
3292
|
}),
|
|
3280
3293
|
api.getComponentSets(fileKey).catch((err) => {
|
|
3294
|
+
const msg = err.message || String(err);
|
|
3281
3295
|
logger.warn({ error: err }, "Failed to fetch component sets from library");
|
|
3296
|
+
apiErrors.push(`Component Sets API: ${msg}`);
|
|
3282
3297
|
return { meta: { component_sets: [] } };
|
|
3283
3298
|
}),
|
|
3284
3299
|
]);
|
|
@@ -3374,9 +3389,13 @@ After instantiating components, use figma_take_screenshot to verify the result l
|
|
|
3374
3389
|
{
|
|
3375
3390
|
type: "text",
|
|
3376
3391
|
text: JSON.stringify({
|
|
3377
|
-
success:
|
|
3392
|
+
success: apiErrors.length === 0,
|
|
3378
3393
|
libraryFileKey: fileKey,
|
|
3379
3394
|
query: query || "(all)",
|
|
3395
|
+
...(apiErrors.length > 0 && {
|
|
3396
|
+
apiErrors,
|
|
3397
|
+
hint: "REST API errors occurred. Check that FIGMA_ACCESS_TOKEN is valid and has file_content:read scope. If the token is correct, the library components may not be published to a team library.",
|
|
3398
|
+
}),
|
|
3380
3399
|
summary: {
|
|
3381
3400
|
totalComponentSets: componentSets.length,
|
|
3382
3401
|
totalStandaloneComponents: standaloneComponents.length,
|
|
@@ -3390,11 +3409,17 @@ After instantiating components, use figma_take_screenshot to verify the result l
|
|
|
3390
3409
|
hasMore,
|
|
3391
3410
|
},
|
|
3392
3411
|
usage: {
|
|
3393
|
-
instantiate: `To use a component: call figma_instantiate_component with the
|
|
3394
|
-
example:
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3412
|
+
instantiate: `To use a component: call figma_instantiate_component with a VARIANT key (not the component set key). For COMPONENT_SET results, pick a variant from the "variants" array.`,
|
|
3413
|
+
example: (() => {
|
|
3414
|
+
const first = paginatedResults[0];
|
|
3415
|
+
if (!first)
|
|
3416
|
+
return undefined;
|
|
3417
|
+
if (first.type === "COMPONENT_SET" && first.variants?.length > 0) {
|
|
3418
|
+
return `figma_instantiate_component({ componentKey: "${first.variants[0].key}" }) — using first variant of "${first.name}"`;
|
|
3419
|
+
}
|
|
3420
|
+
return `figma_instantiate_component({ componentKey: "${first.key}" })`;
|
|
3421
|
+
})(),
|
|
3422
|
+
note: "IMPORTANT: Use variant keys (type COMPONENT), not component set keys (type COMPONENT_SET). Component set keys will fail. Also pre-load any custom fonts the component uses via figma_execute before instantiating.",
|
|
3398
3423
|
},
|
|
3399
3424
|
}),
|
|
3400
3425
|
},
|