figma-console-mcp 1.9.1 β 1.10.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 +70 -19
- package/dist/cloudflare/core/port-discovery.js +191 -0
- package/dist/cloudflare/core/websocket-server.js +13 -0
- package/dist/core/port-discovery.d.ts +72 -0
- package/dist/core/port-discovery.d.ts.map +1 -0
- package/dist/core/port-discovery.js +192 -0
- package/dist/core/port-discovery.js.map +1 -0
- package/dist/core/websocket-server.d.ts +6 -0
- package/dist/core/websocket-server.d.ts.map +1 -1
- package/dist/core/websocket-server.js +13 -0
- package/dist/core/websocket-server.js.map +1 -1
- package/dist/local.d.ts +9 -0
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +115 -34
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/manifest.json +29 -3
- package/figma-desktop-bridge/ui.html +252 -59
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
> **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**.
|
|
9
9
|
|
|
10
|
+
> **π v1.10.0 β Multi-Instance Support:** Run Figma Console MCP in multiple Claude Desktop tabs, CLI terminals, or projects simultaneously. No more port conflicts. [See what's new β](#-multi-instance-support-v1100)
|
|
11
|
+
|
|
10
12
|
## What is this?
|
|
11
13
|
|
|
12
14
|
Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
|
|
@@ -71,12 +73,12 @@ Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
|
|
|
71
73
|
|
|
72
74
|
**Claude Code (CLI):**
|
|
73
75
|
```bash
|
|
74
|
-
claude mcp add figma-console -s user -e FIGMA_ACCESS_TOKEN=figd_YOUR_TOKEN_HERE -- npx -y figma-console-mcp@latest
|
|
76
|
+
claude mcp add figma-console -s user -e FIGMA_ACCESS_TOKEN=figd_YOUR_TOKEN_HERE -e ENABLE_MCP_APPS=true -- npx -y figma-console-mcp@latest
|
|
75
77
|
```
|
|
76
78
|
|
|
77
79
|
**Cursor / Windsurf / Claude Desktop:**
|
|
78
80
|
|
|
79
|
-
Add to your MCP config file:
|
|
81
|
+
Add to your MCP config file (see [Where to find your config file](#-where-to-find-your-config-file) below):
|
|
80
82
|
|
|
81
83
|
```json
|
|
82
84
|
{
|
|
@@ -85,13 +87,31 @@ Add to your MCP config file:
|
|
|
85
87
|
"command": "npx",
|
|
86
88
|
"args": ["-y", "figma-console-mcp@latest"],
|
|
87
89
|
"env": {
|
|
88
|
-
"FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE"
|
|
90
|
+
"FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE",
|
|
91
|
+
"ENABLE_MCP_APPS": "true"
|
|
89
92
|
}
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
```
|
|
94
97
|
|
|
98
|
+
#### π Where to Find Your Config File
|
|
99
|
+
|
|
100
|
+
If you're not sure where to put the JSON configuration above, here's where each app stores its MCP config:
|
|
101
|
+
|
|
102
|
+
| App | macOS | Windows |
|
|
103
|
+
|-----|-------|---------|
|
|
104
|
+
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` | `%APPDATA%\Claude\claude_desktop_config.json` |
|
|
105
|
+
| **Claude Code (CLI)** | `~/.claude.json` | `%USERPROFILE%\.claude.json` |
|
|
106
|
+
| **Cursor** | `~/.cursor/mcp.json` | `%USERPROFILE%\.cursor\mcp.json` |
|
|
107
|
+
| **Windsurf** | `~/.codeium/windsurf/mcp_config.json` | `%USERPROFILE%\.codeium\windsurf\mcp_config.json` |
|
|
108
|
+
|
|
109
|
+
> **Tip for designers:** The `~` symbol means your **home folder**. On macOS, that's `/Users/YourName/`. On Windows, it's `C:\Users\YourName\`. You can open these files in any text editor β even TextEdit or Notepad.
|
|
110
|
+
>
|
|
111
|
+
> **Can't find the file?** If it doesn't exist yet, create it. The app will pick it up on its next restart. Make sure the entire file is valid JSON (watch for missing commas or brackets).
|
|
112
|
+
>
|
|
113
|
+
> **Claude Code users:** You can skip manual editing entirely. Just run the `claude mcp add` command above and it handles everything for you.
|
|
114
|
+
|
|
95
115
|
#### Step 3: Connect to Figma Desktop
|
|
96
116
|
|
|
97
117
|
**Option A β Desktop Bridge Plugin (Recommended):**
|
|
@@ -148,6 +168,8 @@ npm run build:local
|
|
|
148
168
|
|
|
149
169
|
#### Configure Your MCP Client
|
|
150
170
|
|
|
171
|
+
Add to your config file (see [Where to find your config file](#-where-to-find-your-config-file)):
|
|
172
|
+
|
|
151
173
|
```json
|
|
152
174
|
{
|
|
153
175
|
"mcpServers": {
|
|
@@ -155,7 +177,8 @@ npm run build:local
|
|
|
155
177
|
"command": "node",
|
|
156
178
|
"args": ["/absolute/path/to/figma-console-mcp/dist/local.js"],
|
|
157
179
|
"env": {
|
|
158
|
-
"FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE"
|
|
180
|
+
"FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE",
|
|
181
|
+
"ENABLE_MCP_APPS": "true"
|
|
159
182
|
}
|
|
160
183
|
}
|
|
161
184
|
}
|
|
@@ -476,7 +499,7 @@ The **Figma Desktop Bridge** plugin is the recommended way to connect Figma to t
|
|
|
476
499
|
1. Open Figma Desktop (normal launch β no debug flags needed)
|
|
477
500
|
2. Go to **Plugins β Development β Import plugin from manifest...**
|
|
478
501
|
3. Select `figma-desktop-bridge/manifest.json` from the figma-console-mcp directory
|
|
479
|
-
4. Run the plugin in your Figma file β it auto-connects
|
|
502
|
+
4. Run the plugin in your Figma file β it auto-connects via WebSocket (scans ports 9223β9232)
|
|
480
503
|
5. Ask your AI: "Check Figma status" to verify the connection
|
|
481
504
|
|
|
482
505
|
> **One-time import.** Once imported, the plugin stays in your Development plugins list. Just run it whenever you want to use the MCP.
|
|
@@ -508,13 +531,47 @@ The **Figma Desktop Bridge** plugin is the recommended way to connect Figma to t
|
|
|
508
531
|
**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).
|
|
509
532
|
|
|
510
533
|
**Environment variables:**
|
|
511
|
-
- `FIGMA_WS_PORT` β Override the
|
|
534
|
+
- `FIGMA_WS_PORT` β Override the preferred WebSocket port (default: 9223). The server will fall back through a 10-port range starting from this value if the preferred port is occupied.
|
|
512
535
|
- `FIGMA_WS_HOST` β Override the WebSocket server bind address (default: `localhost`). Set to `0.0.0.0` when running inside Docker so the host machine can reach the MCP server.
|
|
513
536
|
|
|
514
537
|
**Plugin Limitation:** Only works in Local Mode (NPX or Local Git). Remote SSE mode cannot access it.
|
|
515
538
|
|
|
516
539
|
---
|
|
517
540
|
|
|
541
|
+
## π Multi-Instance Support (v1.10.0)
|
|
542
|
+
|
|
543
|
+
Figma Console MCP now supports **multiple simultaneous instances** β perfect for designers and developers who work across multiple projects or use Claude Desktop's Chat and Code tabs at the same time.
|
|
544
|
+
|
|
545
|
+
### The Problem (Before v1.10.0)
|
|
546
|
+
|
|
547
|
+
When two processes tried to start the MCP server (e.g., Claude Desktop's Chat tab and Code tab), the second one would crash with `EADDRINUSE` because both competed for port 9223.
|
|
548
|
+
|
|
549
|
+
### How It Works Now
|
|
550
|
+
|
|
551
|
+
- The server tries port **9223** first (the default)
|
|
552
|
+
- If that port is already taken, it automatically tries **9224**, then **9225**, and so on up to **9232**
|
|
553
|
+
- The Desktop Bridge plugin in Figma connects to **all** active servers simultaneously
|
|
554
|
+
- Every server instance receives real-time events (selection changes, document changes, console logs)
|
|
555
|
+
- `figma_get_status` shows which port you're on and lists other active instances
|
|
556
|
+
|
|
557
|
+
### What This Means for You
|
|
558
|
+
|
|
559
|
+
| Scenario | Before v1.10.0 | Now |
|
|
560
|
+
|----------|----------------|-----|
|
|
561
|
+
| Two Claude Desktop tabs (Chat + Code) | Second tab crashes | Both work independently |
|
|
562
|
+
| Multiple CLI terminals on different projects | Only one can run | All run simultaneously |
|
|
563
|
+
| Claude Desktop + Claude Code CLI | Port conflict | Both coexist |
|
|
564
|
+
|
|
565
|
+
### Do I Need to Do Anything?
|
|
566
|
+
|
|
567
|
+
**If you're running a single instance:** Nothing changes. You'll still use port 9223 as before.
|
|
568
|
+
|
|
569
|
+
**If you want multi-instance:** Re-import the Desktop Bridge plugin in Figma (Plugins β Development β Import plugin from manifest). This updates the plugin to scan all ports and connect to every active server.
|
|
570
|
+
|
|
571
|
+
> **Note:** The server-side update happens automatically when you update the npm package. Only the plugin needs a one-time re-import to enable multi-connection support.
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
518
575
|
## π§© MCP Apps (Experimental)
|
|
519
576
|
|
|
520
577
|
Figma Console MCP includes support for **MCP Apps** β rich interactive UI experiences that render directly inside any MCP client that supports the [MCP Apps protocol extension](https://github.com/anthropics/anthropic-cookbook/tree/main/misc/model_context_protocol/ext-apps). Built with the official [`@modelcontextprotocol/ext-apps`](https://www.npmjs.com/package/@modelcontextprotocol/ext-apps) SDK.
|
|
@@ -550,20 +607,12 @@ A Lighthouse-style health scorecard that audits your design system across six ca
|
|
|
550
607
|
|
|
551
608
|
**Enabling MCP Apps:**
|
|
552
609
|
|
|
553
|
-
MCP Apps are
|
|
610
|
+
MCP Apps are enabled by default in the setup configurations above (via `"ENABLE_MCP_APPS": "true"`). If you set up before v1.10.0 and don't have this in your config, add it to your `env` section:
|
|
554
611
|
|
|
555
612
|
```json
|
|
556
|
-
{
|
|
557
|
-
"
|
|
558
|
-
|
|
559
|
-
"command": "node",
|
|
560
|
-
"args": ["/path/to/figma-console-mcp/dist/local.js"],
|
|
561
|
-
"env": {
|
|
562
|
-
"FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE",
|
|
563
|
-
"ENABLE_MCP_APPS": "true"
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
613
|
+
"env": {
|
|
614
|
+
"FIGMA_ACCESS_TOKEN": "figd_YOUR_TOKEN_HERE",
|
|
615
|
+
"ENABLE_MCP_APPS": "true"
|
|
567
616
|
}
|
|
568
617
|
```
|
|
569
618
|
|
|
@@ -611,9 +660,11 @@ The architecture supports adding new apps with minimal boilerplate β each app
|
|
|
611
660
|
|
|
612
661
|
## π€οΈ Roadmap
|
|
613
662
|
|
|
614
|
-
**Current Status:** v1.
|
|
663
|
+
**Current Status:** v1.10.0 (Stable) - Production-ready with multi-instance support, WebSocket Bridge, 56+ tools, Comments API, and MCP Apps
|
|
615
664
|
|
|
616
665
|
**Recent Releases:**
|
|
666
|
+
- [x] **v1.10.0** - Multi-instance support (dynamic port fallback 9223β9232, multi-connection plugin, instance discovery)
|
|
667
|
+
- [x] **v1.9.0** - Figma Comments tools, improved port conflict detection
|
|
617
668
|
- [x] **v1.8.0** - WebSocket Bridge transport (CDP-free connectivity), real-time selection/document tracking, `figma_get_selection` + `figma_get_design_changes` tools
|
|
618
669
|
- [x] **v1.7.0** - MCP Apps (Token Browser, Design System Dashboard), batch variable operations, design-code parity tools
|
|
619
670
|
- [x] **v1.5.0** - Node manipulation tools, component property management, component set arrangement
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port Discovery Module
|
|
3
|
+
*
|
|
4
|
+
* Handles dynamic WebSocket port assignment with range-based fallback.
|
|
5
|
+
* When the preferred port (default 9223) is taken by another MCP server instance
|
|
6
|
+
* (e.g., Claude Desktop Chat tab vs Code tab), the server automatically tries
|
|
7
|
+
* the next port in a fixed range (9223-9232).
|
|
8
|
+
*
|
|
9
|
+
* Port advertisement files are written to /tmp so the Figma plugin can discover
|
|
10
|
+
* which port to connect to. Each instance writes its own file with PID for
|
|
11
|
+
* stale-file detection.
|
|
12
|
+
*
|
|
13
|
+
* Data flow:
|
|
14
|
+
* Server binds port β writes /tmp/figma-console-mcp-{port}.json
|
|
15
|
+
* Plugin scans ports 9223-9232 β connects to first responding server
|
|
16
|
+
* External tools read port files for discovery
|
|
17
|
+
*/
|
|
18
|
+
import { writeFileSync, readFileSync, unlinkSync, existsSync, readdirSync } from 'fs';
|
|
19
|
+
import { join } from 'path';
|
|
20
|
+
import { tmpdir } from 'os';
|
|
21
|
+
import { createChildLogger } from './logger.js';
|
|
22
|
+
const logger = createChildLogger({ component: 'port-discovery' });
|
|
23
|
+
/** Default preferred WebSocket port */
|
|
24
|
+
export const DEFAULT_WS_PORT = 9223;
|
|
25
|
+
/** Number of ports in the fallback range (9223-9232 = 10 ports) */
|
|
26
|
+
export const PORT_RANGE_SIZE = 10;
|
|
27
|
+
/** Prefix for port advertisement files in /tmp */
|
|
28
|
+
const PORT_FILE_PREFIX = 'figma-console-mcp-';
|
|
29
|
+
/** Directory for port advertisement files */
|
|
30
|
+
const PORT_FILE_DIR = tmpdir();
|
|
31
|
+
/**
|
|
32
|
+
* Try to bind a WebSocket server to ports in a range, starting from the preferred port.
|
|
33
|
+
* Returns the first port that binds successfully.
|
|
34
|
+
*
|
|
35
|
+
* @param preferredPort - The port to try first (default 9223)
|
|
36
|
+
* @param host - The host to bind to (default 'localhost')
|
|
37
|
+
* @returns The actual port that was bound
|
|
38
|
+
* @throws If all ports in the range are exhausted
|
|
39
|
+
*/
|
|
40
|
+
export function getPortRange(preferredPort = DEFAULT_WS_PORT) {
|
|
41
|
+
const ports = [];
|
|
42
|
+
for (let i = 0; i < PORT_RANGE_SIZE; i++) {
|
|
43
|
+
ports.push(preferredPort + i);
|
|
44
|
+
}
|
|
45
|
+
return ports;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the file path for a port advertisement file.
|
|
49
|
+
*/
|
|
50
|
+
export function getPortFilePath(port) {
|
|
51
|
+
return join(PORT_FILE_DIR, `${PORT_FILE_PREFIX}${port}.json`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Write a port advertisement file so clients can discover this server instance.
|
|
55
|
+
* Includes PID for stale-file detection.
|
|
56
|
+
*/
|
|
57
|
+
export function advertisePort(port, host = 'localhost') {
|
|
58
|
+
const data = {
|
|
59
|
+
port,
|
|
60
|
+
pid: process.pid,
|
|
61
|
+
host,
|
|
62
|
+
startedAt: new Date().toISOString(),
|
|
63
|
+
};
|
|
64
|
+
const filePath = getPortFilePath(port);
|
|
65
|
+
try {
|
|
66
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
67
|
+
logger.info({ port, filePath }, 'Port advertised');
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
logger.warn({ port, filePath, error }, 'Failed to write port advertisement file');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Remove the port advertisement file for this instance.
|
|
75
|
+
* Call on clean shutdown.
|
|
76
|
+
*/
|
|
77
|
+
export function unadvertisePort(port) {
|
|
78
|
+
const filePath = getPortFilePath(port);
|
|
79
|
+
try {
|
|
80
|
+
if (existsSync(filePath)) {
|
|
81
|
+
unlinkSync(filePath);
|
|
82
|
+
logger.debug({ port, filePath }, 'Port advertisement removed');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Best-effort cleanup β file may already be gone
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if a PID is still alive.
|
|
91
|
+
*/
|
|
92
|
+
function isProcessAlive(pid) {
|
|
93
|
+
try {
|
|
94
|
+
process.kill(pid, 0); // Signal 0 = existence check, doesn't actually kill
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Read and validate a port advertisement file.
|
|
103
|
+
* Returns null if the file doesn't exist, is invalid, or the owning process is dead.
|
|
104
|
+
*/
|
|
105
|
+
export function readPortFile(port) {
|
|
106
|
+
const filePath = getPortFilePath(port);
|
|
107
|
+
if (!existsSync(filePath))
|
|
108
|
+
return null;
|
|
109
|
+
try {
|
|
110
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
111
|
+
const data = JSON.parse(raw);
|
|
112
|
+
// Validate the owning process is still alive
|
|
113
|
+
if (!isProcessAlive(data.pid)) {
|
|
114
|
+
logger.debug({ port, pid: data.pid }, 'Stale port file detected (process dead), cleaning up');
|
|
115
|
+
try {
|
|
116
|
+
unlinkSync(filePath);
|
|
117
|
+
}
|
|
118
|
+
catch { /* best-effort */ }
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return data;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Discover all active Figma Console MCP server instances by scanning port files.
|
|
129
|
+
* Validates each file's PID to filter out stale entries.
|
|
130
|
+
*/
|
|
131
|
+
export function discoverActiveInstances(preferredPort = DEFAULT_WS_PORT) {
|
|
132
|
+
const instances = [];
|
|
133
|
+
for (const port of getPortRange(preferredPort)) {
|
|
134
|
+
const data = readPortFile(port);
|
|
135
|
+
if (data) {
|
|
136
|
+
instances.push(data);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return instances;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Clean up all stale port files (dead PIDs).
|
|
143
|
+
* Useful for maintenance and debugging.
|
|
144
|
+
*/
|
|
145
|
+
export function cleanupStalePortFiles() {
|
|
146
|
+
let cleaned = 0;
|
|
147
|
+
try {
|
|
148
|
+
const files = readdirSync(PORT_FILE_DIR);
|
|
149
|
+
for (const file of files) {
|
|
150
|
+
if (file.startsWith(PORT_FILE_PREFIX) && file.endsWith('.json')) {
|
|
151
|
+
const filePath = join(PORT_FILE_DIR, file);
|
|
152
|
+
try {
|
|
153
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
154
|
+
const data = JSON.parse(raw);
|
|
155
|
+
if (!isProcessAlive(data.pid)) {
|
|
156
|
+
unlinkSync(filePath);
|
|
157
|
+
cleaned++;
|
|
158
|
+
logger.debug({ port: data.port, pid: data.pid }, 'Cleaned up stale port file');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// Corrupt file β remove it
|
|
163
|
+
try {
|
|
164
|
+
unlinkSync(filePath);
|
|
165
|
+
cleaned++;
|
|
166
|
+
}
|
|
167
|
+
catch { /* ignore */ }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Can't read /tmp β unusual but not fatal
|
|
174
|
+
}
|
|
175
|
+
return cleaned;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Register process exit handlers to clean up port advertisement file.
|
|
179
|
+
* Should be called once after the port is successfully bound.
|
|
180
|
+
*/
|
|
181
|
+
export function registerPortCleanup(port) {
|
|
182
|
+
const cleanup = () => unadvertisePort(port);
|
|
183
|
+
process.on('exit', cleanup);
|
|
184
|
+
// Re-register SIGINT/SIGTERM to ensure cleanup runs before the
|
|
185
|
+
// existing handlers in local.ts main() call process.exit()
|
|
186
|
+
const originalSigintListeners = process.listeners('SIGINT');
|
|
187
|
+
const originalSigtermListeners = process.listeners('SIGTERM');
|
|
188
|
+
// Prepend our cleanup β it runs first, then existing handlers take over
|
|
189
|
+
process.prependListener('SIGINT', cleanup);
|
|
190
|
+
process.prependListener('SIGTERM', cleanup);
|
|
191
|
+
}
|
|
@@ -398,6 +398,19 @@ export class FigmaWebSocketServer extends EventEmitter {
|
|
|
398
398
|
isStarted() {
|
|
399
399
|
return this._isStarted;
|
|
400
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Get the bound address info (port, host, family).
|
|
403
|
+
* Only available after the server has started listening.
|
|
404
|
+
* Returns the actual port β critical when using port 0 for OS-assigned ports.
|
|
405
|
+
*/
|
|
406
|
+
address() {
|
|
407
|
+
if (!this.wss)
|
|
408
|
+
return null;
|
|
409
|
+
const addr = this.wss.address();
|
|
410
|
+
if (typeof addr === 'string')
|
|
411
|
+
return null; // Unix socket path, not applicable
|
|
412
|
+
return addr;
|
|
413
|
+
}
|
|
401
414
|
// ============================================================================
|
|
402
415
|
// Active file getters (backward compatible β return active file's state)
|
|
403
416
|
// ============================================================================
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port Discovery Module
|
|
3
|
+
*
|
|
4
|
+
* Handles dynamic WebSocket port assignment with range-based fallback.
|
|
5
|
+
* When the preferred port (default 9223) is taken by another MCP server instance
|
|
6
|
+
* (e.g., Claude Desktop Chat tab vs Code tab), the server automatically tries
|
|
7
|
+
* the next port in a fixed range (9223-9232).
|
|
8
|
+
*
|
|
9
|
+
* Port advertisement files are written to /tmp so the Figma plugin can discover
|
|
10
|
+
* which port to connect to. Each instance writes its own file with PID for
|
|
11
|
+
* stale-file detection.
|
|
12
|
+
*
|
|
13
|
+
* Data flow:
|
|
14
|
+
* Server binds port β writes /tmp/figma-console-mcp-{port}.json
|
|
15
|
+
* Plugin scans ports 9223-9232 β connects to first responding server
|
|
16
|
+
* External tools read port files for discovery
|
|
17
|
+
*/
|
|
18
|
+
/** Default preferred WebSocket port */
|
|
19
|
+
export declare const DEFAULT_WS_PORT = 9223;
|
|
20
|
+
/** Number of ports in the fallback range (9223-9232 = 10 ports) */
|
|
21
|
+
export declare const PORT_RANGE_SIZE = 10;
|
|
22
|
+
export interface PortFileData {
|
|
23
|
+
port: number;
|
|
24
|
+
pid: number;
|
|
25
|
+
host: string;
|
|
26
|
+
startedAt: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Try to bind a WebSocket server to ports in a range, starting from the preferred port.
|
|
30
|
+
* Returns the first port that binds successfully.
|
|
31
|
+
*
|
|
32
|
+
* @param preferredPort - The port to try first (default 9223)
|
|
33
|
+
* @param host - The host to bind to (default 'localhost')
|
|
34
|
+
* @returns The actual port that was bound
|
|
35
|
+
* @throws If all ports in the range are exhausted
|
|
36
|
+
*/
|
|
37
|
+
export declare function getPortRange(preferredPort?: number): number[];
|
|
38
|
+
/**
|
|
39
|
+
* Get the file path for a port advertisement file.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getPortFilePath(port: number): string;
|
|
42
|
+
/**
|
|
43
|
+
* Write a port advertisement file so clients can discover this server instance.
|
|
44
|
+
* Includes PID for stale-file detection.
|
|
45
|
+
*/
|
|
46
|
+
export declare function advertisePort(port: number, host?: string): void;
|
|
47
|
+
/**
|
|
48
|
+
* Remove the port advertisement file for this instance.
|
|
49
|
+
* Call on clean shutdown.
|
|
50
|
+
*/
|
|
51
|
+
export declare function unadvertisePort(port: number): void;
|
|
52
|
+
/**
|
|
53
|
+
* Read and validate a port advertisement file.
|
|
54
|
+
* Returns null if the file doesn't exist, is invalid, or the owning process is dead.
|
|
55
|
+
*/
|
|
56
|
+
export declare function readPortFile(port: number): PortFileData | null;
|
|
57
|
+
/**
|
|
58
|
+
* Discover all active Figma Console MCP server instances by scanning port files.
|
|
59
|
+
* Validates each file's PID to filter out stale entries.
|
|
60
|
+
*/
|
|
61
|
+
export declare function discoverActiveInstances(preferredPort?: number): PortFileData[];
|
|
62
|
+
/**
|
|
63
|
+
* Clean up all stale port files (dead PIDs).
|
|
64
|
+
* Useful for maintenance and debugging.
|
|
65
|
+
*/
|
|
66
|
+
export declare function cleanupStalePortFiles(): number;
|
|
67
|
+
/**
|
|
68
|
+
* Register process exit handlers to clean up port advertisement file.
|
|
69
|
+
* Should be called once after the port is successfully bound.
|
|
70
|
+
*/
|
|
71
|
+
export declare function registerPortCleanup(port: number): void;
|
|
72
|
+
//# sourceMappingURL=port-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port-discovery.d.ts","sourceRoot":"","sources":["../../src/core/port-discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AASH,uCAAuC;AACvC,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC,mEAAmE;AACnE,eAAO,MAAM,eAAe,KAAK,CAAC;AAQlC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,aAAa,GAAE,MAAwB,GAAG,MAAM,EAAE,CAM9E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAoB,GAAG,IAAI,CAe5E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAUlD;AAcD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAoB9D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,aAAa,GAAE,MAAwB,GAAG,YAAY,EAAE,CAW/F;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CA2B9C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAatD"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port Discovery Module
|
|
3
|
+
*
|
|
4
|
+
* Handles dynamic WebSocket port assignment with range-based fallback.
|
|
5
|
+
* When the preferred port (default 9223) is taken by another MCP server instance
|
|
6
|
+
* (e.g., Claude Desktop Chat tab vs Code tab), the server automatically tries
|
|
7
|
+
* the next port in a fixed range (9223-9232).
|
|
8
|
+
*
|
|
9
|
+
* Port advertisement files are written to /tmp so the Figma plugin can discover
|
|
10
|
+
* which port to connect to. Each instance writes its own file with PID for
|
|
11
|
+
* stale-file detection.
|
|
12
|
+
*
|
|
13
|
+
* Data flow:
|
|
14
|
+
* Server binds port β writes /tmp/figma-console-mcp-{port}.json
|
|
15
|
+
* Plugin scans ports 9223-9232 β connects to first responding server
|
|
16
|
+
* External tools read port files for discovery
|
|
17
|
+
*/
|
|
18
|
+
import { writeFileSync, readFileSync, unlinkSync, existsSync, readdirSync } from 'fs';
|
|
19
|
+
import { join } from 'path';
|
|
20
|
+
import { tmpdir } from 'os';
|
|
21
|
+
import { createChildLogger } from './logger.js';
|
|
22
|
+
const logger = createChildLogger({ component: 'port-discovery' });
|
|
23
|
+
/** Default preferred WebSocket port */
|
|
24
|
+
export const DEFAULT_WS_PORT = 9223;
|
|
25
|
+
/** Number of ports in the fallback range (9223-9232 = 10 ports) */
|
|
26
|
+
export const PORT_RANGE_SIZE = 10;
|
|
27
|
+
/** Prefix for port advertisement files in /tmp */
|
|
28
|
+
const PORT_FILE_PREFIX = 'figma-console-mcp-';
|
|
29
|
+
/** Directory for port advertisement files */
|
|
30
|
+
const PORT_FILE_DIR = tmpdir();
|
|
31
|
+
/**
|
|
32
|
+
* Try to bind a WebSocket server to ports in a range, starting from the preferred port.
|
|
33
|
+
* Returns the first port that binds successfully.
|
|
34
|
+
*
|
|
35
|
+
* @param preferredPort - The port to try first (default 9223)
|
|
36
|
+
* @param host - The host to bind to (default 'localhost')
|
|
37
|
+
* @returns The actual port that was bound
|
|
38
|
+
* @throws If all ports in the range are exhausted
|
|
39
|
+
*/
|
|
40
|
+
export function getPortRange(preferredPort = DEFAULT_WS_PORT) {
|
|
41
|
+
const ports = [];
|
|
42
|
+
for (let i = 0; i < PORT_RANGE_SIZE; i++) {
|
|
43
|
+
ports.push(preferredPort + i);
|
|
44
|
+
}
|
|
45
|
+
return ports;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the file path for a port advertisement file.
|
|
49
|
+
*/
|
|
50
|
+
export function getPortFilePath(port) {
|
|
51
|
+
return join(PORT_FILE_DIR, `${PORT_FILE_PREFIX}${port}.json`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Write a port advertisement file so clients can discover this server instance.
|
|
55
|
+
* Includes PID for stale-file detection.
|
|
56
|
+
*/
|
|
57
|
+
export function advertisePort(port, host = 'localhost') {
|
|
58
|
+
const data = {
|
|
59
|
+
port,
|
|
60
|
+
pid: process.pid,
|
|
61
|
+
host,
|
|
62
|
+
startedAt: new Date().toISOString(),
|
|
63
|
+
};
|
|
64
|
+
const filePath = getPortFilePath(port);
|
|
65
|
+
try {
|
|
66
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
67
|
+
logger.info({ port, filePath }, 'Port advertised');
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
logger.warn({ port, filePath, error }, 'Failed to write port advertisement file');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Remove the port advertisement file for this instance.
|
|
75
|
+
* Call on clean shutdown.
|
|
76
|
+
*/
|
|
77
|
+
export function unadvertisePort(port) {
|
|
78
|
+
const filePath = getPortFilePath(port);
|
|
79
|
+
try {
|
|
80
|
+
if (existsSync(filePath)) {
|
|
81
|
+
unlinkSync(filePath);
|
|
82
|
+
logger.debug({ port, filePath }, 'Port advertisement removed');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Best-effort cleanup β file may already be gone
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if a PID is still alive.
|
|
91
|
+
*/
|
|
92
|
+
function isProcessAlive(pid) {
|
|
93
|
+
try {
|
|
94
|
+
process.kill(pid, 0); // Signal 0 = existence check, doesn't actually kill
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Read and validate a port advertisement file.
|
|
103
|
+
* Returns null if the file doesn't exist, is invalid, or the owning process is dead.
|
|
104
|
+
*/
|
|
105
|
+
export function readPortFile(port) {
|
|
106
|
+
const filePath = getPortFilePath(port);
|
|
107
|
+
if (!existsSync(filePath))
|
|
108
|
+
return null;
|
|
109
|
+
try {
|
|
110
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
111
|
+
const data = JSON.parse(raw);
|
|
112
|
+
// Validate the owning process is still alive
|
|
113
|
+
if (!isProcessAlive(data.pid)) {
|
|
114
|
+
logger.debug({ port, pid: data.pid }, 'Stale port file detected (process dead), cleaning up');
|
|
115
|
+
try {
|
|
116
|
+
unlinkSync(filePath);
|
|
117
|
+
}
|
|
118
|
+
catch { /* best-effort */ }
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return data;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Discover all active Figma Console MCP server instances by scanning port files.
|
|
129
|
+
* Validates each file's PID to filter out stale entries.
|
|
130
|
+
*/
|
|
131
|
+
export function discoverActiveInstances(preferredPort = DEFAULT_WS_PORT) {
|
|
132
|
+
const instances = [];
|
|
133
|
+
for (const port of getPortRange(preferredPort)) {
|
|
134
|
+
const data = readPortFile(port);
|
|
135
|
+
if (data) {
|
|
136
|
+
instances.push(data);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return instances;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Clean up all stale port files (dead PIDs).
|
|
143
|
+
* Useful for maintenance and debugging.
|
|
144
|
+
*/
|
|
145
|
+
export function cleanupStalePortFiles() {
|
|
146
|
+
let cleaned = 0;
|
|
147
|
+
try {
|
|
148
|
+
const files = readdirSync(PORT_FILE_DIR);
|
|
149
|
+
for (const file of files) {
|
|
150
|
+
if (file.startsWith(PORT_FILE_PREFIX) && file.endsWith('.json')) {
|
|
151
|
+
const filePath = join(PORT_FILE_DIR, file);
|
|
152
|
+
try {
|
|
153
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
154
|
+
const data = JSON.parse(raw);
|
|
155
|
+
if (!isProcessAlive(data.pid)) {
|
|
156
|
+
unlinkSync(filePath);
|
|
157
|
+
cleaned++;
|
|
158
|
+
logger.debug({ port: data.port, pid: data.pid }, 'Cleaned up stale port file');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// Corrupt file β remove it
|
|
163
|
+
try {
|
|
164
|
+
unlinkSync(filePath);
|
|
165
|
+
cleaned++;
|
|
166
|
+
}
|
|
167
|
+
catch { /* ignore */ }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Can't read /tmp β unusual but not fatal
|
|
174
|
+
}
|
|
175
|
+
return cleaned;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Register process exit handlers to clean up port advertisement file.
|
|
179
|
+
* Should be called once after the port is successfully bound.
|
|
180
|
+
*/
|
|
181
|
+
export function registerPortCleanup(port) {
|
|
182
|
+
const cleanup = () => unadvertisePort(port);
|
|
183
|
+
process.on('exit', cleanup);
|
|
184
|
+
// Re-register SIGINT/SIGTERM to ensure cleanup runs before the
|
|
185
|
+
// existing handlers in local.ts main() call process.exit()
|
|
186
|
+
const originalSigintListeners = process.listeners('SIGINT');
|
|
187
|
+
const originalSigtermListeners = process.listeners('SIGTERM');
|
|
188
|
+
// Prepend our cleanup β it runs first, then existing handlers take over
|
|
189
|
+
process.prependListener('SIGINT', cleanup);
|
|
190
|
+
process.prependListener('SIGTERM', cleanup);
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=port-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port-discovery.js","sourceRoot":"","sources":["../../src/core/port-discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -108,6 +108,12 @@ export declare class FigmaWebSocketServer extends EventEmitter {
|
|
|
108
108
|
* Whether the server has been started
|
|
109
109
|
*/
|
|
110
110
|
isStarted(): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Get the bound address info (port, host, family).
|
|
113
|
+
* Only available after the server has started listening.
|
|
114
|
+
* Returns the actual port β critical when using port 0 for OS-assigned ports.
|
|
115
|
+
*/
|
|
116
|
+
address(): import('net').AddressInfo | null;
|
|
111
117
|
/**
|
|
112
118
|
* Get info about the currently active Figma file.
|
|
113
119
|
* Returns null if no file is active or connected.
|