Figma MCP Bridge Troubleshooting Guide
Purpose
Section titled “Purpose”This note captures the MCP and Desktop Bridge issues observed while analyzing and updating DOL Kid Design System, so future sessions can recover faster and avoid repeating the same setup failures.
Use this guide when working with:
- Figma Desktop Bridge
figma_consoleMCP tools- live variable edits in local Figma files
- DOL design system files such as
DOL Kid Design System
Common Symptoms
Section titled “Common Symptoms”Symptom 1: MCP tools report ports are busy
Section titled “Symptom 1: MCP tools report ports are busy”Typical message:
All WebSocket ports 9223-9232 are in useObserved behavior:
figma_consoletools fail before reaching the filefigma_get_statusmay reportEADDRINUSEfigma_list_open_filesreturns no connected files
Symptom 2: MCP transport is closed
Section titled “Symptom 2: MCP transport is closed”Typical message:
Transport closedObserved behavior:
figma_consoletools stop responding entirely- reconnect calls also fail
- the current Codex thread can no longer rely on built-in Figma MCP tools
Symptom 3: Plugin is open but the file never connects
Section titled “Symptom 3: Plugin is open but the file never connects”Observed behavior:
- Figma console shows the plugin loaded variables successfully
- the plugin may show
Desktop Bridge active - no
FILE_INFOever reaches the waiting server - live variable mutation still does not start
Symptom 4: Plugin connects only to old ports
Section titled “Symptom 4: Plugin connects only to old ports”Observed behavior:
- Figma console shows lines like:
[MCP Bridge] WebSocket connected to port 9223[MCP Bridge] WebSocket connected to port 9224- the current session is waiting on a different bridge and never receives the file
Root Causes We Observed
Section titled “Root Causes We Observed”Cause 1: orphaned figma-console-mcp server processes
Section titled “Cause 1: orphaned figma-console-mcp server processes”Multiple stale node .../figma-console-mcp/dist/local.js processes can remain alive after previous Codex or terminal sessions.
Effects:
- they keep the port range busy
- they confuse bridge setup
- the plugin may reconnect to an old server instead of the current session
Cause 2: stale MCP state inside the current thread
Section titled “Cause 2: stale MCP state inside the current thread”Even after killing stale processes, the current thread may continue reporting EADDRINUSE or Transport closed.
Effects:
- built-in
figma_consoletools are no longer trustworthy for the current thread - a manual WebSocket bridge fallback is required
Cause 3: plugin/server timing mismatch
Section titled “Cause 3: plugin/server timing mismatch”The plugin only connects to servers that exist during its scan or reconnect cycle.
Effects:
- if the server starts after the plugin has already settled, the plugin may not discover it
- if the plugin is still attached to another live port, it may not rescan for a new one
Cause 4: GUI automation is blocked by macOS permissions
Section titled “Cause 4: GUI automation is blocked by macOS permissions”Attempting to reopen the plugin automatically via osascript can fail because Assistive Access is denied.
Effects:
- the agent cannot guarantee GUI recovery end-to-end
- the user may still need to reopen the plugin manually
Cause 5: stale transport state may survive plugin reopen
Section titled “Cause 5: stale transport state may survive plugin reopen”In the observed KID DS incident, reopening Figma Desktop Bridge repeatedly was not enough to restore access.
Effects:
- the plugin could still load variables locally but fail to attach to the session that mattered
- the current Codex thread could remain stuck in
Transport closed - the decisive recovery step was restarting both Figma Desktop and Codex, then reopening the plugin
Standard Recovery Workflow
Section titled “Standard Recovery Workflow”Step 1: inspect live listeners
Section titled “Step 1: inspect live listeners”Run:
lsof -nP -iTCP:9223-9232 -sTCP:LISTENPurpose:
- confirm which ports are actually listening
- identify whether old bridge servers still exist
Step 2: inspect bridge server processes
Section titled “Step 2: inspect bridge server processes”Run:
ps -ax -o pid=,ppid=,etime=,command= | rg 'figma-console-mcp/dist/local\.js'Purpose:
- find stale
figma-console-mcpprocesses - distinguish real listeners from orphaned processes
Step 3: kill stale bridge servers
Section titled “Step 3: kill stale bridge servers”Preferred action:
- kill only the stale
node .../figma-console-mcp/dist/local.jsPIDs - avoid broad destructive commands
Example:
kill <pid1> <pid2> <pid3>Step 4: verify whether built-in MCP still works
Section titled “Step 4: verify whether built-in MCP still works”If figma_console tools still return:
All WebSocket ports 9223-9232 are in useTransport closed
then do not keep retrying the same tool calls.
Switch to a manual bridge fallback.
Step 5: start a single dedicated bridge
Section titled “Step 5: start a single dedicated bridge”Start one bridge on a known port, ideally 9223, and wait for:
- a new WebSocket connection
FILE_INFO- the expected file key
Important:
- the plugin must reconnect to this bridge, not to an old session
- if another live bridge still exists, the plugin may stay attached to it
Step 6: reopen the plugin while the bridge is already listening
Section titled “Step 6: reopen the plugin while the bridge is already listening”Required user action:
- close
Figma Desktop Bridge - reopen it on the target file while the listening bridge is already alive
Verification in plugin console:
[MCP Bridge] WebSocket connected to port 9223This is the most reliable signal that the current bridge is the one the plugin actually sees.
Step 7: verify file identity before editing
Section titled “Step 7: verify file identity before editing”Do not mutate variables until the bridge has confirmed the expected file key.
For DOL Kid Design System, the observed file key was:
uKxQlMGpZU7qIgLdXtJ6K4Safe Mutation Pattern for Variables
Section titled “Safe Mutation Pattern for Variables”When adding DS variables through the bridge:
- Read current collections and variables first.
- Confirm the target collections exist.
- Create missing variables idempotently.
- If semantic tokens should alias a primitive, create or normalize the primitive first.
- Re-read variables after mutation and verify names, collection IDs, and
valuesByMode.
For the KID spacing case, the intended pattern was:
- primitive:
dm-0inGlobal Dimensions - semantic:
Gap/NoneinSpacing - semantic:
Padding/NoneinSpacing
Practical Lessons
Section titled “Practical Lessons”Lesson 1
Section titled “Lesson 1”If the plugin console says it connected to ports other than the bridge you are waiting on, the current session is not the one controlling the file.
Lesson 2
Section titled “Lesson 2”Transport closed is usually a session-level failure, not a normal retry condition. Stop using built-in figma_console tools in that thread and switch to the manual bridge path.
Lesson 3
Section titled “Lesson 3”EADDRINUSE can be real or stale. Always verify with both:
lsoffor listenerspsfor orphaned MCP server processes
Lesson 4
Section titled “Lesson 4”When the plugin is healthy but no file reaches the bridge, the missing step is often not “open plugin” but “open plugin while the correct bridge is already listening”.
Lesson 5
Section titled “Lesson 5”Do not assume GUI automation is available on macOS. If Assistive Access is blocked, explicitly ask the user to reopen the plugin and verify the console line.
Lesson 6
Section titled “Lesson 6”If repeated plugin reopen attempts still leave the session in Transport closed, escalate to a full reset:
- quit Figma Desktop
- restart Codex
- reopen the target Figma file
- reopen
Figma Desktop Bridge
For this incident, that full restart sequence restored built-in Figma MCP access, while plugin reopen alone did not.
Recommended Future Workflow
Section titled “Recommended Future Workflow”For future KID or DOL DS live-edit sessions:
- Check
9223-9232listeners first. - Kill stale
figma-console-mcpprocesses before opening the plugin. - Keep only one intended bridge alive when possible.
- Open the plugin only after the bridge is already listening.
- Confirm the console shows
WebSocket connected to port 9223. - Confirm the expected file key before editing.
- Re-read variables after every write batch.
- If the thread still reports
Transport closedafter several clean reopen attempts, restart both Figma Desktop and Codex before retrying.
When To Escalate
Section titled “When To Escalate”Escalate from normal troubleshooting when:
- the plugin reads variables but never connects to any waiting bridge
- the same thread repeatedly returns
Transport closed - the plugin keeps reconnecting to unknown ports from other sessions
- macOS accessibility restrictions block the required GUI step
In those cases, stop patching blindly and switch to:
- process cleanup
- one-bridge-only recovery
- explicit user confirmation from the plugin console
If those steps still fail, use a full app reset:
- quit Figma Desktop
- restart Codex
- reopen the plugin only after the new session is alive
Prevention And Hardening
Section titled “Prevention And Hardening”Can this be made universal?
Section titled “Can this be made universal?”Not completely.
This workflow depends on four separate failure domains:
- the Codex MCP session
- the local WebSocket bridge process
- Figma Desktop plus the plugin UI iframe
- macOS local networking and accessibility permissions
As long as the system depends on live GUI state plus a per-session local bridge, there is no honest way to guarantee “works in every case”.
The realistic target is:
- make the common path deterministic
- fail fast instead of failing ambiguously
- keep at least one fallback path available
What is causing most of the instability?
Section titled “What is causing most of the instability?”The current bridge architecture optimizes for multi-instance access, not single-session reliability:
- the plugin scans the whole
9223-9232range - it can connect to multiple MCP servers at once
- each Codex or terminal session may create its own local bridge
- if an old bridge survives, the plugin may attach to the wrong session
- if the intended bridge was not alive during the scan window, the plugin may never discover it
That trade-off is convenient for power users with many tabs, but it is the main source of confusing behavior for DS work.
Best prevention options
Section titled “Best prevention options”Option A: single-port, single-owner mode
Section titled “Option A: single-port, single-owner mode”Use exactly one bridge on localhost:9223 and treat any port conflict as an error, not as a fallback opportunity.
Benefits:
- removes port ambiguity
- makes plugin logs deterministic
- easier for non-technical users to recover
Trade-offs:
- only one Codex session can own Figma at a time
- multi-tab parallel access is intentionally disabled
This is the strongest low-complexity option for KID and DOL DS operations.
Option B: standardize on the stable plugin manifest path
Section titled “Option B: standardize on the stable plugin manifest path”Use the stable manifest created by recent versions of figma-console-mcp:
~/.figma-console-mcp/plugin/manifest.jsonBenefits:
- the import path no longer depends on the volatile
node_modulesor npx cache path - updates to the local MCP package refresh the stable plugin files automatically
- reduces confusion about which plugin copy is actually running
Trade-offs:
- this does not eliminate multi-instance ambiguity by itself
- the plugin still follows the server’s multi-port discovery model
Option C: replace per-session bridges with a singleton broker
Section titled “Option C: replace per-session bridges with a singleton broker”Use one long-lived local bridge daemon on 9223, then route requests from Codex sessions through that daemon instead of letting each session own its own WebSocket server.
Benefits:
- closest approach to an “always available” setup
- plugin only needs to know one port
- stale per-session listeners disappear as a class of bug
Trade-offs:
- requires code changes in the bridge architecture
- needs explicit routing and ownership rules between sessions
This is the real fix if the goal is operational reliability rather than ad hoc recovery.
Recommended operating model
Section titled “Recommended operating model”For this environment, the recommended order is:
- Make
9223the only supported Desktop Bridge port for daily use. - Import the plugin once from
~/.figma-console-mcp/plugin/manifest.jsonand use that stable copy. - Use one bridge owner at a time whenever possible.
- Add a local “doctor” script that:
- kills stale
figma-console-mcplisteners - cleans stale port advertisements
- starts one healthy bridge on
9223 - verifies that
FILE_INFOarrives before work starts
- kills stale
- If the session still reports
Transport closed, restart Figma and Codex immediately instead of repeating plugin reopen loops.
Code-level changes worth making upstream
Section titled “Code-level changes worth making upstream”If the bridge code will be maintained, the highest-value hardening changes are:
- Stop scanning
9223-9232by default for normal local mode; prefer a fixed port and require opt-in for multi-instance mode. - Add a visible session identity in the plugin UI so the user can see which server they are attached to.
- Add periodic rescan or explicit “Reconnect now” behavior instead of relying on the initial scan window.
- Add a health endpoint or doctor command that can verify:
- bridge listening
- plugin connected
- expected file key identified
- Prefer fail-fast errors such as “9223 is busy” over silently binding to
9224+in single-user workflows.
Recommendation
Section titled “Recommendation”For design-system work, the best practical answer is not “make every edge case magically work”.
It is:
- use a singleton bridge on
9223 - use the stable plugin manifest path in
~/.figma-console-mcp/plugin/manifest.json - treat multi-port fallback as a debugging mode, not the default operating mode
That combination will not make failures impossible, but it should eliminate most of the ambiguous bridge states that forced repeated plugin reopen and full app restarts.