Skip to content

Figma MCP Bridge Troubleshooting Guide

activeUpdated

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_console MCP tools
  • live variable edits in local Figma files
  • DOL design system files such as DOL Kid Design System

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 use

Observed behavior:

  • figma_console tools fail before reaching the file
  • figma_get_status may report EADDRINUSE
  • figma_list_open_files returns no connected files

Typical message:

Transport closed

Observed behavior:

  • figma_console tools 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_INFO ever 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

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_console tools are no longer trustworthy for the current thread
  • a manual WebSocket bridge fallback is required

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

Run:

Terminal window
lsof -nP -iTCP:9223-9232 -sTCP:LISTEN

Purpose:

  • confirm which ports are actually listening
  • identify whether old bridge servers still exist

Run:

Terminal window
ps -ax -o pid=,ppid=,etime=,command= | rg 'figma-console-mcp/dist/local\.js'

Purpose:

  • find stale figma-console-mcp processes
  • distinguish real listeners from orphaned processes

Preferred action:

  • kill only the stale node .../figma-console-mcp/dist/local.js PIDs
  • avoid broad destructive commands

Example:

Terminal window
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 use
  • Transport closed

then do not keep retrying the same tool calls.

Switch to a manual bridge fallback.

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 9223

This 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:

uKxQlMGpZU7qIgLdXtJ6K4

When adding DS variables through the bridge:

  1. Read current collections and variables first.
  2. Confirm the target collections exist.
  3. Create missing variables idempotently.
  4. If semantic tokens should alias a primitive, create or normalize the primitive first.
  5. Re-read variables after mutation and verify names, collection IDs, and valuesByMode.

For the KID spacing case, the intended pattern was:

  • primitive: dm-0 in Global Dimensions
  • semantic: Gap/None in Spacing
  • semantic: Padding/None in Spacing

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.

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.

EADDRINUSE can be real or stale. Always verify with both:

  • lsof for listeners
  • ps for orphaned MCP server processes

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”.

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.

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.

For future KID or DOL DS live-edit sessions:

  1. Check 9223-9232 listeners first.
  2. Kill stale figma-console-mcp processes before opening the plugin.
  3. Keep only one intended bridge alive when possible.
  4. Open the plugin only after the bridge is already listening.
  5. Confirm the console shows WebSocket connected to port 9223.
  6. Confirm the expected file key before editing.
  7. Re-read variables after every write batch.
  8. If the thread still reports Transport closed after several clean reopen attempts, restart both Figma Desktop and Codex before retrying.

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

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

The current bridge architecture optimizes for multi-instance access, not single-session reliability:

  • the plugin scans the whole 9223-9232 range
  • 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.

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.json

Benefits:

  • the import path no longer depends on the volatile node_modules or 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.

For this environment, the recommended order is:

  1. Make 9223 the only supported Desktop Bridge port for daily use.
  2. Import the plugin once from ~/.figma-console-mcp/plugin/manifest.json and use that stable copy.
  3. Use one bridge owner at a time whenever possible.
  4. Add a local “doctor” script that:
    • kills stale figma-console-mcp listeners
    • cleans stale port advertisements
    • starts one healthy bridge on 9223
    • verifies that FILE_INFO arrives before work starts
  5. If the session still reports Transport closed, restart Figma and Codex immediately instead of repeating plugin reopen loops.

If the bridge code will be maintained, the highest-value hardening changes are:

  1. Stop scanning 9223-9232 by default for normal local mode; prefer a fixed port and require opt-in for multi-instance mode.
  2. Add a visible session identity in the plugin UI so the user can see which server they are attached to.
  3. Add periodic rescan or explicit “Reconnect now” behavior instead of relying on the initial scan window.
  4. Add a health endpoint or doctor command that can verify:
    • bridge listening
    • plugin connected
    • expected file key identified
  5. Prefer fail-fast errors such as “9223 is busy” over silently binding to 9224+ in single-user workflows.

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.