Architecture
Purpose
The companion provides MCP access to Figma documents where the user has opened the Bridge plugin. All runtime state is local to the user's machine and held in memory.
Components and transports
The process has two transport roles:
- STDIO serves MCP.
stdoutis reserved for MCP protocol messages, while logs and diagnostics go tostderr. - The loopback WebSocket
/bridgeserves the Bridge plugin. It accepts only127.0.0.1:<port>andlocalhost:<port>Host values.
Connection lifecycle
- The MCP client starts the local companion and establishes a STDIO session.
- The plugin UI opens
ws://127.0.0.1:<bridge-port>/bridgewith thefigma-mcp-bridge.v2subprotocol. The bridge port defaults to3846and can be set with the MCP server's--port <1-65535>argument. - The plugin sends
hellowith a randomconnection_idand document context. - The companion validates the message, stores the connection in the in-memory registry, and returns
hello_ack. - The MCP client calls
list_figma_connections, selects an activeconnection_id, and passes it to every document-specific tool. - The companion serializes calls into bridge requests and matches responses by
request_id. - On disconnect, the connection is removed and pending requests complete with a controlled error.
A connection_id identifies a plugin invocation rather than a persistent Figma file. A connection replacement uses compare-and-swap so a stale socket cannot remove the active replacement.
Bridge protocol
The bridge wire format has these properties:
- One MessagePack map per binary WebSocket frame.
- The
figma-mcp-bridge.v2subprotocol. - Envelope fields:
type,protocol_version,sent_at, and, when required,connection_id,request_id,method,payload, anderror. - Lowercase canonical UUIDs and UTC ISO-8601 timestamps.
- A 16 MiB bridge-message limit and a 12 MiB limit for base64 binary data.
- An operation allowlist and structured payloads, with no JavaScript execution or arbitrary property reflection.
Requests for one connection run sequentially and have a 30-second deadline. Mutations can use dry_run and idempotency_key; the plugin retains invocation-local results so a repeated key does not repeat the write.
State and security boundaries
The connection registry and pending requests live only in companion memory. Restarting the process requires the plugin to reconnect.
The bridge is loopback-only. It validates Host, Origin, and WebSocket subprotocol values before accepting a connection. The plugin stores only the local bridge port and does not send an access token in the WebSocket URL or bridge envelopes.