grab / grab/TalkToFigmaDesktop
Fix: Support ephemeral MCP clients (Perplexity, ChatGPT) — race condition on startup
- Dominant language
- TypeScript
- Stars
- 51
- Forks
- 10
- PR merge metrics
- No merged PRs in 30d
Description
## Problem
MCP clients like **Perplexity** and **ChatGPT** spawn a **fresh MCP server process for each tool call** (unlike Cursor/Claude Code which keep the process alive). This causes the connection to fail almost every time due to race conditions in the WebSocket + channel join flow.
### Error messages users see:
- `Error joining channel: Not connected to Figma`
- `Not connected to Figma. Connection timed out.`
## Root Cause
Three related race conditions:
### 1. `joinChannel()` has a premature connection check
`joinChannel()` immediately throws "Not connected to Figma" if `ws.readyState !== OPEN`, **before** `sendCommandToFigma()` can use its built-in connection retry loop.
```js
// Current code — fails for ephemeral clients
async function joinChannel(channelName) {
if (!ws || ws.readyState !== WebSocket.OPEN) {
throw new Error("Not connected to Figma"); // Instant failure!
}
await sendCommandToFigma("join", { channel: channelName });
}
```
### 2. Polling loop can detect OPEN before `ws.on('open')` fires
`sendCommandToFigma()` polls `ws.readyState` every 100ms. When the WebSocket transitions to OPEN, the polling `setInterval` can detect it **before** the `ws.on('open')` event handler runs. This means `channelReadyPromise` is still `null`, so the command is sent without waiting for the channel join.
### 3. No channel persistence across processes
Ephemeral clients lose all state (including `currentChannel`) when the process dies after each tool call. There's no way to pre-configure the channel.
## Proposed Fixes
### Fix 1: Remove premature check from `joinChannel()`
Let `sendCommandToFigma` handle the connection wait — it already has a retry loop.
```js
async function joinChannel(channelName) {
await sendCommandToFigma("join", { channel: channelName });
}
```
### Fix 2: Add `FIGMA_CHANNEL` environment variable
Allow pre-configuring the channel so each fresh process auto-joins on startup:
```js
let currentChannel = process.env.FIGMA_CHANNEL || null;
```
### Fix 3: Auto-join safety net in `sendCommandToFigma()`
After confirming the WebSocket is open, check if a channel join is still needed:
```js
// After WS is confirmed open and channelReadyPromise is awaited:
if (requiresChannel && channel && !currentChannel) {
await joinChannel(channel);
}
```
### Fix 4: Increase connection timeout
6 seconds is tight for ephemeral processes. 10 seconds gives more breathing room:
```js
const maxWait = 10000; // was 6000
```
## Configuration for Ephemeral Clients
With these fixes, Perplexity/ChatGPT users configure their MCP like this:
```json
{
"command": "node",
"args": ["path/to/dist/server.js"],
"env": {
"FIGMA_CHANNEL": ""
}
}
```
## Why Cursor works but Perplexity doesn't
| Behavior | Cursor / Claude Code | Perplexity / ChatGPT |
|----------|---------------------|---------------------|
| Process lifecycle | Persistent | Ephemeral (new per call) |
| WebSocket | Connects once | Must reconnect every call |
| Channel state | Joined once, remembered | Lost after each call |
| Time before first tool call | Seconds to minutes | Milliseconds |
| Race condition impact | None | Breaks every time |
## Tested
- Perplexity with GPT-5.4 Thinking model — confirmed working after fixes
- Claude Code — still works (no regression)
## Additional Ideas for Future Improvement
1. **`execute_figma_code` tool** — A single tool that accepts arbitrary Plugin API JavaScript and executes it inside Figma (like MCP Magic's `use_figma`). Would make the 40+ fixed tools extensible without code changes.
2. **Figma REST API tools** — Add tools that call `api.figma.com` directly for comments, reactions, and file metadata (no plugin needed).
3. **Connection diagnostics tool** — Help users troubleshoot connection issues from within the AI chat.
4. **Promise-based connection wait** — Replace the polling `setInterval` with a proper Promise that resolves from the `ws.on('open')` handler directly.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.