Architecture

This document explains how Figma Agent Kit is structured: packages, runtime roles, data paths, and design principles.

Goals

  • Let AI agents (Cursor, Claude Code, Codex, …) read and write the Figma file currently open in Figma Desktop
  • Keep canvas traffic on localhost — no Figma REST upload of the document for bridge tools
  • Support multiple MCP clients at once via Leader / Follower election on a single bridge port
  • Ship an optional in-plugin AI path (rename / group) that is independent of MCP credentials

Monorepo layout

figma-agent-kit/
├── bridge.config.json          # Single source of truth for default WS port (1998)
├── packages/
│   ├── figma-agent-mcp/        # npm: stdio MCP + HTTP/WS bridge server
│   └── figma-agent-plugin/     # Figma Desktop plugin (bridge client + AI + slice export)
├── scripts/
│   ├── sync-bridge-config.mjs  # Sync port → MCP + plugin + manifest
│   └── release-kit.mjs         # Co-release MCP + plugin at the same version
└── docs/                       # This documentation set
PackageDistributed asRole
figma-agent-mcpnpm / GitHub PackagesMCP server + bridge leader/follower
figma-agent-pluginGitHub Release ZIPFigma UI + Plugin API handlers

Versions are kept in lockstep (0.1.x). Prefer pnpm release:kit:* over releasing only one side.

System overview

End-to-end:

  1. The agent talks MCP over stdio to a figma-agent-mcp process.
  2. That process is either the Leader (binds localhost:PORT) or a Follower (forwards to the Leader).
  3. The plugin UI iframe opens a WebSocket to the Leader and speaks MessagePack.
  4. The UI forwards RPC to the plugin main thread, which calls the Figma Plugin API (documentAccess: dynamic-page).

Tech stack

LayerTechnologies
MCPTypeScript (ESM), @modelcontextprotocol/sdk, ws, msgpackr, zod, pngjs / upng-js
PluginTypeScript, Rsbuild (main bundle), esbuild (MsgPack codec + JSZip inject), Figma Plugin API
Monorepopnpm workspaces, shared bridge.config.json
CIGitHub Actions — build, pack, tag releases → npm / GitHub Releases / GitHub Packages

Module map (MCP)

ModuleResponsibility
index.tsCLI entry, election, MCP stdio server
election.tsLeader listen / follower attach / failover poll
leader.tsHTTP /ping, /files, /rpc + WS upgrade
follower.tsHTTP client to leader
bridge.tsPer-fileKey WebSocket table, heartbeats, RPC timeout
codec.tsMsgPack encode/decode (useRecords: false)
tools.ts / schema.ts37 tools + Zod schemas
compress-png.tsTinyPNG-style compression for save_screenshots

Module map (plugin)

ModuleResponsibility
bridge/handlers.tsTool implementations (getNodeByIdAsync, motion, writes)
bridge/serializer.tsNode tree serialization for agents
ui/ui.htmlWS client, settings, i18n, slice export UI
ui/codec.tsMsgPack codec bundled into the UI
rename/*, group/*AI rename / visual group on clones
export/slices.ts1× preview / 3× PNG export helpers

Leader / Follower election

Multiple agent windows often spawn multiple MCP processes. Only one process may bind the bridge port.

  • Leader: binds port, accepts plugin sockets, serves discovery + RPC.
  • Follower: every tool call → POST /rpc (MsgPack) on the Leader; list_filesGET /files.
  • Health poll (~3–5s): if Leader dies, a Follower retries election and may take over.

RPC path (tool call)

Important details:

  • Wire tool name may appear as type or tool.
  • Screenshot data is raw PNG bytes on the bridge (MsgPack bin), not base64.
  • Logs go to stderr only — stdout is reserved for MCP stdio.

Screenshot & slice export paths

ToolCompressionTypical use
get_screenshotNoneAgent vision / preview (default scale=2)
save_screenshotsPNG default onDelivery slices; use scale=3 to match plugin UI export

Two AI paths

MCP bridge tools never need an LLM API key. Optional rename/group AI runs only inside the plugin UI.

Port configuration

  1. Edit root bridge.config.json (defaultPort).
  2. Run pnpm sync:bridge (also runs on predev / prebuild).
  3. Rebuild the plugin and restart MCP.

Runtime override for MCP only: FIGMA_AGENT_MCP_PORTmust match the port baked into the plugin manifest / UI.

Principles

  1. Local-first — bridge traffic stays on localhost; design data is not uploaded for MCP tools.
  2. MsgPack on the hot path — WS + follower RPC; small discovery endpoints stay JSON.
  3. Stdout purity — never print diagnostics on stdout in the MCP process.
  4. Async node lookupdynamic-page plugins use figma.getNodeByIdAsync.
  5. Co-versioned releases — upgrade plugin + MCP together after protocol changes.
  6. Capability honesty — Motion tools require a Figma build that exposes motion APIs; otherwise return a clear error.

Further reading