figma-bridge-mcp
This server lets an AI assistant securely connect to Figma Desktop through a local plugin bridge and inspect, create, update, and export designs without a Figma token.
Connect & pair with Figma Desktop (
figma_connect,figma_pairing,figma_status) and manage access keys.Inspect designs read selections, node geometry, styles, tokens, component data, specs, and plugin API facts (
figma_selection,figma_inspect,figma_spec,figma_reference).Capture screenshots of nodes or selections as PNG files (
figma_screenshot).Create and edit designs by running engine commands: duplicate/reparent nodes, create text/shapes, set properties, gradients, components, variables, styles, layouts, prototypes, FigJam items, Slides, and more (
figma_run).Render JSX directly into the Figma canvas, including components, rich text, assets, and design tokens (
figma_render).Review history and diff local audit logs, git history, structural snapshots, and optionally Figma REST version history (
figma_history).Manage comments via the optional REST layer: read design feedback, post/reply with confirmation (
figma_comments).Export assets, tokens, CSS/DTCG, code specs, and raw node JSON for design-to-code workflows.
Map components and sync tokens two-ways between code and Figma, create bindings, rebind themes, and maintain design-system memory.
Work across multiple Figma windows/files by targeting a specific
fileKeyor Figma URL.
Provides tools for inspecting, creating, and updating designs in Figma Desktop, including screenshots, design specs, JSX rendering, tokens, assets, components, FigJam, and Figma Slides.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@figma-bridge-mcpwhat components and styles are in the current Figma file?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
figma-bridge-mcp
A local MCP server that lets AI assistants inspect, create, and update designs in Figma Desktop. It connects through a small Figma development plugin and exposes focused tools for screenshots, design specs, JSX rendering, tokens, assets, components, FigJam, and Figma Slides.
Everything runs on 127.0.0.1. No Figma Personal Access Token required. No
cloud. No binary patching of the Figma app.
An optional REST add-on adds version history, comments, and published-library metadata. Its Figma token stays on your machine and is never placed in your MCP client configuration or chat.
Requirements: Node.js 18 or newer, Figma Desktop, and an MCP client that can start local stdio servers.
Codex, Claude Code, and Cursor: MCP and skills in one bundle
Figma Bridge ships three focused shared skills plus thin plugin adapters for all three clients:
figma-bridge-design-to-code— exact Figma implementation in the target stack;figma-bridge-code-to-figma— semantic, componentized screens from code;figma-bridge-component-library— tokens, styles, components, variants and properties.
Client | Plugin format | Full install path |
Codex / ChatGPT |
| This repository's Codex marketplace |
Claude Code |
| This repository's Claude marketplace |
Cursor | Agent Plugins 1.0 ( | GitHub-backed team marketplace or local checkout |
The adapters all discover the same skills/ directory and start the same local
MCP package. Users do not download or maintain the skills separately.
For Codex, add this repository as a marketplace and install the bundle:
codex plugin marketplace add KaiUweHella/figma-bridge-mcp
codex plugin add figma-bridge-mcp@figma-bridgeStart a new Codex task after installation; plugin MCP servers and their
tools are discovered when a task starts. Figma Bridge is a local STDIO server,
so Codex may label its MCP auth as Unsupported. That label only means STDIO
does not offer Codex's OAuth/Bearer login flow; the Figma connection still uses
its own authenticated localhost handshake. Once the server is loaded, Codex
discovers these 12 tools dynamically: figma_connect, figma_status,
figma_pairing, figma_run, figma_render, figma_selection,
figma_history, figma_comments, figma_inspect, figma_reference,
figma_screenshot, and figma_spec. If a task still shows Tools: (none),
the server did not start in that task; it is not a static plugin capability
list.
This is a GitHub-hosted repository marketplace, not a submission to the
universal OpenAI plugin directory. The catalog follows the repository, while
each released plugin entry pins an exact v<version> Git tag and starts the
matching npm runtime version. main and @latest therefore cannot silently
move an installed skill bundle onto a different server contract.
For Claude Code, add this repository as a marketplace and install the bundle:
claude plugin marketplace add KaiUweHella/figma-bridge-mcp
claude plugin install figma-bridge-mcp@figma-bridgeStart a new Claude Code session after installing or updating the plugin, then
open /mcp. The figma-bridge server should be connected and show 12
tools: figma_connect, figma_status, figma_pairing, figma_run,
figma_render, figma_selection, figma_history, figma_comments,
figma_inspect, figma_reference, figma_screenshot, and figma_spec.
Claude namespaces MCP tools internally (for example,
mcp__figma-bridge__figma_connect) and resolves their schemas at runtime. It
is therefore normal for claude plugin details figma-bridge-mcp@figma-bridge
to report tool schemas resolved at runtime; not counted; /mcp is the source
of truth for the live tool count.
Choose either the full Claude plugin above or the MCP-only fallback below; do
not register both under the same figma-bridge server name. Claude gives an
existing manual MCP registration precedence and suppresses the plugin's copy,
so a stale manual command can make a healthy plugin appear to have no tools.
If /mcp reports a failed or zero-tool server, run
claude mcp get figma-bridge. When it reports a manual project, local, or user
configuration, use the scope-specific removal command it prints, start a new
session, and check /mcp again.
If /mcp reports CONNECTION_CLOSED and the debug output says
No version is set for command node, the process exited before the MCP
handshake. In the same shell environment that launches the MCP client, verify:
command -v node
command -v npx
node --version
npx --versionBoth version commands must succeed and Node must be 18 or newer. If the command
paths resolve to asdf shims without an active Node version, configure one in
asdf; if nvm should own Node instead, place its active Node directory ahead
of stale asdf shims in PATH. Restart the MCP client after correcting the
runtime environment.
The Claude marketplace uses the same pinned GitHub release and shared skill
tree. The matching npm package must be published before users install that
release because the plugin launches its local stdio server through npx. Its
public plugin source is an explicit HTTPS Git URL, so installing or updating it
does not require SSH keys or a GitHub known_hosts entry.
For Cursor Teams or Enterprise, import this GitHub repository into a team marketplace and install Figma Bridge from Customize. Individual users and contributors can use the same GitHub source without a central Cursor listing: clone the tagged release, link that checkout into Cursor, and reload the window:
mkdir -p ~/.cursor/plugins/local
ln -s /absolute/path/to/figma-bridge-mcp ~/.cursor/plugins/local/figma-bridge-mcpCursor detects the root Agent Plugin manifest and loads both the skill and MCP server.
Clients without plugin or Agent Skill support keep using the normal server
configuration below. They still receive the compact mandatory workflow through
MCP instructions, the user-invoked design-to-code, code-to-figma, and
create-figma-component MCP prompts, and
figma_reference {name:"workflow"}.
Quick start
1. Add the MCP server (MCP-only fallback)
Use this when the full plugin install is unavailable or you only want the MCP
tools without the bundled skill. Do not add this fallback when the full plugin
is already installed. The npx setup needs no clone or build step.
For Claude Code:
claude mcp add figma-bridge -- npx -y figma-bridge-mcp@latestFor another MCP client, add the equivalent server configuration:
{
"mcpServers": {
"figma-bridge": {
"command": "npx",
"args": ["-y", "figma-bridge-mcp@latest"]
}
}
}Restart the MCP client if it does not discover the server immediately. There is
intentionally no env block: the bridge creates its local credentials during
pairing.
git clone https://github.com/KaiUweHella/figma-bridge-mcp.git
cd figma-bridge-mcp
npm install{
"mcpServers": {
"figma-bridge": {
"command": "node",
"args": ["/absolute/path/to/figma-bridge-mcp/src/server.js"],
"env": { "FIGMA_BRIDGE_PROFILE": "local-development" }
}
}
}Use the local-development profile when a released Bridge is running at the
same time (for example from Claude Code). It pins the source checkout to
localhost:3460 and isolates its daemon, tokens, pairing and audit data below
~/.figma-bridge-mcp/profiles/local-development/. figma_connect generates
the gitignored, checkout-owned plugin/local/manifest.json bundle; import that
exact file and open Figma Bridge Local Development. You can also refresh
both checkout import bundles explicitly with npm run prepare:plugin-imports.
The main Plugin passes that manifest identity's exact port to the shared UI,
while Figma's manifest policy also permits only :3460.
Omitting FIGMA_BRIDGE_PROFILE retains the normal released profile and its
3456–3460 fallback behavior.
2. Pair each Figma editor once
Ask your AI assistant to connect to Figma, or call
figma_connectdirectly. It starts the local bridge and returns an access key plus a plugin manifest path.In Figma Desktop:
Plugins → Development → Import plugin from manifest…and choose~/.figma-bridge-mcp/plugin/manifest.json(the path returned byfigma_connect).Open
Plugins → Development → Figma Bridgein Figma Design, paste the access key, and click Save & connect.Figma stores plugin data separately for Design, FigJam and Slides. To use another editor, open the plugin there and save the same key once. In an already paired editor, Setup → Copy access key copies the in-use key without displaying or logging it.
When the plugin shows Connected (authenticated), the assistant can work with that Figma file. The pairing is remembered. Starting the configured MCP automatically wakes a daemon that exited after its idle timeout and reuses a healthy daemon/socket, so a new AI session does not interrupt the open Figma file. If the automatic wake fails, call
figma_connectonce before deeper diagnosis. If the only socket is open but its plugin iframe no longer answers a read-only probe,figma_connectreloads just that iframe and it reconnects with the stored key.
If a timed-out compatibility command leaves only the execution lane uncertain
while health reads still answer, call figma_connect with
{forceRecovery:true,fileKey:"<exact-file-key>"}. This explicitly reloads only
that authenticated Plugin UI; it refuses an unknown file and never reloads
while another Figma operation is still active.
The plugin keeps looking for the local bridge if it was opened first. It uses a
small bounded WebSocket burst for instant startup, then a quiet localhost health
probe every three seconds and opens a socket only after the daemon is present.
Use the reload icon to force one immediate scan. After an npm upgrade,
figma_connect refreshes the files at the stable path above, but Figma may keep
the previously imported build in its application cache. figma_status detects
that mismatch. Re-import the same manifest.json path once when it reports an
older plugin build; the saved access key is retained.
Figma Dev Mode requires its own manifest.json bundle
The normal manifest.json does not work in Dev Mode. Figma does not support
combining the existing FigJam editor target with dev in one manifest:
Import
~/.figma-bridge-mcp/plugin/dev-mode/manifest.jsonfor Figma Bridge Dev Mode. It keeps the authenticated MCP bridge connected for selection, inspection, specs and exports. Dev Mode is read-only, so rendering and canvas edits still require switching the file to Design mode and opening the normal Figma Bridge plugin there.
3. Use it with Figma
Select a frame or layer in Figma and describe the outcome you want. For example:
"Inspect my current selection and explain its layout."
"Create a settings card next to the selected frame."
"Export the selected screen's tokens and assets into this project."
"Implement the selected frame, then compare the result with Figma."
The assistant can read the current selection, capture screenshots and specs, render JSX, export assets, or apply targeted edits. Keep the Figma Bridge plugin open in every document the assistant should access. If more than one document is connected, pass a Figma URL or file key so the target is unambiguous.
Related MCP server: tellfigma
REST add-on (optional)
Everything above works with zero Figma credentials. Three things the local plugin bridge structurally cannot reach live behind Figma's REST API, and can be unlocked with a personal access token:
Feature | What it adds |
Version history |
|
Comments |
|
Library metadata |
|
Enabling it — the token never leaves your machine:
Create a personal access token in Figma (Settings → Security → Personal access tokens) with scopes: File content (read), File versions (read), Comments (read and write). Current user (read) is optional — it only makes
figma_statusshow your handle.Open the Figma Bridge plugin in Figma Desktop, connect, expand Setup, paste the token under Figma REST token, and select Save token. The plugin's Save history button becomes available after the token is stored.
figma_statusreports that the token is configured without making a remote request. Runfigma_status {validateRest:true}when you want an explicit validity check; it reports your handle or verifies file access when the optional Current user scope is absent.
The token travels from the plugin over the authenticated localhost
WebSocket to the daemon, which stores it in ~/.figma-bridge-mcp/rest-token
(mode 0600). It is never entered in chat, never stored in your MCP client
config, never echoed back by any tool, and never written to the audit log
(REST calls retain only the allowlisted endpoint capability, read/write class,
and non-reversible summaries). Clear token in the plugin removes the file.
Headless/CI alternative: set the FIGMA_REST_TOKEN environment variable — it
overrides the file.
Scope: by default REST calls target the file currently open in Figma
Desktop (the plugin pushes its file key). Other files require an explicit
fileKey parameter (bare key or full Figma URL). Note that a PAT itself can
read every file its account can access — keep the scopes minimal.
The REST client is a closed internal allowlist, not a generic HTTP escape hatch. It permits token health, version lists, version-pinned document contents, comments, and file-wide published-component metadata. A bare current file fetch and all node/CSS/export/variable/style/Dev-Resource endpoints are rejected before the token is read or the network is touched; those operations must use the local Plugin API commands above.
How it works
MCP client ──stdio──▶ figma-bridge-mcp (src/)
│
MCP tool adapters ─▶ Capability Catalog ─▶ CommandPlan
│
┌───────────────────────┴──────────┐
Command Application Modules generic CLI adapter
│ │
Design Capture │
Asset Policy │
└──────┬─────┘
Daemon Client Module
│ HTTP: signed requests
▼
local daemon :3456–3460
│ WS: challenge/response
▼
Figma Bridge plugin in Figma DesktopThe engine lives under
engine/. It began as a fork offigma-ds-cliv2.1.0 and has diverged well past it (see attribution). The Chrome-DevTools "Yolo mode" — which patches the Figma app binary — was removed entirely; there is no code path to it.Specialized MCP reads (
figma_spec,figma_inspect,figma_screenshot) and migratedfigma_runactions execute directly through value-returning Command Application Modules.export assets,node set-text,create frame|rect|ellipse|polygon|star, boundednode tree, current-page Node Discovery throughfind, directnode bindings, native Inspectnode css, exact Prototype Inspection, Component Identity Facts, Style Facts throughstyle show, bounded Style Catalog pagination throughstyle list, Layout Grid Facts throughgrid list, paginated Style Consumer Facts throughstyle consumers, Variable Facts throughvar show, Design Link set/inspect, and Design Contract capture/check use the same Implementation from MCP and Commander. Otherfigma_runactions remain on the deliberately broad child-process compatibility Adapter while they migrate one vertical slice at a time. One Daemon Client Module owns signing, timeouts and transport errors for both paths.The Design Capture Module walks an explicit node once and locally projects structure, style and the lossless output formats from those same facts. A Capture is reused only after a cheap revision probe proves the authenticated plugin connection and Figma document revision are unchanged. Missing or unstable revision metadata disables reuse; selection and named-section calls remain uncached in this first Slice. Captures distinguish authored Figma Auto Layout/Grid, Figma's marked
inferredAutoLayoutheuristic and geometry fallback. They also preserve Code-to-Figma semantic/fallback metadata separately from later native Figma annotations, plus full component and variable-mode contracts.The Design Link Registry gives a component, screen or frame one durable, repository-owned Design Entity id.
figma-bridge.jsonholds portable code/Storybook/Figma links; Figma plugin data holds only the same id and kind. This dual anchor lets future agents resolve the exact existing component from either side without putting repository paths into a Figma document.The report-only Round-trip Planner compares current code and the current normalized Figma subtree with an explicitly Accepted Design Baseline. The Project Design Context projects that status, the entity links and the exact next reads through one in-process Command Application. When semantic paths exist, changed subtrees are reported with their current node ids; plugin markers themselves never count as visual changes.
A Design Contract turns one linked Design Entity's complete Design Capture into a deterministic repository gate. Run
figma_run ["contract", "capture","ui.button"]once and review the JSON; laterfigma_run ["contract","check","ui.button"]reports canonical drift and separately enforces variant matrices, token-binding floors, geometry tolerances and prototype transitions. Volatile Figma handles are ignored and depth-limited captures are refused.A versioned Round-trip Fidelity Contract classifies each core Figma fact independently for Code → Figma and Figma → code, including its concrete implementation and verification seam. CI fails when a core fact or direction is unclassified; inspect the current projection with
figma_reference {name:"fidelity"}.One Capability Catalog resolves every Figma Command entering through MCP into an immutable plan before either execution adapter runs it. That plan is the single source for exposure, Figma/workspace/external-network/shared-local-state effects, target need, confirmation, normalized paths, retry, timeout, accepted exit codes and background-job identity. Unknown commands default to denied/write/no-retry.
One Workspace I/O module confines Command Capability file effects to the MCP launch directory by default. Paths are checked after symlink resolution, opened files are revalidated before bytes are read or written, and structured whole-file inputs have byte, nesting, collection and YAML-alias limits. This does not narrow normal Figma-to-code or code-to-Figma work inside the project. For a legitimate second repository, add canonical roots explicitly with
FIGMA_BRIDGE_EXTRA_ROOTS(use the platform path-list separator::on macOS/Linux,;on Windows). The launch directory always remains allowed.One immutable Figma Target Context resolves explicit
fileKey, a pasted Figma URL from that command's documented node field, a Design Link Registry, or implicit single-window targeting once per command and then accompanies planning, audit, job identity and daemon execution. URLs in text, JSX, names, annotations, paths, labels, and remote assets remain payload and cannot retarget a command. One shared Asset Policy classifies image fills, vector art and vector clusters for both Design Capture projections and export.One versioned Dispatch Receipt binds every authenticated Plugin action to the selected connection epoch, file or draft session, expected editor and advertised operation capability. The Plugin validates it immediately before execution and echoes the same identity with a terminal state; receipt identities never enter Audit or user-facing output.
One deadline-aware Plugin Write Lane serializes conservative writes per resolved document session before dispatch. Different files continue in parallel; queued expiry or caller cancellation never reaches Figma, while an unknown dispatched outcome quarantines only its target until the Plugin main thread replays the exact settlement receipt. Aggregate queue state is visible without exposing file, connection or receipt identities.
Runtime protocol validators reject malformed HTTP execution payloads and plugin frames at the transport boundary. TypeScript checks the JavaScript seams (including the Figma plugin), while deterministic context, payload and median and tail latency budgets catch architectural regressions in CI without treating brief shared-runner scheduling pauses as a sustained regression.
The daemon brokers commands to the Figma plugin over a localhost WebSocket. Two gates protect it:
HTTP routes (
/health,/exec) require a per-request HMAC signature keyed with the session token, a 0600 file — the token itself never crosses the wire.The plugin WebSocket (
/plugin) requires the access key: anOrigin/Hostallowlist plus a mutual challenge-response handshake in which the key is only ever an HMAC secret and never crosses the wire either. This closes the upstream gap where any local process could connect to the plugin socket and run code in your Figma document — and the inverse gap, where anything answering on a local port could drive an honest plugin.The unauthenticated
GET /plugin-readyroute is a CORS-scoped discovery beacon containing only bridge identity, readiness and port. It carries no file, token, version or connection data; every command route remains signed and the discovered WebSocket must still complete the mutual handshake.
Tools
Tool | Purpose |
| Ensure Safe Mode is available without replacing a healthy daemon/socket; self-heal a single unresponsive plugin iframe; explicitly recover one exact split execution lane with |
| Report local daemon/plugin/file/key state and, by default, run one bounded closed Figma main-thread responsiveness probe per target; |
| Show the access key; |
| Run a Capability Catalog-approved engine command; discover them with |
| Render JSX into the open Figma design. |
| Inspect a node by id: geometry, fills/strokes/effects, clip, opacity (YAML). |
| Save a PNG of a node/selection to a temp file (path + dimensions + applied scale returned). |
| Design-to-code spec of one node or a bounded |
| Offline Figma Plugin API reference ( |
| Private local capability history — filter by |
| The user's current selection in Figma (ids, names, types, sizes) — pushed live by the plugin. Instances resolve to their stable publish |
| REST add-on: read design-review comments ( |
Node ids are accepted in every form a user has at hand: 12:34, the URL
form 12-34, or a full Figma URL (whose file key is checked against the files
you actually have open — see Several files at once).
figma_run with ["node","tree",...] is a fast bounded outline for discovery
and follow-up node ids. It reports depth/output truncation explicitly; use
figma_spec plus a screenshot for exact Figma-to-code implementation facts.
An explicit detached node handle is rejected by node tree: Figma can retain
direct-id handles after a node leaves the canvas, so handle availability is not
present-tense hierarchy evidence. figma_run with
["node","delete","12:34",...] reports removed, already-detached, or
not-found per target and verifies every removal against the target's former
parent after first proving a complete parent-membership chain to the DOCUMENT
root. Nested deletion batches remain supported and execute deepest-first.
If Figma returns a 1-pixel PNG axis for a node whose scaled geometry is larger,
the Screenshot Command rejects the image before saving it and reports both the
actual raster and logical geometry. Refresh the identity with
figma_run {args:["component","list"]}, retry the exact current Variant id, or
capture its parent Component Set. This avoids accepting a stale or unsettled
Variant export as visual evidence.
The documented figma_run ["export","node",…] and
figma_run ["export","screenshot",…] names remain available. Their PNG mode
is a compatibility alias of the same Screenshot Command Application, so it
uses one bounded capture, validates the PNG before writing through Workspace
I/O, and reports dimensions from the encoded raster. JPG, SVG and PDF use a
separate Plan-bound Image Export Application. It validates bounded canonical
transport and the requested JPEG, SVG or PDF container before Workspace I/O;
these formats remain available without being misrepresented as verification
PNGs.
Write commands can be gated behind an explicit confirm:true by setting
FIGMA_WRITE_CONFIRM=1 in the server's environment. The gate works on
subcommand level: reads like node tree or component list pass freely,
mutations like node delete, combos, or tokens spacing require confirm.
figma_status reports whether that policy is currently required or
disabled.
Omitting confirm or passing confirm:false is not a portable dry run: when
the policy is disabled, a valid write executes immediately. Use
preview:true with figma_run or figma_render whenever the request must be
guaranteed non-mutating. The Bridge validates the request and returns a
payload-free Command Plan without dispatching it; preview:true also wins when
confirm:true is present. The engine CLI remains direct execution and uses
only each command's explicitly documented --dry-run option.
Workspace file access is zero-configuration for the project from which the MCP server is launched. For a multi-project workflow, configure additional roots on the server process, for example on macOS/Linux:
{
"env": {
"FIGMA_BRIDGE_EXTRA_ROOTS": "/work/shared-design-system:/work/second-app"
}
}Use ; between roots on Windows. Relative extra roots resolve from the launch
workspace. Paths outside the launch workspace and these explicit additions are
rejected without printing the private absolute path.
Remote raster images in figma_render JSX are opt-in. Configure exact origins
on the MCP/CLI host process; public sources require HTTPS, while intentional
private or loopback sources use the separate private-origin setting:
export FIGMA_BRIDGE_IMAGE_ORIGINS=https://images.example.com
export FIGMA_BRIDGE_IMAGE_PRIVATE_ORIGINS=http://assets.internal:8080Multiple origins are comma-separated. Wildcards, URL credentials and fragments are rejected. Signed query strings may be used to retrieve an image, but they are never retained in the Render Plan, result, Audit Trail or source provenance. The host reauthorizes redirects, validates every resolved address, pins the approved connection, rejects compressed or oversized responses, verifies MIME type against PNG/JPEG/GIF/WebP bytes and embeds the content by SHA-256 identity. The Figma Plugin receives only verified bytes and remains restricted to its existing localhost Bridge domains.
Local PNG/JPEG/GIF/WebP references use the same bounded asset contract.
Literal quoted and braced src/image forms resolve through the configured
workspace roots and converge on one content identity; empty values, runtime
expressions, unsupported formats and paths outside those roots fail before any
Figma mutation.
Safe Mode exposes the core canvas edits as typed commands; raw eval remains
blocked:
figma_run {args:["node","duplicate","12:34","--name","Copy"]}
figma_run {args:["node","reparent","12:34","56:78","--index","0"]}
figma_run {args:["create","text","Watermark","--parent","56:78","-x","24","-y","24"]}
figma_run {args:["create","star","Badge","--points","8","--inner-radius","0.55"]}
figma_run {args:["node","boolean","subtract","12:34","12:35","--name","Cutout"]}
figma_run {args:["node","set","12:34","--rotation","8","--radii","4,8,12,16","--layout-mode","column","--padding","16"]}
figma_run {args:["component","instantiate","12:34","--parent","56:78"]}
figma_run {args:["component","prop","set","12:34","State","Active"]}
figma_run {args:["gradient","apply","12:34","linear-gradient(90deg, #7c3aed, #06b6d4)","--field","stroke","--stroke-weight","1"]}The read-only canvas info and canvas next commands share one targeted
Canvas Awareness path across CLI and MCP. They wait for dynamic page loading,
validate the returned aggregate geometry, and accept only a finite numeric gap
plus the explicit right or below direction before anything reaches Figma.
This keeps automatic Code-to-Figma placement useful without treating option
values as executable code.
canvas pages and canvas page likewise use one Page Navigation path across
CLI and MCP. Page listing is a bounded safe read; switching is explicit,
non-retrying, and resolves only an id, exact name, or unique substring. Use
node:<id-or-Figma-URL> for explicit node identity or name:<literal> for an
explicit page name; an unprefixed value remains the compatible query: search.
Only the explicit node form may derive the target file from a Figma URL. It
changes the active editor page without modifying document content, while
ambiguous names fail instead of selecting a page by guesswork.
The same discriminator protects union lookups in Figma Slides and
component add-variant: node:, name: and (for Slides) label: select one
identity surface, while bare values preserve the existing query workflow.
URL-shaped names and labels therefore stay usable without silently retargeting
the Command. component add-variant --from also restores and freshly verifies
TEXT, BOOLEAN, INSTANCE_SWAP and SLOT sublayer references plus nested Instance
exposure that Figma's native clone can drop.
Top-level find provides bounded Node Discovery on the fully loaded current
page across CLI and MCP. Case-insensitive partial-name matching, the optional
node-type filter and a 1–200 result limit are parsed as inert data before
readiness. Search stops immediately when the result limit is reached and after
100,000 visited nodes; either bound is reported so follow-up reads never rely
on a silently incomplete node-id list.
The mutating canvas page-create and canvas page-divider commands share a
separate non-retrying Page Structure path. Duplicate page names and divider
indexes are validated before creation. Optional divider names must use Figma's
native all-asterisk, all-dash or all-space form so the command cannot silently
create a normal page. A failure after creation triggers Command-owned cleanup
and reports possible residue truthfully if Figma cannot remove it.
node duplicate works for cloneable design nodes including Sections;
node reparent accepts an explicit child index and preserves canvas position
when the destination is not Auto Layout. create frame|rect|ellipse|polygon|star
and vector|slice|line|autolayout|text use action-specific, readback-verified creation Applications
across CLI and MCP. They accept an explicit parent where Figma permits it, so
small children can be added to an existing frame without rebuilding it. Text
creation also preflights the requested font before creating its node.
ID-scoped node group|ungroup|boolean|flatten replace selection-dependent
structural edits. Components can be created, instantiated, swapped, detached,
and have their overrides reset or compacted without relying on the current
selection. The direct node set surface covers geometry, visibility/locking,
fills/strokes/effects, blend and mask settings, individual corners, constraints,
Auto Layout container/child properties, polygon/star geometry, and Section
visibility. The single-node command preflights all supported properties and
Variable references, freshly verifies the complete result, and restores its
full field journal if any setter or postcondition fails. Unsupported fields
remain explicit skips, while ambiguous Variables stop before mutation. Legacy
selection-based create subcommands remain unreachable through figma_run;
VectorNetwork input is bounded inert JSON on its typed route.
Dev Mode measure add|edit|delete mutations share one Plan-bound Application
across CLI and MCP. Add and edit own rollback; delete reports its irreversible
freshly verified outcome without retrying after dispatch.
component main <instanceId> is the bounded identity read for reuse. Commander
and MCP resolve the same explicit Instance through one safe-read Command
Application and return its native main component, optional set, publish keys,
variants and component-property facts. A full Figma node URL may supply the
target file; display names remain descriptive and are never treated as durable
component identity.
The operation audit is pinned to the installed official
@figma/plugin-typings version. It exhaustively classifies every PluginAPI
creator and structural method, checks every engine command against the Safe
Mode Capability Catalog, and checks all official variable/easing value types.
A Figma typings upgrade or newly registered command therefore fails CI until
the new operation is supported or recorded as an explicit boundary. Run
figma_run {args:["api","gap"]} for the current direct/alternative/boundary
summary.
Native JSX instances require durable Registry identity (entity plus a
published key or local id). Their editable overrides use the component's
real Figma structure:
<Instance
entity="ui.card"
prop:Selected="true"
text:Title="New title"
fill:StatusDot="var:status/healthy|#22c55e"
swap:LeadingIcon="ui.icon.leaf"
/>prop: resolves a component-property definition; text: and fill: resolve
one named descendant. swap: values and INSTANCE_SWAP property values are
Design Entity ids, resolved from figma-bridge.json; component display names
are intentionally not accepted as swap identity. Missing, ambiguous or
unlinked targets stop preflight before the first canvas node is created.
JSX and browser capture carry only the Design Entity plus variant/override
intent. Command preparation reads the Registry once and embeds a fingerprinted,
target-aware binding table in the Semantic Render Plan. Published keys are
preferred; an unpublished local node remains usable only when its Registry file
key exactly matches the resolved Figma target. Authored key, id and
component name handles cannot replace that binding. The generated
compatibility path consumes the same table, so it does not reintroduce a second
identity lookup.
For raster content, <Image src="assets/artwork.png" /> is materialized by the
host as bounded embedded bytes and stays on the native executor. Image bytes
ride inside the plugin payload as base64 against a 5 MB protocol value, so one
file may be at most 3.5 MB and a render measures its whole plan before
dispatch: several images that each fit still share that one payload, and the
refusal says so before the plugin is touched. An intentional
<Image name="Artwork placeholder" /> remains useful, but it selects the
generated compatibility executor and returns completed-with-findings with the
exact image needs host-materialized embedded bytes degradation. A placeholder
therefore never counts as native or pixel-exact image fidelity. The executable
MCP form, Registry setup command, and current element-specific props are kept in
figma_reference {name:"workflow:code-to-figma"}; use
figma_run {args:["render","--prop-reference","Image"]} for the bounded Image
prop reference.
Dimensions and typography accept the same var:name|fallback form. The native
executor binds width, height and min/max constraints plus font family/style,
weight, size, line height, letter spacing, paragraph spacing and paragraph
indent. Family/style use STRING variables; the other typography and dimension
fields use FLOAT variables. A missing bound font stops preflight with an
install-or-choose-another-face message instead of silently substituting it.
Named Text Styles are reconciled before canvas creation as well: an explicit
style="Typography/Eyebrow" is reused only when its complete typography
matches; a conflicting same-name style stops, and otherwise exact typography
is reused. Unmatched implicit typography stays literal by default instead of
creating a parallel style. Set MCP materializeResources:true or CLI
--materialize-resources when the source intentionally asks Figma Bridge to
create generated Text Styles and spacing/radius Variables. Explicit named
styles and var: fallbacks remain available without that flag because they
already carry authored resource intent.
Line height keeps its authored unit in the Semantic Render Plan: quoted
unitless CSS multipliers such as lineHeight="1.1" and percentages such as
lineHeight="110%" become Figma PERCENT; explicit
lineHeight="24px" and numeric JSX lineHeight={24} remain PIXELS; and
lineHeight="auto" remains AUTO. Negative, non-finite, or unknown values
stop before mutation. Text Style matching compares both unit and value.
Figma float32 metric readback is normalized for stable comparison, and
family-specific faces such as DM Sans/Manrope SemiBold and ExtraBold are
tried before any fallback family. Successful native renders return
textStyleReport and variableReport counts for references,
unique reused resources, created resources, bound properties and literals kept
as literals. After a native render the text is measured back: every rendered
text is matched to the plan by the semantic path the executor stamps on it,
and a font family or style that differs from the requested one, or a line
count that differs from what the browser measured (render --dom-capture
carries the browser's line count beside the plan), becomes a finding — the
text-fidelity stage in the outcome, printed after every CLI render. The
native executor falls back to Inter when a family is not installed; that
fallback is now visible instead of silent. Ambiguous or unsupported preflight errors include the
corresponding zero/nonzero counts and do not leave newly created variables or
canvas nodes behind. Command Preview reports the generated-resource policy
without echoing JSX or other private payloads.
<Text> also preserves editable inline Rich Text. Nested <strong>/<b>,
<em>/<i>, <u>, <Span ...> and <a href="..."> markup becomes native
Figma ranges; HTML entities are decoded before UTF-16 range offsets are
calculated. Span runs support font, fontStyle, weight, italic, size,
color, letterSpacing, underline/decoration and safe links:
<Text font="Inter" size="14">
Hello{" "}
<strong>
bold <em>and italic</em>
</strong>
<Span color="#ef4444" size="18">
red
</Span>
<a href="https://example.com">link</a>
</Text>Plugin window
The Figma Bridge plugin window is more than the connection status:
Compact by default — the always-on view is a small status card plus one control row. Activity and Setup expand only when needed and close each other, so the panel does not cover the canvas after the one-time key entry.
Save history — the primary safety action, available after the optional REST token is configured. It writes a human-readable labeled checkpoint such as
Figma Bridge checkpoint · 21 Aug 2026, 14:05into Figma's own version history. There is no restore API for plugins: you roll back through Figma's version history panel.Pause agent — a kill switch: while paused, the plugin rejects every incoming agent command with an explicit error.
Activity — a secondary icon in the title bar opens every command the agent runs, with duration and ok/error state. A small badge carries the event count without competing with the safety controls.
Selection readout — whatever the user selects is pushed to the agent automatically (debounced) and shown as "Agent sees: …", so the user always sees what
figma_selectionwill return. Select a frame, say "build this" — no node-id copying.Setup — the access key (masked by default, with Show/Hide) and the optional REST token, always reachable whether or not the bridge is connected.
Design-to-code workflow
The design is the complete specification — the tooling makes copying it easier than interpreting it. Build a screen from Figma in six steps:
Keep the target project's framework and styling system. Do not add Tailwind, a UI kit or an icon library solely for the screen, and never replace exported Figma artwork with a convenient approximation. Reuse a project component only when its rendered design and states actually match.
figma_screenshoton the target frame, then read the saved PNG — the visual ground truth. Never build from a node tree alone.One
figma_specwithphase: "all",depth: 3–4(for a large screen:depth: 30plusoutFile, which stitches the complete capture from bounded plugin evals under one observed revision and writes it to the workspace — implement from that file, never from the screenshot) — build the markup skeleton and its bounded exact styles: real text characters, resolved icon/component names (instances are descended into, so overrides and true main-component names appear), hierarchy and flex direction. Copy texts and icons verbatim. Alayout:inferred (Figma heuristic — verify)marker is not authored Auto Layout; check the hierarchy before treating it as the component contract.Export tokens (
figma_runwith["export","css"]or["export","dtcg"]) and wire them up as CSS variables / theme. The output names its source Figma file — check it is the file you are building.Export assets (
figma_runwith["export","assets","<nodeId>","-o","/abs/path/src/assets"]) — every→ assets/…reference in the spec points at a file this writes. Pass an absolute path; large exports keep running in the background ("still RUNNING") — re-run the same call to poll. Manifest v2 keeps source identity, content digest, semantic label, physical filename and placements separate.assets.jsonis merged across runs and byte-identical assets are deduped; same-name/different-content collisions get a stable digest suffix instead of overwriting an earlier file. Existing v1 manifests remain readable. Each asset carries placement data (x/yoffsets,parentname path,parentId,absolutePosition,overhang), so the manifest alone positions an overlay — no spec cross-reference needed. The export summary lists the absolutely-positioned and overhanging files explicitly: those are the ones builds lose. After export,assets.jsonis authoritative for any collision-resolved filename. Oversized PNGs are downsampled by default to 2× their largest Figma usage (retina density), without upscaling and only when the encoded file becomes smaller. Aspect ratio, manifest placement and CSS crop behavior remain unchanged; pass--raster-scale 0to retain original PNG bytes. CLI and MCP now execute this through one Asset Export Command Application. Equivalent default flag spellings attach to the same tracked job; polling never starts a duplicate. Every multi-step read is pinned to one daemon-confirmed Figma connection, file and target-Page revision. A change aborts before publication. Document-wide revision observation initializes only for freshness-sensitive reads; ordinary eval/render paths keep Figma's dynamic page loading. A process-safe output lock serializes CLI/MCP merges into the same directory. A partial byte-export failure keeps successful files, preserves last-known-good failed placements and reports every failed asset. If all selected assets fail, the command returns an error and leaves an existingassets.jsonunchanged instead of replacing it with an empty manifest. Asset bytes use an authenticated, digest-checked stream outside the generic JSON result: each 256 KiB chunk is acknowledged before the next, the Daemon download is signed and one-shot, and the publication Worker stages each asset before requesting another. A single PNG/SVG may therefore exceed the former Base64 ceiling without raising the 5 MiB result limit or retaining all export bytes in memory. Figma still supplies one complete byte array per individual asset; the bound is the largest current asset (maximum 512 MiB), not the total export. Passforce:truetofigma_runwhen an intentional fresh export should bypass the short retry-result cache; a running job is still never duplicated.One
figma_specnodeIds[]batch for any missing deeper styles — put all section/node reads into the same call instead of requesting each section separately. In Manual Mode this means one approval for the batch. Usephase: "style",depth: 0for exact containers anddedup: truefor repeated lists/cards. Apply sizes, gaps, padding, alignment, fill/hug sizing, paints incl. gradients (→ var(name)marks a design-token binding), radii, shadows, typography,opacity,clip(overflow hidden) andabspositioning. Decorative vectors appear asvector art → assets/…lines with placement — place the exported SVGs, never approximate them in CSS.Structured YAML/JSON additionally retains exact component property definitions and values (including INSTANCE_SWAP and SLOT), property references, preferred values, direct overrides, exposed instances and slot violations. Variable bindings include collection identity, authored scopes, explicit/resolved modes,
codeSyntax.WEBand the resolved value;inferredVariablesis emitted separately as suggestion-only evidence.For a large section, request
depth:0first. This is a complete contract for the section container itself (including background, border, radius and layout) without descendants. Then request child node ids in bounded calls. Usededup:truefor repeated cards/lists; sharedS<n>references remain lossless and stop identical instance styles exhausting the result budget.Verify — screenshot your build and compare against the PNG from step 1, then run the mechanical check:
figma_run ["verify-build", "/abs/path/to/project"]It greps the project against
assets.jsonand lists every exported file that is not referenced in the build — with size, offsets and parent, so placing it is one step — plus aborder-imagelint (CSSborder-imageignoresborder-radius; gradient strokes on rounded boxes need the wrapper or mask pattern). Exit code 1 when files are missing, so it works as a CI gate too.With a build screenshot it also runs the visual pass:
figma_run ["verify-build", "/abs/path/to/project", "--compare", "/abs/build.png"]The reference render is fetched live from Figma (
--node <id>, default: the manifest's export root) or supplied offline via--design <png>. Both images are normalized to a common width and pixel-diffed (antialiasing-tolerant); the output reports the overall diff percentage, a height-mismatch finding (build too tall/short = inserted or dropped block), the worst differing regions in node-pixel coordinates — the same space the spec andassets.jsonuse — and writes a diff PNG (red = differing, on the dimmed design). Informational by default;--max-diff <pct>gates the exit code.
For large screens, section-level agents are an optional elapsed-time
optimization after the screenshot, structure map, tokens and assets are fixed.
Use them only for substantial sections with disjoint component/style files;
the coordinator keeps ownership of the shared shell, tokens, assets.json,
integration and final pixel diff. Parallel agents usually consume more total
tokens because each needs project context, so use sequential work when token
cost matters more than wall-clock time.
Use an existing browser tool or project harness for the build screenshot. Do not install Playwright (or another browser dependency) solely for capture without the user's approval; if it is already present, it is a valid capture mechanism rather than a Figma Bridge dependency.
The same spec is available as
figma_run ["export", "code-spec", "<nodeId>"]. Its default is the readable
tree; pass -f yaml or -f json for the canonical model.
Lossless structured spec formats
figma_spec and export code-spec default to format:"tree", the concise,
line-oriented agent view whose footers carry the required asset and fidelity
actions. Use yaml or formatted json explicitly when a consumer needs the
versioned canonical model. Both structured formats serialize the same model;
only syntax differs. Roundtrip tests require every field — text,
ids, layout provenance, paint, typography, mode-aware variables, assets,
component contracts, Bridge intent, native annotations, capture
completeness, and fidelity checks — to survive exactly. Minified JSON is not
offered: real agent tests showed that a single huge line was materially harder
to act on despite carrying the same raw fields.
The model's capture field explicitly reports requested/actual depth,
payload completeness, hidden-node policy, and whether the requested depth cut
off descendants. There is no silent tool-result truncation: if a spec exceeds
the configured output budget, the call returns complete:false with a
section-by-section retry recipe and returns no misleading partial design.
depth:0 intentionally means “the requested node only” and is complete, not
a depth-truncated tree.
Every Design Capture also carries a hidden-content census independent of
depth and includeHidden: total, visible, hidden and hidden-text counts plus
bounded layer ids and text previews. Hidden layers remain excluded by default
so they cannot become phantom UI, while Code-Spec marks exact hidden content
and alternate-state inspection incomplete until the same node is requested
with includeHidden:true.
Used Component Sets are keyed by Design Entity, published set key, or local
set id—not by their mutable display name. Code-Spec normalizes State,
Status, Interaction, and Boolean component properties and reports every
set as defined, noneDefined, or notCaptured. A notCaptured set blocks
style output and returns one bounded nodeIds[] batch recipe, avoiding both
guessed states and repeated Manual Mode approvals.
CSS and DTCG token export preserve every Figma collection mode and resolve
aliases per mode. DTCG keeps the complete mode table in the
figma-bridge-mcp extension while its normal $value remains the default
mode for standard consumers. CSS emits the default under :root and each
additional mode under [data-figma-mode="<Mode name>"]; applying that
attribute is an explicit application responsibility. Different mode values
are never converted into clamp() without authored viewport/min/max intent.
Color aliases with Figma-authored opacity are read as the native closed
COMPOSE_COLOR(alias, opacityPercent) expression. CSS receives the resolved
alpha-composed color; DTCG additionally preserves the exact Alias name,
Variable id and opacity percentage in the Bridge extension.
Public MCP failures use stable error kinds and user-facing details; internal Node executable paths and command arguments are never returned. Safe read recovery keeps its single retry, but the operation, readiness wait and CLI fallback all spend the same overall deadline. A missing daemon token goes straight to the existing startup fallback instead of waiting for readiness.
For large screens, first request one shallow structure map, then send up to
eight bounded section/style reads through nodeIds[] in the next single
figma_spec call. The batch fails closed on the first failed read or when its
combined result would exceed the output budget; it never presents partial
batch data as complete.
IMAGE-fill filenames are keyed by Figma's stable image hash, not by the local
layer name/path. This keeps figma_spec, isolated child calls, asset export and
assets.json on the same filename even when generic layers such as “Frame 64”
are reached through different roots. Linked vectors use their Design Entity
identity; unlinked vectors fall back to canonical visual content identity,
never a volatile node id. verify-build recomputes Manifest v2 digests from
the physical files and exits non-zero for tampering or missing asset files;
v1 entries without digests remain compatible but are reported as unverified.
For MCP design-to-code calls, dedup:false is the default: every visible layer
keeps its own id, native Figma Inspect css{…}, layout/paint/token facts and
complete text. Mixed rich-text layers carry their individual styled ranges.
The footer reconciles the live visible-layer count with explicit rows, SVG
internals, component internals and non-rendering helpers. A style projection
is rejected when depth limits or an unaccounted layer would force guessing;
split it by the node ids from the structure map. Set dedup:true only for a
compact overview using shared S<n> style and repeat references.
For repeated explicit-node calls, phase, format and deduplication do not
trigger another full Figma walk. The in-memory Design Capture cache is bounded
to 8 entries / 8 MiB by default (DESIGN_CAPTURE_CACHE_ENTRIES and
DESIGN_CAPTURE_CACHE_BYTES). Every hit still probes the live document
revision; there is no TTL and no stale-while-revalidate path.
Code ↔ Figma design memory
Give each important component, screen or frame a durable Design Entity id.
The id describes the concept, not its current location: use names such as
ui.button, ui.account-card or screen.settings.
After selecting or identifying a Figma node, an agent can create the link with:
figma_run {args:["link","set","9:9","screen.settings","--kind","screen","--source","src/routes/settings.tsx","--export","SettingsScreen","--story","screens-settings--default"], confirm:true}This converges two small adapters:
figma-bridge.jsonis the committed, reviewable Registry with repo-relative code paths plus optional Storybook and Figma handles.Figma stores only
{version,id,kind}as plugin data on the node. It contains no local path, credential or machine-specific state.
Use figma_run ["link","inspect","9:9"] to resolve a node and
figma_run ["link","list"] to inspect the repository memory without reading
Figma. Once linked, figma_selection and figma_spec automatically expose the
same id and the Registry's code/Storybook targets. Agents should reuse or edit
that code component instead of creating a look-alike. Repeating the same set
command is safe and repairs either side after an interrupted write.
After visually verifying that code and Figma correspond, explicitly record their current fingerprints. Screen entities require a real browser screenshot and a passing pixel threshold:
figma_run ["link","accept","screen.settings","--compare","/abs/build.png","--max-diff","5"]Source code is never stored in the Registry. The initial code Adapter hashes
the complete linked file plus its export identity; therefore an unrelated edit
in a shared file may conservatively report a code change, but a real change is
never hidden. The Figma Adapter hashes the normalized linked subtree. For
Code-to-Figma nodes it also stores each unique figmaBridge.semanticPath with
that node's subtree hash. A later link status can therefore list the exact
added, removed or changed semantic paths and recommend node-scoped specs.
Changing a semantic marker alone does not change the visual fingerprint;
duplicate paths are reported as ambiguous instead of guessed.
figma_run ["link","status","screen.settings"]
figma_run ["link","context","screen.settings"]Status | Meaning |
| Neither side moved from the accepted baseline. |
| Only the linked code file moved. |
| Only the linked Figma subtree moved. |
| Both moved; neither side is overwritten. |
| No baseline has been explicitly accepted yet. |
link context is the preferred agent entry point after a link exists. It
returns the smallest relevant projection: entity, code/export, Figma root,
Storybook story, current Round-trip Plan, discovered DESIGN.md/token files
and exact next reads. It is generated on demand, not persisted as another
memory file. link accept writes only figma-bridge.json; it never changes
Figma or code. For screens it also stores the measured diff and SHA-256 hashes
of both comparison images, so a structural fingerprint cannot certify a
visibly wrong baseline.
Conventional DESIGN.md, design/DESIGN.md, tokens.json and
design/tokens.json locations are discovered automatically. Configure custom
repo-relative locations once when needed:
figma_run ["link","configure","--design-doc","docs/product-design.md","--tokens","src/theme/tokens.json"]Commit figma-bridge.json. Do not put secrets, absolute paths or generated
credentials in it.
Reviewed CSS ↔ Figma boundary strategies
Semantic Code-to-Figma uses stable policy ids rather than silent visual
substitutions: minmax.native-grid, space-around.equal-slots,
border.single-paint-native, sticky.metadata-only, filters.layer-stack,
masks.vector-mask, font.named-faces, and figma-effects.native. The full
matrix and its remaining hard stops live in
docs/css-figma-semantic-matrix.md.
Reviewed lossy policies can opt into an automatic native Figma annotation on
the exact affected semantic node. The annotation explains the unsupported CSS
fact, links the relevant Figma properties and is mirrored as versioned
figmaBridge.fallbackAnnotations plugin data for future agents. Equivalent
native conversions remain unannotated to avoid review noise. The first active
policy is border.single-paint-native: Figma receives the first explicitly
painted CSS side as the shared native stroke, retains all four side weights,
and marks strokes plus strokeWeight. Native renders report how many
fallback annotations were added, deduplicated or unsupported.
Intrinsic auto-sized DOM containers and their single-line text map to Figma HUG sizing when the parent does not stretch them. Explicit fixed dimensions stay fixed. Text that participates in an authored stretch/available-width or wrapping relationship maps to FILL instead; centered or end-aligned FILL text keeps that intent through the native Text alignment property. Positioned text keeps measured box geometry. The bridge does not add arbitrary percentage width headroom to prevent wrapping.
Variable-font axes are captured, but the structural gate asks whether the
required font should be installed or an available named face should be used
before rendering. Native Figma Glass remains an editable native effect with
all effect parameters retained; it is not silently treated as CSS
backdrop-filter, because Figma's CSS export does not expose those Glass
parameters.
Storybook mirroring
Figma components carry a stable publish key (survives library publishing;
node ids are file-local). The key now flows through figma_spec (canonical
structured model + the "Component sets used" tree trailer), figma_selection, component list, figma_inspect, and DESIGN.md.
To link them to their code mirror:
figma_run ["map", "storybook", "http://localhost:6006"]The conventional local origins localhost:6006, 127.0.0.1:6006, and
[::1]:6006 work without configuration. Approve an exact hosted HTTPS origin
or an intentional private-network origin before using it:
export FIGMA_BRIDGE_STORYBOOK_ORIGINS=https://storybook.example.com
export FIGMA_BRIDGE_STORYBOOK_PRIVATE_ORIGINS=http://storybook.internal:6006Configured values are exact origins, not wildcard URLs. A Storybook base path
such as https://storybook.example.com/design-system may then be passed to the
command. Every redirect must lead to another approved origin. You can also pass
a local Storybook directory; those reads stay inside the configured workspace
roots.
This matches the file's components against the Storybook index by normalized
name and writes figma-map.json into your project: Figma key ↔ story id /
import path, with a confidence per match plus both unmatched lists. Edit
entries by hand and set "matchedBy": "manual" to pin them — pinned entries
survive re-runs. When the file exists, figma_selection and figma_spec
annotate components with ↔ story <id> (<importPath>) automatically.
figma-map.json remains a legacy read adapter, so existing mappings continue
to work. New durable links belong in figma-bridge.json; link set never
copies legacy rows into it. Migrate a component when you next touch it by
assigning its real Design Entity id and passing its story via --story. Remove
the legacy file only after link list shows every mapping you still need.
Bring your own design system
This project ships no design system — no shadcn, no Tailwind preset, no icon pack. That is deliberate: a bundled system is someone else's opinion rendered into your file. What it ships instead is a way to make your system legible to an agent in one command:
figma_run ["kit", "init", "./my-app", "--storybook", "http://localhost:6006"]Four reads, one report:
Step | Result |
|
|
|
|
| inventory with stable publish keys |
|
|
It ends by naming what is still missing — an unmapped Storybook, components with
no story, the tokens sync command that keeps the two in step — because a setup
that quietly lacks the mapping looks finished until an agent needs it.
DESIGN.md is what an agent should read first; tokens.json is what it binds to.
Several files at once
The bridge holds one connection per Figma window in which you started the plugin. That is the consent model: a file is reachable because you opened it and launched the plugin there — not because a flag widened the scope.
One window — nothing changes. Commands go there.
Several windows — a command must name its target, or it fails with the list of connected files:
figma_status # lists every connected window figma_run {args: ["canvas","info"], fileKey: "GY5SasBJ…"} figma_spec {nodeId: "12:34", fileKey: "GY5SasBJ…"}figma_render,figma_selection,figma_inspect,figma_screenshot, andfigma_specaccept the samefileKeyparameter. A full Figma node URL also supplies its file key automatically. Without a target,figma_selectionsays which files are open rather than guessing. On the engine CLI the flag is--figma-file, not--file:evalandspecalready use-f, --filefor a local path.
There is deliberately no "all files" option. Every write names one file, so a mistaken command cannot fan out across a library. Two windows on the same file are indistinguishable for routing, so the newer one takes over and the older is told it lost the bridge. Audit entries carry only a redacted target summary, so multi-file operations remain correlatable without retaining a raw file key.
Reaching files you have not opened is out of scope: Figma's REST API cannot write document content, so a bulk rename across thirty library files is not something this tool can honestly offer.
FigJam
The plugin runs in FigJam boards too, over the same bridge — no second transport, no extra permission:
figma_run ["jam", "sticky", "Ship the handshake", "--color", "green"]
figma_run ["jam", "stickies", "[\"Discovery\",\"Build\",\"Ship\"]", "--columns", "3"]
figma_run ["jam", "shape", "Decide?", "--type", "DIAMOND"]
figma_run ["jam", "connector", "1:2", "3:4", "--text", "yes"]
figma_run ["jam", "table", "3", "4", "--data", "[[\"Step\",\"Owner\"],[\"Handshake\",\"Alex\"]]"]
figma_run ["jam", "board"] # read everything back, with connectors
figma_run ["jam", "arrange"] # arrange only the current selection
figma_run ["jam", "arrange", "--ids", "1:2,3:4"]
figma_run ["jam", "arrange", "--all"] # explicit: whole pageNew nodes land to the right of whatever is already on the board unless you pass
--at x,y, so an agent adding to a populated board does not stack everything at
the origin. Every command checks figma.editorType first and says "this is a
figma file, not a FigJam board" rather than failing on an undefined API.
figma_status reports which editor the bridge is attached to.
jam arrange is deliberately selection-scoped. Agents can pass exact node ids
without changing the user's selection; rearranging the whole page requires the
visible --all flag. Sections and connectors are never moved by this command.
The public surface was exercised in Figma Desktop on 2026-08-10. Maintainers
keep the detailed command and readback evidence outside the public repository.
Figma Slides beta
Slides uses the same authenticated plugin bridge and the same Semantic Render Plan; there is no separate presentation renderer. The surface covers deck structure, native slide properties, deck-wide reads, bounded batch edits, media insertion and one explicit render target:
figma_run ["slides", "inspect"] # deck grid, focus, labels, transitions
figma_run ["slides", "manifest"] # compact per-slide projection for review and version control
figma_run ["slides", "lint", "--fail-on-issues"] # duplicate-name, skipped-placement, missing-transition, empty-slide, unlisted-slide
figma_run ["slides", "create", "Agenda", "--row", "0", "--col", "1"]
figma_run ["slides", "duplicate", "Agenda", "--label", "Agenda alternative"]
figma_run ["slides", "move", "Agenda alternative", "1", "0"]
figma_run ["slides", "transition", "Agenda", "DISSOLVE", "--duration", "0.4"]
figma_run ["slides", "skip", "Appendix", "on"]
figma_run ["slides", "delete", "1:42"]
figma_run ["slides", "batch", "[{\"op\":\"create\",\"label\":\"Q&A\"}]"] # plan only
figma_run ["slides", "batch", "[{\"op\":\"create\",\"label\":\"Q&A\"}]", "--apply"]
figma_run ["slides", "media", "label:Agenda", "assets/loop.gif", "--fit", "FIT"]
figma_run ["slides", "focus", "label:Agenda"]
figma_run ["slides", "view", "single-slide"]
figma_run ["render", "<Frame>…</Frame>", "--slide", "12:34"] # render one plan into one slideFigma renumbers native slide names whenever the canvas grid changes. The
optional argument to create and --label on duplicate therefore store a
durable Bridge label in plugin data; inspect and manifest report both the
native name and the stable label. References resolve by id, exact native
name or label, then unique substring; typed node:, name: and label:
prefixes restrict the lookup. Ambiguity is an error, delete always requires an
explicit reference, and duplicate/move refuse a nonexistent target row rather
than accepting Figma's fallback placement.
render --slide <nodeId> takes a node id from inspect; it deliberately does
not accept a name or label, because a render must never pick its own target
when a reference turns out ambiguous. The root lands in the slide's own
coordinate space. render-plan-batch stays refused in Slides.
batch is a plan by default and performs nothing until --apply; every
reference and coordinate is validated against one grid snapshot first, a
coordinate that only becomes valid because of an earlier item is refused, and
the first failing item reverses everything applied before it. A created
slide whose grid position Figma has not listed yet is verified by identity,
label, focus and selection and reported as gridObserved: false.
media inserts a still image (IMAGE paint), an animated GIF (a playable
MEDIA node) or a video (VIDEO paint) into one slide. The file rides inside
the plugin payload as base64, so the ceiling is 3.5 MB of file bytes — the
same ceiling render and node set-image apply to images.
Two facts about Figma's canvas grid shape every command. figma.getCanvasGrid()
can lag the page tree by minutes: a slide that exists on the page may be
missing from the grid, or the grid may keep a removed slide's id and throw on
every read. The Bridge treats the grid as authoritative for coordinates and
the page tree as authoritative for existence. Readers list such slides under
unlisted with no coordinate (and say why when the grid cannot be read at
all), lint reports them as unlisted-slide, delete, transition, skip,
media and focus still reach them, and move refuses one with the reason
— without a coordinate there is no origin to restore. Every operation checks
figma.editorType === "slides" before touching a Slides-only API. Polls,
facepiles, embeds and speaker notes have no public create or read API; the
roadmap in docs/slides-roadmap.md records what was
probed against the live runtime, and editor acceptance is maintainer-verified
separately from the public repository.
Token sync (two-way)
tokens import only ever creates, so a value edited in code never reaches an
existing Figma variable and a value edited in Figma never reaches code.
tokens sync closes that loop:
figma_run ["tokens", "sync", "src/tokens.json"] # plan only
figma_run ["tokens", "sync", "src/tokens.json", "--apply"] # write itThe import surface is broader than the sync surface. One-shot import accepts
Tailwind v3 config, Tailwind v4/CSS, Storybook indexes, DTCG/W3C JSON, and the
DTCG-compatible token shapes exported by Style Dictionary and Tokens Studio.
That compatibility does not include Tokens Studio theme semantics or arbitrary
preprocessors; metadata such as $themes is ignored while token sets and
aliases are read.
Tailwind JS/CJS/MJS/TS configuration is parsed as static data. The importer
understands ordinary object exports, theme, theme.extend and typed
satisfies Config forms without resolving imports or executing plugins.
Dynamic expressions inside token-bearing theme fields stop with instructions
to import Tailwind v4 @theme CSS or resolved W3C JSON instead. Unrelated
fields such as content and plugins remain compatible but are discarded.
New FLOAT variables in explicit spacing/* or space/* namespaces are scoped
to Figma's GAP consumers only. radius/* and radii/* variables are scoped
to CORNER_RADIUS only. The inference is deliberately namespace-exact:
names such as spacingFactor are left at Figma's default scope, and rendering
does not silently change the scopes of existing user or library variables.
Numeric spacing and radius literals may reuse an exact generated variable only
when its identity and value are unambiguous. If duplicate collections expose
the same generated name, the native renderer keeps the exact visual literal
unbound and reports that decision; explicit var: references still fail
closed until their collection identity is resolved. If no exact generated
Variable exists, the literal remains literal unless the caller explicitly sets
materializeResources:true / --materialize-resources; the opt-in preserves
the former generated-token workflow rather than removing it.
Other new COLOR, FLOAT, or STRING variables surface SCOPE DECISION REQUIRED
with only the compatible Figma choices. The agent should ask before narrowing
them; inspect the catalog with figma_reference {name:"variable-scopes"} and
apply the answer with figma_run ["var","update","<name>","--collection", "<collection>","--scopes","TEXT_FILL,STROKE_COLOR"].
Safe three-way sync accepts only DTCG / W3C design tokens (.json, what
export dtcg emits) and CSS custom properties (.css, what export css
emits). Sass $variables are not CSS custom properties and .scss is refused
rather than partially parsed. Note that
export dtcg writes every local variable into one file while sync targets one
collection — pass --collection accordingly. If most names in the file already
live in another collection, sync says so instead of offering to duplicate them. Tailwind configs
are an import source only — their parser buckets values into
colour/spacing/radius and cannot round-trip, so sync refuses them by name
rather than silently dropping tokens it did not understand.
Why a lockfile. A two-way sync without memory cannot tell "the code
changed" from "Figma changed" — it only sees that the two differ, and whichever
direction it picks destroys the other side's work. figma-tokens.lock.json
records the state at the last successful sync, so every decision is a
three-way comparison:
code | Figma | result |
changed | unchanged | update Figma |
unchanged | changed | reported, never overwritten — update your code file |
both changed | conflict — nothing is applied | |
unchanged | unchanged | unchanged |
Commander and figma_run execute this comparison through the same closed
Command Plan. The source, prior lock and exact Collection are resolved before
Plugin readiness; duplicate Collection or Variable names and flattened DTCG or
CSS name collisions stop explicitly instead of selecting the first match.
For compatibility with the original sync command, an existing Collection uses
its first declared mode. If the Collection has more modes, sync says so; it
does not silently switch to defaultModeId or change values in the other
modes.
Conflicts stop the whole run. Resolve them by editing one side, or decide them
all at once with --ours (the code file wins) / --theirs (Figma wins, and
nothing is written to Figma).
Deletions need --prune, and even then only touch variables sync itself
created — a variable it never tracked is reported as untracked and left alone.
The lockfile also stores each variable's Figma id, which is what makes a rename one rename instead of a delete plus a create that would drop every layer binding. Pairing is by value and only when unambiguous: renaming and re-valuing a token in the same commit falls back to create + delete, so do those as two steps if the bindings matter.
Without --apply the command exits 1 when changes are pending, so it works as a
CI check for "is Figma in sync with the repo?".
Apply is intentionally non-atomic inside Figma: retype and delete have no
general safe inverse. It rechecks the complete observed state immediately
before the first write, stops after the first failed operation, verifies the
fresh post-state and reports no-mutation, bounded partial-residue, or
verification-unavailable. An apply is never automatically retried after
dispatch. The lockfile advances only after complete verified success. A
per-lockfile process lease serializes source/lock read, Figma work and
publication across CLI and MCP processes, and publication atomically replaces
the prior file from a fully written sibling. If publication fails, the prior
lock remains intact and the command reports any Figma changes that already
committed.
Binding, and switching which collection a design follows
tokens sync writes token values. Two neighbouring things it deliberately
does not do:
figma_run ["node", "bind", "12:34", "radius", "radius/lg", "--collection", "TARGET_COLLECTION"]
figma_run ["tokens", "rebind", "TARGET_COLLECTION", "--node", "12:34"] # plan
figma_run ["tokens", "rebind", "TARGET_COLLECTION", "--node", "12:34", "--apply"] # writenode bind attaches a variable to a property of an existing node — fill,
stroke, radius, gap, padding (or one side), opacity, stroke-width,
width, height. Its read counterpart is node bindings. The legacy
--batch mutation is currently disabled until it has a typed bounded request,
complete preflight and truthful rollback/residue result; run one explicit node
per command in the meantime.
For a bounded machine-readable read, add --json:
figma_run ["node", "bindings", "12:34"]
figma_run ["node", "bindings", "12:34", "--json"]The result preserves direct node.boundVariables topology: scalar aliases,
array positions such as fills.1, and component-property names. It also keeps
the variable id, name, resolved type and collection identity when Figma can
resolve them. This is intentionally a fast discovery outline, not proof of
every variable influencing the node. Use Design Capture/Code Spec for bindings
inside styles, paints, effects or text ranges, mode values, resolved values and
provenance.
A variable name that is not unique is refused, not guessed — this file has
radius/lg in two collections, and the answer names both so --collection can
settle it. The variable's type is checked against the property first, so a
COLOR on radius fails with a sentence rather than a plugin stack trace.
Typography variables have their own range-aware command because text can carry different bindings on different character spans:
figma_run ["font", "bind", "12:36", "fontWeight", "type/weight", "--collection", "Typography"]
figma_run ["font", "bind", "12:36", "line-height", "type/line-height", "--start", "0", "--end", "12"]
figma_run ["font", "unbind", "12:36", "lineHeight", "--start", "0", "--end", "12"]Bindable fields are fontFamily, fontSize, fontStyle, fontWeight,
letterSpacing, lineHeight, paragraphSpacing and paragraphIndent;
kebab-case spellings are accepted too. Existing fonts—and for family/style/
weight bindings the relevant available family styles—are loaded before the
binding is changed. Variable names are refused when ambiguous, and STRING vs
FLOAT is checked before Figma is called. A numeric fontWeight binding still
is not a general variable-font-axis setter: Figma selects a valid weight for
the active font.
tokens rebind is the theme switch: it walks a subtree and repoints every
binding at the same-named variable in a target collection. Design a card
against SOURCE_COLLECTION, run rebind with TARGET_COLLECTION, and the same
card follows the target collection's values — no redesign. It plans by default;
--apply writes. Tokens
with no counterpart in the target are listed and left pointing where they were,
so a partial theme is a report rather than a half-broken design.
Page-wide rebind remains available as a dry-run report, but --page --apply is
temporarily disabled. A scoped --node <id> --apply write remains available;
the page-wide write returns only after it has bounded traversal, exact
collection resolution, complete preflight and rollback evidence.
node set changes properties on nodes that already exist — fill, stroke,
strokeWidth, radius, opacity, x, y, width/height, name,
visible — one explicit node at a time:
figma_run ["node", "set", "12:34", "--name", "Card", "--radius", "12"]The legacy node set --batch path is disabled until FID-03 replaces it with a
typed bounded mutation Operation that preflights all entries and can report
rollback or residue without oversizing the Plugin response.
var delete-all is also disabled. Use exact var delete <names...> or
ambiguity-aware col delete <collection> so destructive scope is explicit.
Collection deletion always returns a non-mutating confirmation preview before
dispatch, even when global write confirmation is disabled. Prefer
col delete <collection> --only-if-empty when cleanup must never remove member
Variables: the Plugin verifies empty membership immediately before the native
irreversible removal and refuses a populated Collection without mutation.
A colour takes a hex or var:<name>. The difference is not cosmetic: a hex is
frozen, a var: reference stays bound, so a later tokens rebind can still
move it.
Local styles, variable metadata, and modes
The local design-system primitives that used to require manual UI work now have first-class Figma Commands. They use the live Plugin API, not REST:
figma_run ["style", "list", "--type", "TEXT"]
figma_run ["style", "show", "--type", "TEXT", "Heading/H1"]
figma_run ["style", "create", "PAINT", "Brand/Primary", "--properties", "{\"paints\":[{\"type\":\"SOLID\",\"color\":{\"r\":0.1,\"g\":0.3,\"b\":0.9}}]}"]
figma_run ["style", "apply", "Brand/Primary", "12:34,12:35", "--field", "fill"]
figma_run ["style", "consumers", "Brand/Primary"]
figma_run ["style", "publish-status", "Brand/Primary"]
figma_run ["style", "bind-font", "Body", "fontSize", "--variable", "type/size/body"]
figma_run ["style", "unbind-font", "Body", "fontSize"]style covers local PAINT, TEXT, EFFECT and GRID styles. update accepts the
same type-specific JSON properties as create; apply validates the style
type against fill, stroke, text, effect or grid. Name lookups refuse
ambiguity. Consumers come from getStyleConsumersAsync(), and publish state is
one of Figma's UNPUBLISHED, CURRENT or CHANGED values.
style show returns exact bounded Style Facts through the same safe-read
Application from CLI and MCP. It preserves ids/keys, plain and Markdown
descriptions, documentation links, authored PAINT/TEXT/EFFECT/GRID unions and
the scalar-versus-array topology of Variable Aliases. A GRID Auto count is
projected as { "$figmaScalar": "Infinity" } instead of the lossy JSON value
null. When the Figma runtime omits a typings-required plain or Markdown
description, Style Facts reports null instead of fabricating content.
--type is optional, but narrows name lookup to one style family and
avoids loading unrelated local styles. Resolved Variable values, consumers,
image/video bytes, shader definitions and complete Design Capture remain
separate reads. URL-shaped
style names remain payload and cannot retarget the Figma Command.
style list is the compact bounded catalog rather than a dump of every
authored value. It returns id/key/name/type/remote identities plus
total, returned, omitted, complete and a scope-bound next cursor.
Use --type TEXT (or PAINT/EFFECT/GRID) to load only one native family,
--limit 1..500 to size a page, and --cursor <next> to continue. The default
page size is 100. A continuation page remains complete: false because that
page alone is not the full catalog; next: null means no later page remains.
Use style show with an id for the complete authored facts of a row.
style consumers <style> is a separate long-budget read because Figma's
native getStyleConsumersAsync() scans the document and cannot be interrupted
with a visit limit. The result says this explicitly under scan, while
total, returned, omitted, complete and next describe the bounded
output page. --type, --limit 1..500 and --cursor mirror the catalog flow;
the cursor is additionally bound to the original style query. Rows contain
only consumer node identity and official inherited-style fields. No document
content is changed.
style publish-status <style> returns only compact Style identity and Figma's
official UNPUBLISHED, CURRENT or CHANGED state. It uses the same bounded
safe-read path from CLI and MCP; --type can narrow ambiguous names without
loading unrelated families. Complete authored values remain available through
style show, so publish-status never transports GRID Auto counts or other
irrelevant value topology through a lossy generic JSON path.
Variables expose the metadata and mode operations that token-file sync does not own:
figma_run ["var", "show", "space/md", "--collection", "Primitives"]
figma_run ["var", "create-batch", "--collection", "Primitives", "[{\"name\":\"color/neutral/0\",\"type\":\"COLOR\",\"value\":\"#ffffff\"}]"]
figma_run ["var", "create-batch", "--collection", "Roles", "[{\"name\":\"surface/default\",\"type\":\"COLOR\",\"alias\":\"color/neutral/0\",\"aliasCollection\":\"Primitives\"}]"]
figma_run ["var", "update", "space/md", "--description", "Medium spacing", "--scopes", "GAP"]
figma_run ["var", "update-batch", "[{\"variable\":\"space/md\",\"collection\":\"Primitives\",\"description\":\"Medium spacing\",\"scopes\":[\"GAP\"]},{\"variable\":\"space/lg\",\"collection\":\"Primitives\",\"hidden\":false}]"]
figma_run ["var", "set-value", "space/md", "12", "--mode", "Light"]
figma_run ["var", "set-value", "space/card", "--alias", "space/md", "--mode", "Light"]
figma_run ["var", "set-value-batch", "[{\"variable\":\"space/md\",\"collection\":\"Primitives\",\"mode\":\"Light\",\"value\":12},{\"variable\":\"space/card\",\"mode\":\"Dark\",\"alias\":\"space/md\"}]"]
figma_run ["var", "code-syntax", "space/md", "WEB", "var(--space-md)"]
figma_run ["var", "resolve", "space/md", "12:34"]
figma_run ["col", "mode-add", "Primitives", "Dark"]
figma_run ["col", "mode-rename", "Primitives", "Dark", "Dim"]
figma_run ["col", "extend", "Primitives", "Brand"]var show returns exact bounded Variable Facts through the same safe-read
Application from CLI and MCP: values by mode, alias ids, scopes, code syntax,
collection/mode identity and publish status. Variable and collection names must
resolve uniquely; --collection is the explicit way to disambiguate duplicate
variable names. For a color Alias whose opacity was authored in Figma,
valuesByMode retains Figma's native COMPOSE_COLOR expression and percentage.
The current public Figma Plugin API can read but cannot set that expression
(measured on 2026-09-03 with typings 1.136.0: setValueForMode throws
Composed color variable values are not supported, and the typings admit an
Expression only when reading valuesByMode): var set-value and
var set-value-batch therefore refuse before mutation when replacing such a
prior value, and exact Snapshot import refuses before Plugin readiness instead
of flattening it. Create the variable and its alias through the Bridge, then
set or detach the opacity in Figma until Figma exposes a writable public
setter. var create-batch accepts either value or an explicit
alias per item. aliasCollection scopes a target in another Collection;
same-batch forward references are also supported. Alias ambiguity, type
mismatch and cycles stop before creation, and equal literals are never inferred
as Aliases by the command. A workflow may supply an explicit alias after an
opted-in policy proves one exact compatible target; ambiguous candidates remain
a report rather than a guess. One selected mode is initialized per call, so separate mode intent
remains separate rather than being copied into empty modes. When metadata edits for several known local Variables are
already available, collect them into one var update-batch JSON array instead
of emitting one var update call per Variable. The batch preflights every
identity before its first write and owns reverse rollback under one confirmation;
keep var update for a single target. Batch several already-known mode-value
assignments into one var set-value-batch call instead of repeating var set-value. Its explicit best-effort-rollback policy does not reject
previously empty modes: successful writes commit normally; after a later
failure, prior values are restored and any value that Figma cannot remove is
reported as exact residue. This preserves normal utility without claiming
atomic rollback. Keep var set-value for a single pair. URL-shaped names remain payload and cannot
retarget the command. var resolve deliberately requires a consumer node because aliases
can resolve differently under that node's selected modes. Collection
show, update, mode-add, mode-rename, mode-remove and
publish-status follow the same ID/exact-name/unique-substring lookup policy.
Figma plan limits on mode count remain Figma-enforced and surface as errors.
Collection extensions use VariableCollection.extend() for local collections
and extendLibraryCollectionByKeyAsync() for published keys. Figma restricts
this feature to Enterprise plans; the CLI reports Figma's plan error unchanged.
Text-style bindings support exactly Figma's bindable typography fields:
family, style, weight, size, line height, letter spacing, and paragraph values.
Enabled team libraries
Library discovery and imports also stay on the authenticated plugin transport:
figma_run ["library", "collections"]
figma_run ["library", "variables", "Acme/Primitives", "--type", "COLOR"]
figma_run ["library", "import-variable", "<published-variable-key>"]
figma_run ["library", "import-style", "<published-style-key>"]
figma_run ["library", "import-component", "<published-component-key>"]
figma_run ["library", "import-component-set", "<published-component-set-key>"]collections and variables are reads. The four import-* commands
materialize published assets in the current file and are therefore writes in
the Capability Catalog. They share one non-retrying Command Application in CLI
and MCP: the selected native import runs once, then a fresh lookup verifies the
returned id, exact published key, resource kind and remote identity. Figma does
not expose a safe inverse and an imported resource may have existed already, so
failed or unavailable verification reports possible residue without deleting
or retrying the import. Resource descriptions are bounded in the structured
result. Figma only exposes discovery for variable collections and variables.
Published styles, components and component sets can be imported when their
stable key is already known, but the Plugin API cannot enumerate them.
Libraries must be enabled for the current file in Figma's UI before
library collections can see them; the Plugin API cannot enable a library.
The shipped plugin already declares the required teamlibrary permission.
Name lookup uses collection key, exact collection name, then an unambiguous
collection or library-name substring. Library discovery owns an 18-second
Plugin-API timeout below the Bridge deadline, so a stalled Figma library
request names the operation and suggests checking whether the library is
enabled instead of degrading into a generic execution timeout.
Prototypes, Dev Mode measurements, and annotations
These document features are also Plugin-API-first:
figma_run ["prototype", "inspect", "12:34"]
figma_run ["prototype", "add", "12:34", "--trigger", "click", "--navigate-to", "12:36"]
figma_run ["prototype", "set", "12:34", "--json", "[{\"trigger\":{\"type\":\"ON_CLICK\"},\"actions\":[{\"type\":\"BACK\"}]}]"]
figma_run ["measure", "add", "12:34:right", "12:36:left", "--offset", "16", "--text", "gap"]
figma_run ["annotate", "categories"]
figma_run ["annotate", "add", "Review spacing", "--node", "12:34", "--category", "Review", "--properties", "width,fontSize"]
figma_run ["annotate", "edit", "12:34", "0", "--text", "Resolved"]prototype set --json is the lossless form for Figma's multiple actions,
SET_VARIABLE, SET_VARIABLE_MODE, and conditional blocks. It writes through
setReactionsAsync() so dynamic-page manifests are supported. Measurement
writes are guarded to Figma Dev Mode and use PageNode's native measurement
methods. Annotation indexes are zero-based; custom category create/edit/remove
commands are available alongside categories. These manual review notes are
independent from the semantic renderer's automatic Boundary Fallback
Annotations, which are emitted only by explicitly opted-in lossy mapping
policies and remain machine-readable through plugin data.
prototype inspect is the read-only verification path shared by CLI and MCP.
It preserves the complete authored reaction JSON while enforcing explicit
depth, collection, value, string and total serialized-size bounds below the
Plugin protocol cap. Oversized or malformed facts fail rather than being
silently truncated, and safe JSON escaping keeps terminal controls inert
without changing the parsed interaction facts.
prototype add, set, and clear use one non-retrying mutation Application
in both CLI and MCP. Reaction JSON is bounded before dispatch, the previous
reaction set is journaled, and fresh readback proves the exact requested set.
Failed verification restores the journal when possible and otherwise reports
bounded reaction residue; an exact readback after a native setter error remains
an explicit verified-after-error success.
2026 Plugin APIs: video, shaders, grid, slots, and Draw
The current official Plugin API surface is exposed as Figma Commands rather than REST calls:
figma_run ["export", "video", "12:34", "--format", "mp4", "--fps", "30", "-o", "/abs/demo.mp4"]
figma_run ["shader", "list"]
figma_run ["shader", "import", "<shader-id>"]
figma_run ["shader", "apply", "12:34", "<shader-id>", "--field", "fill", "--properties", "{\"definition-id\":0.8}"]
figma_run ["layout", "grid", "set", "12:34", "--rows", "2", "--columns", "3", "--row-gap", "12"]
figma_run ["layout", "grid", "auto-flow", "12:34", "--auto-tracks", "rows", "--positioning", "row_auto_flow"]
figma_run ["layout", "grid", "reorder-rows", "12:34", "--from", "0,2", "--to", "3"]
figma_run ["grid", "list", "12:34", "--json"]
figma_run ["slot", "create", "12:37", "Content", "--settings", "{\"minChildren\":1,\"maxChildren\":3}"]
figma_run ["slot", "validate", "12:37"]
figma_run ["draw", "inspect", "12:38"]
figma_run ["draw", "text-path", "12:38", "--text", "Around the curve"]
figma_run ["draw", "stroke-profile", "12:38", "--preset", "TAPER"]
figma_run ["draw", "pattern", "12:38", "12:39", "--field", "fill"]Video export resolves a selected descendant to its top-level animated frame
and accepts only Figma's format-specific FPS values. CLI and MCP share one
long-running, non-retrying Command Application whose Plan owns the exact output.
It validates requested settings, canonical bounded Base64 and the MP4, GIF or
WebM container before Workspace I/O. The current generic result transport
supports exports up to its documented bounded payload; larger video streaming
requires a separate versioned media-stream capability rather than widening the
image/SVG Asset Stream. Shader properties are
keyed by definition ID, not display name, and an available shader must be
imported before it is applied. Shader import and application use the same
non-retrying verified mutation path in CLI and MCP: apply never imports
implicitly, and a failed postcondition either restores the complete prior
paint/effect field or reports possible residue. layout grid means the Auto
Layout Grid model; the older top-level grid command remains Layout Guide
management.
layout grid set uses one journaled, non-retrying CLI/MCP path for Grid
conversion, dimensions, gaps and explicit track definitions. It refuses a
manual shrink beneath an occupied child span, verifies the complete requested
configuration while preserving unspecified facts, and restores prior Grid or
free-layout state on failure when Figma permits it.
layout grid auto-flow uses one non-retrying CLI/MCP mutation path: it treats
auto-track and item-positioning settings as one intent, verifies both through
a fresh node read, and restores both prior values on a mismatched result or
reports the exact remaining field residue.
layout grid place likewise shares one non-retrying CLI/MCP path. It validates
manual Grid bounds and occupied cells before moving the child, freshly proves
the resulting hierarchy, cell, spans and alignment, and restores the complete
prior parent/free-layout or Grid state when the postcondition fails.
layout grid reorder-rows|reorder-columns shares one irreversible,
non-retrying path. It validates the native track permutation and freshly proves
the track definitions plus every child anchor/span. A thrown call is reported
as no mutation only when fresh state proves the complete old Grid; otherwise
confirmed differences remain explicit residue because the Plugin API exposes
no safe general inverse.
grid list [nodeId] is the exact bounded read for those layout guides and may
use the current selection when the id is omitted. It preserves gridStyleId,
all ROWS/COLUMNS/GRID fields, colors and Variable Aliases. Figma's Auto count
is returned as { "$figmaScalar": "Infinity" }, never the lossy JSON value
null; text output renders that value as Auto.
Slots expose GA SlotSettings, preferred values, reset, and limit violations.
Create and edit share one non-retrying, freshly verified mutation path in CLI
and MCP; failures remove only newly created Slot identities or restore the
prior effective definition when Figma permits it. Reset is also non-retrying
and reports native confirmation plus fresh before/after facts; it does not
claim an independently known default subtree or rollback. JSX <Slot> uses
ComponentNode.createSlot() and validates configured limits after rendering.
An empty Slot on a main Component can have no reported
violation even when minChildren is nonzero; instantiate the Component and run
slot validate on that exercised Instance to observe BELOW_MIN. Component
Property Facts normalize Figma's missing SLOT defaultValue to null and
nullable descriptions, preferred values and settings to their complete
effective defaults.
When generating a library from code, decompose each rendered subtree from the
leaves upward before creating the wrapper. Repeated buttons, icons, avatars,
badges, rows and card shells should remain nested reusable Components. Map
editable copy to TEXT, optional descendants to BOOLEAN, replaceable children to
INSTANCE_SWAP, state/size/tone to VARIANT and structurally flexible regions to
native SLOT. A visually polished wrapper with inert descendants is not a
finished code-to-Figma component contract.
Draw commands cover text paths, repeat transform
groups, stretch/scatter/dynamic strokes, variable-width profiles, and async
pattern fill/stroke setters. Run the corresponding inspect/validate read
before writes when modifying an unfamiliar document.
Finding what needs fixing
figma_run ["analyze", "lint", "--node", "12:34"]One pass for the four things a design-system review acts on: colours that match
an existing variable but are not bound to it, layers still carrying a default
name, text with no style, text under 12px. --fail-on-issues makes it a CI
gate; --kind narrows it; --json never truncates.
A hardcoded colour is only reported when a variable already holds that exact value — otherwise the finding is noise you cannot act on. Because the match is known, each one arrives with the command that fixes it:
unbound token colour — 1
12:35 Badge fill is #8a9a8d, which is sage/400
fix: node bind 12:35 fill "sage/400" --collection "Sprout Primitives"analyze colors|typography|spacing still give the full census. Lint is the
pass that answers whether anything needs doing at all.
Variable fonts and OpenType facts
Figma does not expose a general variation-axis tuple through the Plugin API. The bridge therefore separates facts Figma actually reports from axis intent that a caller records explicitly:
figma_run ["font", "inspect", "12:36"]
figma_run ["font", "inspect", "12:36", "--start", "0", "--end", "12", "--all-open-type"]font inspect returns styled text ranges with fontName, numeric read-only
fontWeight, size, enabled OpenType feature tags and resolved typography
variable bindings. --all-open-type also includes false feature values. The
result names the API limit explicitly: a reported fontWeight is not a general
wght/wdth/opsz/custom-axis tuple, and OpenType features are read-only.
Typography Variable bindings use the same explicit node/range boundary:
figma_run ["font", "bind", "12:36", "fontSize", "type/body", "--collection", "Typography"]
figma_run ["font", "bind", "12:36", "lineHeight", "type/leading", "--start", "0", "--end", "12"]
figma_run ["font", "unbind", "12:36", "lineHeight", "--start", "0", "--end", "12"]font bind and font unbind are non-retrying writes shared by CLI and MCP.
They preflight type compatibility, fonts, ambiguity and the prior binding, then
freshly read the exact node/range binding after the setter. A silent no-op or
wrong Variable id is a failure, not success; the prior binding is restored and
verified where possible, otherwise the result reports bounded residue.
When the exact axis values are known from the UI or another font tool, preserve them on the text node as range metadata:
figma_run ["font", "remember-axes", "12:36", "wght=357,wdth=82", "--start", "0", "--end", "12"]
figma_run ["font", "axes", "12:36"]
figma_run ["font", "forget-axes", "12:36", "--start", "0", "--end", "12"]
figma_run ["font", "forget-axes", "12:36"] # clear every stored rangeremember-axes changes plugin metadata only — never the font or rendered
glyphs — and is therefore classified as a write by the Capability Catalog.
figma_spec carries these records as axes-meta[start:end](tag=value,…), plus
Figma's reported fw… value and enabled ot(…) tags, so design-to-code capture
does not silently discard the documented intent.
Native Plugin API facts
Two read commands expose Figma's own representations without contacting the REST API:
figma_run ["node", "css", "12:34"]
figma_run ["node", "css", "12:34", "--json"]
figma_run ["export", "node-json", "12:34"]
figma_run ["export", "node-json", "12:34", "-o", "facts/card.json"]node css calls getCSSAsync() and returns the declarations Figma exposes for
its Inspect panel. This is deliberately separate from export css, which
exports design-token custom properties. Its text and JSON projections share
one target-bound, bounded safe-read Application; an oversized or malformed
Plugin result is refused rather than silently truncated. export node-json
uses exportAsync({format:"JSON_REST_V1"}): Figma returns the official REST
nodes response envelope with the requested node under document, alongside
components, componentSets, schemaVersion and styles. The bytes
come from the live plugin document and need neither a token nor a network
request. CLI and MCP share one target-bound safe-read Application: the Command
Plan owns the optional output file before readiness, and bounded
document.id/type, JSON structure, transport size and formatted output are
validated before stdout or Workspace publication.
Version history and diffs
Figma's plugin API can write a version but not read one back, so "what changed
since this morning" has no answer from the bridge alone. history supplies one
without any credential: record the structure of a subtree, record it again
later, diff the two.
figma_run ["history", "save", "Before refactor", "--description", "Agent restore point"]
figma_run ["history", "snapshot", "--label", "before refactor"]
# … agent works …
figma_run ["history", "diff", "latest", "live"]history save creates the named entry directly through
saveVersionHistoryAsync() and is a Figma write. snapshot, list and diff
remain local/read-only Figma operations; reading Figma's native historical
versions still requires the optional REST add-on.
A snapshot stores one normalized record per node — geometry, layout, paints,
typography, component keys — plus a content hash and a subtree hash, so the
differ can report an untouched section instead of walking it. They live in
~/.figma-bridge-mcp/snapshots/<fileKey>/, gzipped, newest 20 kept. The
Structural Snapshot Store keeps its directories 0700 and files 0600, and
bounds both compressed and expanded data before parsing.
Refs are latest, previous, a bounded index from history list, an exact or
unique-prefix filename, or live for the document right now. Local refs never
accept host paths or escape the owning file namespace. The report separates
added, removed, replaced, moved and changed — that last distinction is the one that
matters in practice: an agent that deletes a frame and re-renders it keeps the
name path but gets new node ids, and without the replaced-detection every
re-render would read as a hundred deletions. --changelog emits markdown
instead; diff exits 1 when anything differs, so it also works as a CI gate.
Via MCP this is a parameter, not a thirteenth tool:
figma_history {diff: {from: "latest", to: "live"}}
figma_history {diff: {from: "version:1234", to: "version:5678"}} # REST add-onversion: refs go through the REST layer and diff what designers saved, using
the same differ. The two sources cannot be mixed in one diff: a REST document
and a plugin snapshot expose different properties, so every node would look
changed — the tool says so rather than producing a misleading wall of output.
Motion
Figma Motion (Config 2026 Beta) is reachable through figma_run with
["motion", …]: keyframe tracks (add), whole specs from JSON (apply),
named presets (preset), choreographed offsets across nodes (stagger),
Figma's first-party animation styles (styles, style), frame duration
(timeline), readback (inspect) and removal (clear).
Like every other command it runs over the plugin bridge — there is no separate
transport for it. styles, inspect, and timeline without --duration are
reads. timeline --duration … and every other Motion action count as writes
under FIGMA_WRITE_CONFIRM=1.
Motion is rolling out behind a Figma Beta flag. Without access, the commands
fail with a named MOTION_DISABLED error telling you to update Figma Desktop
rather than a generic API failure.
Motion also travels with the design in both directions:
Figma → code.
figma_specandexport code-speccapture a layer's native keyframe tracks and project them to CSS: every animated layer gets amotion:segment and the spec ends with a## Motion (implement as CSS)section holding one@keyframesblock andanimationdeclaration per layer, using Figma's own easing curves. Springs, Variable-bound easings and fields CSS cannot express are listed as limitations; Figma timelines play once, so loops (iteration-count,direction) stay a code decision, and aprefers-reduced-motion: reduceoverride is expected. The JSON/YAML model carriesmotionwith the samecssprojection.Code → Figma. The browser capture expression reads each element's CSS animation facts and the referenced
@keyframes, pauses playback while it captures resting geometry, andrender --dom-capturewrites the animation as native Motion tracks on the created layer (negative delays become phase shifts,alternatebecomes a two-period timeline). The render result reportsMotion: N native keyframe track(s) …plus any layer Figma refused.
Security model
No Figma API token required — Figma is driven through the local plugin, never
api.figma.com. The REST add-on is strictly opt-in: without a token the code path is inert, and with one the token lives in a 0600 file (or your own env var), not in the MCP client config.No binary patching — Yolo/CDP mode is stripped from the vendored engine.
Capability-gated commands —
figma_runonly accepts Commands exposed by the Capability Catalog;connectis not exposed, so Safe-Mode-only connection is enforced. The same resolved plan drives the write-confirm gate, target requirement and retry policy, preventing adapter drift.Target/editor-bound dispatch — every Plugin action carries a Dispatch Receipt derived from the resolved connection. Stale epochs, wrong files, wrong editors and missing capabilities fail before mutation; malformed or mismatched terminal receipts are outcome-unknown and never authorize a write retry.
Owned Structured Render rollback — native single and batch renders preflight every plan through one shared mutation-free resource catalog, then journal only Command-created variables, collections, Text Styles, image handles and scene nodes. Failures clean removable entries in reverse order; non-removable or failed cleanup is reported as bounded residue rather than false atomicity.
Bounded Semantic Render outcomes — the Plugin response must match one closed, versioned and deeply bounded result before any Adapter can report success. CLI text, MCP structured content, JSON/YAML, Audit completion and process code project the same root, structural, resource and mutation facts; stacks, source payloads, file keys, credentials and arbitrary fields cannot enter that result.
One compound-render lifecycle — wrapper auto-split and
render-batchuse the same closed request through CLI and MCP. Gap/direction, placement, presets, collections, Registry links, icons, fallbacks, local assets and optional conversion/resize/verification stages are retained explicitly. Roots prepare before readiness, the visible render dispatches once, and bounded stage evidence preserves exact known partial handles on failure.Host-materialized remote images —
figma_rendernever gives the Plugin a remote URL. Explicit exact-origin policy, DNS/address checks, connection pinning, redirect reauthorization, identity encoding, byte/time limits and raster validation complete on the host inside the Command deadline. Local and remote rasters then enter the same embedded, content-hashed Plan shape; repeated references are read once per Command.No shell — the engine is spawned with
execFile(shell:false).Two-layer daemon auth, no secret on the wire — signed HTTP requests (per-request HMAC over method/path/body, keyed with the session token, nonce replay guard) + a mutual challenge-response handshake on the plugin socket (
Origin/Hostallowlisted). Neither the session token nor the access key is ever transmitted in either direction — see Handshake.Localhost-locked plugin —
plugin/manifest.jsonrestrictsnetworkAccess.allowedDomainsto WebSocket and discovery HTTP traffic onlocalhost:3456–3460only.Isolated state — token, pid, key, and Audit Trail live under
~/.figma-bridge-mcp/, separate from any upstream figma-ds-cli install. A source checkout can additionally selectFIGMA_BRIDGE_PROFILE=local-developmentfor a distinct namespace and a daemon/plugin pinned to:3460while the released profile remains active. Its importable bundle is generated below the gitignoredplugin/local/directory; trackedplugin/manifest.jsonalways remains the normal released identity.Private Audit Trail — commands attempt a closed, versioned start/outcome lifecycle in
~/.figma-bridge-mcp/audit.log. Capability, effects, redacted data summaries, outcome, duration, terminal Dispatch state and bounded mutation attempted/restored/residue counts remain useful tofigma_history; raw commands, JSX, labels, paths, file keys, REST bodies, credentials, and errors or residue identities are never retained in new records. CLI errors and MCP structured errors consume the same closed mutation projection. Directory/file modes are0700/0600. It rotates at 5 MB with one previous generation, which also bounds readable legacy entries. Storage failure never blocks Figma work: MCP output reports a degraded Audit Trail until a complete record succeeds.
Port fallback. The daemon binds the first free port in 3456–3460 and
publishes it in ~/.figma-bridge-mcp/daemon-port; the CLI/MCP layers resolve the
port per call (env DAEMON_PORT > port file > 3456), and the plugin scans the
whole range, so a foreign process squatting 3456 no longer blocks connecting.
After three bounded WebSocket scans, quiet /plugin-ready probes discover a
returning daemon every three seconds without flooding Chromium with refused
socket attempts. If the daemon is reachable but this iframe still cannot open
its authenticated socket, the panel says that this Figma window is
disconnected instead of claiming MCP is absent; another Figma window may still
be connected. The discovery beacon still exposes no connection state or
identity. A normal figma_connect is idempotent and preserves an
authenticated, responsive socket. If that one socket is open but a live
round-trip fails, it asks the persistent Figma plugin thread to recreate only
the UI iframe; if the relay cannot answer, it resets that stale socket so quiet
discovery can reconnect. Only explicit key rotation or daemon restart
replaces the daemon. Stop/restart signals are sent only after the PID file and
the published listening socket identify the same process, and escalation is
limited to that listener PID rather than every client sharing the port.
The squatter check is an unauthenticated /health probe, and authenticated
requests are HMAC-signed — a squatter on a range port sees neither the session
token nor anything replayable (signatures bind timestamp, nonce, method, path
and body; the daemon rejects reused nonces). The plugin socket is safe on any
range port for the same reason: the handshake below carries no secret and binds
the port it ran on. Setting DAEMON_PORT explicitly disables the fallback;
values outside 3456–3460 are unsupported — the plugin manifest is
Figma-enforced and cannot reach them.
Handshake
The plugin socket runs a mutual challenge-response (proto 4,
engine/src/lib/plugin-handshake.js):
daemon → plugin {type:'challenge', proto:4, nonce:<dNonce>, port:<bound>}
plugin → daemon {type:'hello', proto:4, nonce:<pNonce>, version, capabilities, proof}
daemon → plugin {type:'hello-ack', connectionEpoch, proof, restTokenConfigured}where proof = HMAC-SHA256(access key, transcript). The plugin proof binds both
nonces, the bound port, the plugin version and its canonical capability list;
the daemon proof binds both nonces, the port and its random connection epoch
with a distinct role label and nonce ordering, so neither proof can be replayed
as the other. Five properties follow:
The key never crosses the wire. A process that binds a range port before the daemon and records the whole exchange learns one HMAC over nonces it will never see again. This retires the residual risk earlier versions documented, where the raw key was the first frame the plugin sent.
The daemon proves itself too. Before proto 2 the plugin trusted whatever answered and would run any
evalit was sent — impersonating the daemon needed no key at all. The panel now refuses every command until the ack verifies.The bound port is inside the transcript and checked against the actual socket. A squatter on 3456 that forwards a challenge from the real daemon on 3457 is rejected before the plugin sends a proof. Rewriting the challenge to 3456 instead makes the daemon's verification fail.
Capabilities are authenticated. The daemon verifies the exact canonical capability claim the plugin signed, so a relay cannot add a newer write-lane safety capability to a stale plugin.
The connection epoch is authenticated. Every Dispatch Receipt uses the epoch signed by the daemon ack, so a result retained by the Plugin main thread across an iframe reconnect cannot impersonate a request from the new socket.
Write-lane settlement survives an iframe reconnect. The Plugin main thread keeps awaiting an uncancellable Figma operation after its caller times out, then sends
lane-settledwith the exact receipt. If the UI socket was disconnected, the same main thread replays that proof after reauthentication. Reconnect alone never releases quarantine; a new runtime lacks the random request identity. If a runtime was forcibly terminated before it could report settlement, recovery requires first stopping that old Plugin runtime, then explicitly restarting the Bridge Daemon—closing only the socket is not sufficient proof.Status proves more than socket presence. Its bounded health operation is a closed typed read with no JavaScript payload and never enters the write lane. A responsive new connection epoch may release stale slots belonging only to non-mutating reads from an older epoch. Unknown writes remain quarantined until their exact settlement arrives; an attempted retry that reaches its deadline reports duplicate risk and tells the caller to inspect the targeted file before authoring another mutation.
There is no fallback to an older handshake protocol. figma_connect refreshes the installed plugin
files on every run. Figma may continue using an application-cached plugin
build, so the authenticated handshake reports the imported build separately
from the bundled build. figma_status then gives an explicit re-import path
instead of treating the mismatch as a generic connection failure.
Multiple Figma MCP servers
Figma Bridge can coexist with another Figma MCP. Its registered server
namespace is figma-bridge and every tool result carries
_mcp: "figma-bridge-mcp" plus protocol metadata that scopes failures to this
server. Errors also repeat that a failed Bridge call does not establish that
Figma or another Figma MCP is unavailable.
The bundled skills pin their workflows to Figma Bridge and begin with this
server's figma_status. They never silently switch to a different MCP for a
write, because another server may use different targeting, approval, retry and
security semantics. An MCP cannot control which tool a host selects before the
first call, so a generic task with several Figma servers remains a host/agent
routing decision; once a Bridge skill is selected, the server identity and
workflow contract make that route explicit and testable.
The panel carries its own SHA-256/HMAC implementation: the plugin UI is a
sandboxed null-origin iframe, where WebCrypto availability is not ours to
guarantee, and a silent fallback to something weaker is the worst outcome for an
auth handshake. tests/plugin-handshake.test.js runs that shipped code against
Node's crypto so the two implementations cannot drift apart.
Known limitations
Figma can terminate the complete plugin runtime. Reconnect loops and UI reloads work only while at least the plugin main thread is still running. If Figma destroys both the iframe and main thread, no plugin JavaScript remains that could reopen itself; start Plugins → Development → Figma Bridge once in that file. The stored access key is reused and discovery reconnects it automatically, so no new pairing is needed.
Figma Slides is beta and deliberately bounded. Grid inspection, slide create/duplicate/move/delete, skip state and transitions are supported. Speaker notes, interactive polls/embeds, presenter controls and a complete content-authoring workflow are not. See the Slides roadmap for actionable candidates versus Plugin API boundaries.
Non-localhost network actions are few and explicit:
api setuphas no network effect; it verifies the exact official@figma/plugin-typingsdependency and atomically builds a private offline reference forfigma_reference. External access is limited to the explicitly approved Storybook origin used byimport/map storybook(or the local directory you pass), and — only when you opt into the REST add-on — calls toapi.figma.com. Nothing else talks to the network — the upstream's iconify/unsplash/remove.bg/screenshot-url integrations were removed entirely;<Icon>infigma_renderJSX renders as a named placeholder (real icons come out of the Figma file viaexport assets).One transport, no CDP remnants. Every command reaches Figma the same way: engine → daemon → plugin eval. The upstream's Chrome-DevTools client, its
figma-useshell round-trip, the binary-patchinginitwizard and thefigma-usedependency are all gone (~5,600 lines removed), so there is no second code path that could bypass the plugin bridge.
Development
npm run check:contracts # separate host + Figma Plugin static contracts
npm run check:plugin # generated Plugin runtime has no source drift
npm run check:architecture-latency # warmed latency budget in an idle process
npm run measure:architecture # context, payload and local latency baselines
npm run build:package-artifact -- --output-dir release-artifact # reproducible reviewed tarball + evidence
npm test # all contracts and regression suitesThe current domain language lives in CONTEXT.md, API coverage in
docs/figma-plugin-api-coverage.md. The public
documentation index is docs/README.md.
Avoid running an upstream figma-cli at the same time. The daemon now falls
back within 3456–3460 when 3456 is taken, so both can coexist, but the plugin
scans the whole range and the two daemons use different access keys — which one
the plugin reaches first is a coin toss. This build isolates its own
token/pid/port files under ~/.figma-bridge-mcp/.
License
figma-bridge-mcp is released under the MIT License. It is provided
"as is", without warranty; the exact warranty and liability terms are in the
license itself. Third-party copyright and license notices are retained in
NOTICE and engine/LICENSE.
Inspiration & attribution
Two projects shaped this one, in different ways.
figma-cli (Sil Bormüller) is where
the engine/ directory comes from: it was vendored at v2.1.0 in July 2026 and
has diverged since — the CDP transport and the binary-patching installer are
gone, the plugin socket is authenticated, and most of what the engine does now
was written here. The remaining explicitly derived source files have all
diverged from their upstream bytes. The upstream MIT license is retained in
full at engine/LICENSE, and NOTICE records what
changed.
Attribution inventory: 3 retained modified-derived source files, 0 byte-identical source files, and 1 verbatim upstream license file.
The exact revision, paths, and reference hashes are machine-verifiable in
UPSTREAM_PROVENANCE.json.
figma-console-mcp contributed an idea rather than code: that a Figma bridge can be genuinely local — a plugin socket on the loopback interface, no cloud relay, no patched binary. Nothing here is derived from its source; the tool surfaces, the transport and the plugin are unrelated. Where this project differs is that the socket also proves who is on the other end of it.
Plugin identity. The development manifests use the product-aligned ids
figma-bridge-mcp and figma-bridge-mcp-dev. Figma scopes clientStorage —
where the paired access key lives — to the plugin id and, in the desktop
editors, separately across Design, FigJam and Slides. Installations from before
0.5.0 therefore need to re-import the manifest; each editor in use needs the
same Bridge access key saved once.
Available Tools
12 toolsfigma_commentsA
REST comments: list feedback; post/reply always previews and needs confirm:true. Missing token returns setup.
| Name | Required | Description | Default |
|---|---|---|---|
| x | No | post: canvas x (with y, when no nodeId) or node offset x. | |
| y | No | post: canvas y / node offset y. | |
| action | No | list (default) or post. | |
| nodeId | No | post: node anchor (id or URL). | |
| confirm | No | Required true to actually post — first call without it returns a preview. | |
| fileKey | No | File key/URL; defaults to the open file. | |
| message | No | post: the comment text. | |
| replyTo | No | post: comment id to reply to (threads under it). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds meaningful behavioral information beyond the annotations: all post/reply calls preview and require confirm:true, and a missing token returns setup. The annotations only indicate readOnlyHint=false, destructiveHint=false, and openWorldHint=true, so the confirmation and auth details are valuable additions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loads the purpose and critical confirm behavior. Both sentences earn their place, though 'REST comments' is slightly cryptic and the semicolon-heavy first sentence could be clearer as a full phrase.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The schema covers parameters well and the description adds critical behavioral details, but with no output schema the return values are not described: no mention of what list returns, what a preview response looks like, or pagination. For an 8-parameter tool with multiple modes, this leaves some gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all eight parameters. The description mentions confirm:true, but the schema already explains the preview and confirmation flow; no additional parameter semantics are provided. Baseline 3 is appropriate because the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description identifies the resource (comments) and the core actions: list feedback, and post/reply with preview behavior. It is clear enough to distinguish this from sibling tools like figma_inspect or figma_spec, though 'REST comments' is a bit terse and doesn't explicitly say 'Figma file comments'.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives practical usage context: listing is the default action, while posting/reply always goes through a preview and requires confirm:true. It also notes the missing-token setup response. It does not explicitly compare to alternatives, but no sibling tool appears to handle comments, so this is acceptable.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
figma_connectC
Connect Safe Mode; recover the plugin, create its key, or show setup.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations are present: readOnlyHint=false, openWorldHint=true, destructiveHint=false. The description adds that it may 'recover', 'create its key', or 'show setup', suggesting a setup/recovery operation with possible side effects (creating a key). While it doesn't conflict with annotations, it doesn't clearly describe side effects or what 'connect Safe Mode' entails; openWorldHint=true is consistent but vague. No contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is short and front-loaded with the main verb. However, the semicolon list of three actions is somewhat compressed and could be more explicit, but it is not bloated.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given that this is an action-y tool with zero parameters, the description is too vague about what 'Connect Safe Mode' actually does, what the outcomes are, and how it differs from figma_pairing/figma_status. An agent would not know what to expect after calling it. The output schema is missing, which increases the need for behavioral context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has zero parameters, so the description carries no parameter burden. Baseline for 0 params is 4. The description's ambiguity about actions is not necessarily about parameters, so this score is fair.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Connect') and resource ('Safe Mode'), and mentions three possible actions: recover the plugin, create its key, or show setup. However, it is ambiguous what 'connect safe mode' actually means and which of these actions will occur on invocation. It distinguishes a bit from siblings like figma_pairing and figma_status, but not clearly enough.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool versus alternatives like figma_pairing or figma_status. The description implies it is for a setup/recovery scenario, but it doesn't state conditions, prerequisites, or when to prefer another sibling.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
figma_historyBRead-only
Private command history, git/Figma versions and snapshot diff; payloads omitted.
| Name | Required | Description | Default |
|---|---|---|---|
| diff | No | Diff live/latest/previous, an index, or a Figma version. | |
| limit | No | Default 20; max 200. | |
| format | No | Default markdown. | |
| nodeId | No | Node-id filter. | |
| fileKey | No | Figma key/URL for versions. | |
| gitPaths | No | Repo-relative paths for git history. | |
| repoPath | No | Repo root for gitPaths. | |
| includeVersions | No | Merge Figma versions via optional REST. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover readOnly/openWorld/non-destructive, and the description adds useful behavior: 'private' scopes the history and 'payloads omitted' discloses a response constraint. It does not detail auth/rate limits or the optional REST merge, but with annotations present this is a reasonable level of disclosure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Twelve words with no filler; the opening 'Private command history' sets scope immediately and the trailing 'payloads omitted' is a high-value boundary. Every phrase earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite rich schema and annotations, this is an 8-parameter multi-mode tool with no output schema, and the description gives no mode-selection logic between diff, fileKey/includeVersions, and gitPaths/repoPath. It also does not clarify what 'private' entails or what the output shape looks like beyond payloads being omitted, leaving the agent to infer the intended workflow.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline applies; to/from defaults, nodeId, fileKey, gitPaths, repoPath, format, limit, and includeVersions are already explained in the schema. The description's mention of versions and diffs maps to those parameters but adds no parameter-level detail beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names concrete resources—private command history, git/Figma versions, and snapshot diff—so an agent understands what domain the tool addresses. It lacks an explicit verb like 'retrieves' or 'lists' and does not name a sibling, but the resource set is specific enough to separate figma_history from figma_screenshot, figma_comments, and similar siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No sentence explains when to use this tool or when to prefer one of its sibling tools. The capability list implies a history/diff use case, but there is no exclusion guidance or alternative routing, which is important given 11 sibling tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
figma_inspectARead-only
Inspect node geometry, paint, effects, component and text facts as YAML.
| Name | Required | Description | Default |
|---|---|---|---|
| nodeId | Yes | Figma node id ("1:2"), URL form ("1-2"), or a full Figma URL. | |
| fileKey | No | Target connected file: bare key or Figma URL. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already declare readOnlyHint=true and destructiveHint=false. The description adds useful behavioral context by specifying the output as YAML and enumerating the fact categories covered. It does not contradict the annotations, though it omits details about failure modes or file resolution.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, efficient sentence with no filler. It front-loads the action and resource, then states the output format, earning full credit for conciseness.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only inspection tool, the description is adequate: it names the output format, the fact categories, and the schema documents parameters. There is no output schema, but the description partially compensates by indicating YAML results. It could specify exact response structure or edge-case behavior, but these are minor gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both nodeId and fileKey are already well documented. The description adds selection context through 'node facts' but does not provide additional parameter semantics beyond the schema. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Inspect') with a clear resource ('node geometry, paint, effects, component and text facts') and an explicit output format ('as YAML'). This makes it readily distinguishable from visual siblings like figma_render or figma_screenshot, even without naming them.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description clearly implies when to use this tool: when an agent needs structured node facts rather than rendered output. It does not explicitly name alternatives or state when not to use it, but the context is sufficiently clear for an agent to select it appropriately.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
figma_pairingADestructive
Show the plugin key; rotate:true replaces it and restarts the daemon.
| Name | Required | Description | Default |
|---|---|---|---|
| rotate | No | Replace the key and restart the daemon. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description explicitly discloses the destructive behavior: rotate:true replaces the key and restarts the daemon. This aligns with destructiveHint=true and adds specific context about what is affected, which goes beyond the annotation alone.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single semicolon-separated sentence with no filler. It front-loads the main purpose and then introduces the optional variant, making it easy to parse quickly.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple tool with one optional parameter and no output schema, the description covers the core behavior and the destructive branch sufficiently. It omits return-value details and usage prerequisites, but these are minor given the tool's low complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema fully documents the rotate parameter, but the description adds the default behavior of simply showing the key when rotate is not true. This clarifies the optional nature of the parameter and the outcome of the default call.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the primary action ('Show the plugin key') and the optional destructive variant ('rotate:true replaces it and restarts the daemon'). This distinguishes figma_pairing from its siblings by specifying a unique resource and operation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is provided on when to use figma_pairing versus its siblings, nor when rotation is appropriate or what prerequisites exist. The only condition mentioned is the rotate flag, but not the context that should trigger it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
figma_referenceBRead-only
Offline API reference plus capabilities, fidelity, variable scopes and workflows.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | API name or special topic; omit to list names. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the tool as readOnlyHint=true and destructiveHint=false, so the agent knows this is a safe read operation. The description adds the 'Offline' trait and the content scope, which gives some behavioral context, but it doesn't disclose specifics like list behavior on omitted name or return content format. Given annotation coverage, this meets a baseline without adding much beyond it.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single sentence that leads with the core identity ('Offline API reference') and enumerates topics without filler. It is appropriately small and front-loaded, with no redundant phrasing.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-optional-parameter, read-only reference tool with no output schema, the description names the resource and its topic areas, and the schema covers invocation details. It lacks explicit instruction that omitting name lists all entries, but that is already in the schema; the main gap is unclear differentiation from sibling tools, which is more a usage-guideline issue. Overall it is sufficient for the tool's low complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the parameter is fully documented in the schema with 'API name or special topic; omit to list names.' The description contributes no additional parameter semantics beyond the schema, so the baseline score of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description identifies the tool as an 'Offline API reference' and lists the domains it covers (capabilities, fidelity, variable scopes, workflows), which signals a lookup/reference function. However, it lacks an explicit verb like 'query' or 'look up,' and it does not differentiate itself from siblings like figma_spec or figma_inspect, so an agent must infer the action from the noun.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is given on when to use this tool instead of alternatives such as figma_spec or figma_inspect. The term 'Offline' hints at a network-independent use case, but no explicit when/when-not conditions or alternative tool references appear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
figma_renderC
Render one JSX value or roots[] batch into Figma.
| Name | Required | Description | Default |
|---|---|---|---|
| x | No | ||
| y | No | ||
| gap | No | ||
| jsx | No | ||
| icons | No | ||
| label | No | Optional intent stored only as non-reversible audit metadata. | |
| roots | No | ||
| preset | No | ||
| verify | No | ||
| confirm | No | Required by policy; omission is not a preview. | |
| fileKey | No | Figma key/URL. | |
| preview | No | Return a payload-free, non-mutating Command Plan. | |
| manifest | No | ||
| direction | No | ||
| collection | No | ||
| asComponent | No | ||
| keepWrapper | No | ||
| resizeProbe | No | ||
| smartPosition | No | ||
| allowedFreePaths | No | ||
| approvedFallbacks | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate readOnlyHint=false and openWorldHint=true, so the description adds no new behavioral insight. It does not explain side-effects, scope of mutation, reversibility, or the confirm/preview flow. Given the open-world hint, more disclosure would be expected, but the description stays silent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The single-sentence description is concise and free of filler, but it lacks structure and does not front-load critical information like the required confirm parameter or the preview mode. It is appropriately short but at the cost of essential detail.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with 21 parameters, no output schema, and low schema coverage, the description is severely incomplete. It fails to explain the difference between jsx and roots, the meaning of most parameters, or the behavioral implications. An agent would be guessing at almost every operational aspect.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is only 19%, so the description must compensate, but it only mentions jsx and roots as primary inputs. The remaining 19 parameters (x, y, gap, preset, verify, confirm, etc.) are left unexplained. The description adds only marginal meaning beyond the schema, far from sufficient for correct invocation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action (render), the inputs (JSX or roots[] batch), and the target (Figma). It distinguishes between two input modes, giving a specific verb-resource pair. However, it does not explicitly differentiate it from sibling tools like figma_run or figma_screenshot, so it misses the distinguishing context.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool versus alternatives. No mention of alternatives, conditions, or exclusions. The description simply declares what the tool does without any contextual routing, leaving an agent without criteria for tool selection.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
figma_runBDestructive
Run an approved command. See figma_reference {name:"capabilities"} or --help.
| Name | Required | Description | Default |
|---|---|---|---|
| args | Yes | Command argv, e.g. ["canvas","info"]. | |
| force | No | Asset Export: bypass completed retry cache. | |
| label | No | ||
| confirm | No | Required by policy; omission is not a preview. | |
| fileKey | No | Figma key/URL. | |
| preview | No | Return a payload-free, non-mutating Command Plan. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag this as destructive (destructiveHint: true), and the description adds no behavioral nuance beyond 'approved.' It does not mention preview mode, the policy-required confirm flag, mutations, or consequences, so the description contributes little beyond the structured hints.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One short sentence states the core behavior and immediately directs the agent to the authoritative capability reference. There is no filler or redundant restatement of schema or annotations.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The pointer to figma_reference and --help gives an agent a discovery path, and the schema covers most parameters, but the description alone leaves key operating context unclear: what commands exist, how approval is determined, and what preview/confirm semantics mean operationally. Given the destructive hint and lack of an output schema, more context would improve call correctness.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 83%, so the parameters are mostly self-documenting; the description itself adds no parameter-level detail. Per the high-coverage baseline, a 3 is appropriate rather than a lower score.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific action ('Run an approved command') and distinguishes this as the executor tool while pointing to figma_reference for allowed commands. It is not a tautology, but the command space itself is left unspecified until the agent consults the reference.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The instruction to see figma_reference {name:'capabilities'} or --help implies how to identify approved commands, and the word 'approved' imposes a usage constraint. However, it does not explicitly state when to prefer this tool over sibling tools or what conditions disqualify a command.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
figma_screenshotBRead-only
MANDATORY: save/read the node PNG before comparing the build.
| Name | Required | Description | Default |
|---|---|---|---|
| scale | No | Scale (default 0.5; 2000px cap); returns exact pixel/logical sizes. | |
| nodeId | No | Figma node id ("1:2"), URL form ("1-2"), or a full Figma URL. Omit to use the current selection. | |
| fileKey | No | Target connected file: bare key or Figma URL. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already declare readOnlyHint=true and destructiveHint=false, and the description does not contradict them; 'save/read' reinforces a read-oriented behavior. However, the description adds little beyond that—no detail about output format, persistence, caching, or side effects.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is very short and front-loads the key directive 'MANDATORY,' making it efficient to parse. However, the terseness sacrifices a clear purpose statement and leaves much to inference.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given no output schema and many sibling tools, the description is incomplete: it does not explain what the PNG contains, how it is returned or saved, how optional parameters interact with the current-selection fallback, or what 'comparing the build' means. An agent would need additional context to invoke this tool confidently.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so scale, nodeId, and fileKey are already fully documented. The tool description adds no extra parameter semantics or usage nuances beyond what the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description identifies a resource (node PNG) and an action (save/read), but it reads as a workflow command—'MANDATORY: save/read the node PNG before comparing the build'—rather than a clear statement of tool function. It does not explicitly say that the tool captures a screenshot of a Figma node, and it does not distinguish itself from sibling tools like figma_render.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides an explicit temporal condition: this tool is mandatory before comparing the build. This gives clear context for when to invoke it, though it does not mention when not to use it or how it compares to alternative tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
figma_selectionBRead-only
Read the current Figma selection and reuse its id.
| Name | Required | Description | Default |
|---|---|---|---|
| fileKey | No | Figma key/URL. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate this is read-only and non-destructive, so the description does not contradict them. It adds the notion that the tool depends on a 'current' selection state, but does not disclose what happens when there is no active selection or whether the returned id is a node id. This is acceptable but not rich.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence with no filler or redundant phrasing. The purpose and output are stated directly, making it efficiently scannable for an agent.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a low-complexity tool with one optional parameter, full schema coverage, and safety annotations, the description is nearly complete. It conveys that the tool returns a reusable id, though it could mention behavior for an empty selection or confirm the return format.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the fileKey parameter is already described as 'Figma key/URL.' The description adds no additional parameter-level meaning, which is consistent with the baseline of 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a clear verb ('Read') and a specific resource ('the current Figma selection'), and indicates the output ('its id'). It distinguishes itself from rendering/screenshot/comment tools, though it does not explicitly name any sibling to differentiate from.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit guidance about when to use this tool versus alternatives like figma_inspect or figma_reference, nor any mention of prerequisites such as an active Figma session or a non-empty selection. The usage is only implied by the verb 'Read' and the notion of a 'current selection'.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
figma_specARead-only
Exact layer facts; never invent. nodeIds[] batches 8 reads behind one approval.
| Name | Required | Description | Default |
|---|---|---|---|
| dedup | No | false: inline every layer; true: compact repeats. | |
| depth | No | Tree depth; 0 = node only, default 12, max 30. | |
| phase | No | all (default), structure, or style. | |
| format | No | tree (default), yaml, or json. | tree |
| nodeId | No | Node id or Figma URL. | |
| fileKey | No | Target connected file: bare key or Figma URL. | |
| nodeIds | No | Node ids for one bounded batch; phase/depth/format options apply to every id. | |
| section | No | Optional child layer name. | |
| includeHidden | No | Include invisible nodes (default false). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish readOnlyHint=true and destructiveHint=false, so the safety profile is covered. The description adds meaningful beyond-annotation behavior: a 'never invent' no-hallucination promise and batching behind a single approval. It does not mention output shape or errors, but the read-only nature is well covered.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The entire description is two short sentences with zero filler. It front-loads the core promise ('Exact layer facts; never invent') and follows with a specific, actionable batching note. Every word earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
This is a complex tool: 9 optional parameters, anyOf constraints, no output schema, and 11 sibling tools. Two brief sentences do not provide enough context for an agent to understand what 'spec' returns, how nodeId and nodeIds interact, or when to pick this over figma_inspect/reference. The schema fully covers parameter syntax, but not tool selection or output expectations.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the 9 parameters are already fully documented in the input schema. The description adds a small extra semantic around nodeIds — that batching 8 reads happens behind one approval — but it does not compensate for or clarify the other parameters, so the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The phrase 'Exact layer facts' clearly identifies the resource (layers) and the nature of the output (facts, not guesses), while 'never invent' signals a fidelity guarantee. It does not use an explicit verb like 'get' or 'return', and it does not explicitly distinguish itself from figma_inspect or figma_reference, so it stops short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The line 'nodeIds[] batches 8 reads behind one approval' gives actionable guidance to batch multiple reads for efficiency, which is genuinely useful. However, it never says when to choose figma_spec over sibling tools or when not to use it, so the guidance remains implied rather than explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
figma_statusBRead-only
This Figma Bridge only: round-trip status, write policy, optional REST check.
| Name | Required | Description | Default |
|---|---|---|---|
| fileKey | No | Figma key/URL for REST validation. | |
| probePlugin | No | Probe plugin eval; false checks only its socket. | |
| validateRest | No | Validate the optional REST token. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare the operation as read-only, non-destructive, and open-world. The description adds that it performs a round-trip status check and can validate a REST token, which is useful, but it does not clarify what 'write policy' means or how the optional REST check behaves when skipped.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact, scannable, and front-loaded with scope and purpose. It loses a point because the telegraphic fragment style creates mild ambiguity, for example whether 'Figma Bridge only' is an API restriction or a domain qualifier.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description should explain what a successful status response contains or what a negative result means, but it does not. It also leaves 'write policy' and 'round-trip' behavior underspecified, so an agent can select the tool but not confidently interpret its result.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, and the schema already documents fileKey, probePlugin, and validateRest clearly. The description's high-level status/REST phrasing maps to those parameters but adds no additional semantic detail beyond what the input schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description identifies the resource (Figma Bridge) and the three relevant facets: round-trip status, write policy, and optional REST check. It is not a tautology, though it lacks an explicit verb and does not name a sibling tool for differentiation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no when-to-use guidance and no mention of alternative tools such as figma_connect or figma_pairing. The phrase 'Figma Bridge only' restricts scope but does not help an agent decide when this status tool is the right choice.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
10 tool updates
v0.6.1- Changed
figma_comments2 fields changed- changed
Input schema / properties / fileKey / descriptionPrevious value: -"Bare file key or full Figma URL. Default: the file open in Figma Desktop."New value: +"File key/URL; defaults to the open file." - changed
Input schema / properties / nodeId / descriptionPrevious value: -"post: anchor the comment to this node (\"1:2\", URL form, or full URL)."New value: +"post: node anchor (id or URL)."
- Changed
figma_history12 fields changed- changed
Input schema / properties / diff / descriptionPrevious value: -"Structural diff instead of the log: what nodes were added, removed, recreated, moved or changed between two states. Refs are \"live\" (the document right now), \"latest\"/\"previous\", an index, or a Figma version id (needs the REST layer). Record comparison points with figma_run [\"history\",\"snapshot\"]."New value: +"Diff live/latest/previous, an index, or a Figma version." - changed
Input schema / properties / diff / properties / changelog / descriptionPrevious value: -"Emit a markdown changelog instead of the terse report."New value: +"Return markdown changelog." - changed
Input schema / properties / diff / properties / from / descriptionPrevious value: -"Older side. Default \"previous\"."New value: +"Older ref; default previous." - changed
Input schema / properties / diff / properties / nodeId / descriptionPrevious value: -"Subtree root when a side is \"live\" (default: current page)."New value: +"Live subtree root." - changed
Input schema / properties / diff / properties / to / descriptionPrevious value: -"Newer side. Default \"latest\". Use \"live\" for the current document."New value: +"Newer ref; default latest." - changed
Input schema / properties / fileKey / descriptionPrevious value: -"File for includeVersions: bare key or full Figma URL. Default: the file open in Figma Desktop."New value: +"Figma key/URL for versions." - changed
Input schema / properties / format / descriptionPrevious value: -"Output format (default markdown table)."New value: +"Default markdown." - changed
Input schema / properties / gitPaths / descriptionPrevious value: -"Repo-relative file paths whose git log to merge in (generated code files)."New value: +"Repo-relative paths for git history." - changed
Input schema / properties / includeVersions / descriptionPrevious value: -"Also merge the file's Figma version history (what designers saved, by whom) via the optional REST layer. Needs a configured REST token; without one a note is appended instead."New value: +"Merge Figma versions via optional REST." - changed
Input schema / properties / limit / descriptionPrevious value: -"Max entries (default 20, max 200)."New value: +"Default 20; max 200." - changed
Input schema / properties / nodeId / descriptionPrevious value: -"Only entries touching this node id (\"1:2\" or URL form \"1-2\")."New value: +"Node-id filter." - changed
Input schema / properties / repoPath / descriptionPrevious value: -"Repo root for gitPaths (default: server working directory)."New value: +"Repo root for gitPaths."
- Changed
figma_pairing1 field changed- changed
Input schema / properties / rotate / descriptionPrevious value: -"Generate a NEW key, invalidating the old one. Run figma_connect afterwards to restart the daemon."New value: +"Replace the key and restart the daemon."
- Changed
figma_reference1 field changed- changed
Input schema / properties / name / descriptionPrevious value: -"API name, capabilities or workflow topic; omit to list API names."New value: +"API name or special topic; omit to list names."
- Changed
figma_render22 fields changed- added
Input schema / properties / allowedFreePathsAdded value: +{ + "items": { + "minLength": 1, + "type": "string" + }, + "maxItems": 100, + "type": "array" +} - added
Input schema / properties / approvedFallbacksAdded value: +{ + "items": { + "minLength": 1, + "type": "string" + }, + "maxItems": 100, + "type": "array" +} - added
Input schema / properties / asComponentAdded value: +{ + "type": "boolean" +} - added
Input schema / properties / collectionAdded value: +{ + "minLength": 1, + "type": "string" +} - changed
Input schema / properties / confirm / descriptionPrevious value: -"Required when write-confirm mode is on."New value: +"Required by policy; omission is not a preview." - added
Input schema / properties / directionAdded value: +{ + "enum": [ + "row", + "col" + ], + "type": "string" +} - changed
Input schema / properties / fileKey / descriptionPrevious value: -"Target connected file: bare key or Figma URL."New value: +"Figma key/URL." - added
Input schema / properties / gapAdded value: +{ + "maximum": 10000, + "minimum": 0, + "type": "number" +} - added
Input schema / properties / iconsAdded value: +{ + "minLength": 1, + "type": "string" +} - removed
Input schema / properties / jsx / descriptionRemoved value: -"JSX markup to render." - added
Input schema / properties / keepWrapperAdded value: +{ + "type": "boolean" +} - changed
Input schema / properties / label / descriptionPrevious value: -"Optional audit-log intent note."New value: +"Optional intent stored only as non-reversible audit metadata." - added
Input schema / properties / manifestAdded value: +{ + "minLength": 1, + "type": "string" +} - added
Input schema / properties / presetAdded value: +{ + "enum": [ + "macbook-14", + "macbook-16", + "desktop", + "desktop-hd", + "iphone-15", + "iphone-15-pro-max", + "iphone-se", + "android", + "ipad-11", + "ipad-13" + ], + "type": "string" +} - added
Input schema / properties / previewAdded value: +{ + "description": "Return a payload-free, non-mutating Command Plan.", + "type": "boolean" +} - added
Input schema / properties / resizeProbeAdded value: +{ + "maximum": 2000, + "minimum": 1, + "type": "number" +} - added
Input schema / properties / rootsAdded value: +{ + "items": { + "minLength": 1, + "type": "string" + }, + "maxItems": 100, + "minItems": 1, + "type": "array" +} - added
Input schema / properties / smartPositionAdded value: +{ + "type": "boolean" +} - added
Input schema / properties / verifyAdded value: +{ + "type": "boolean" +} - added
Input schema / properties / xAdded value: +{ + "type": "number" +} - added
Input schema / properties / yAdded value: +{ + "type": "number" +} - removed
Input schema / requiredRemoved value: -[ - "jsx" -]
- Changed
figma_run6 fields changed- changed
Input schema / properties / args / descriptionPrevious value: -"Engine subcommand and flags, e.g. [\"canvas\",\"info\"]."New value: +"Command argv, e.g. [\"canvas\",\"info\"]." - changed
Input schema / properties / confirm / descriptionPrevious value: -"Required for write commands when write-confirm mode is on."New value: +"Required by policy; omission is not a preview." - changed
Input schema / properties / fileKey / descriptionPrevious value: -"Target connected file: bare key or Figma URL."New value: +"Figma key/URL." - added
Input schema / properties / forceAdded value: +{ + "description": "Asset Export: bypass completed retry cache.", + "type": "boolean" +} - removed
Input schema / properties / label / descriptionRemoved value: -"Optional audit-log intent note." - added
Input schema / properties / previewAdded value: +{ + "description": "Return a payload-free, non-mutating Command Plan.", + "type": "boolean" +}
- Changed
figma_screenshot1 field changed- changed
Input schema / properties / scale / descriptionPrevious value: -"Export scale (default 0.5, capped at 2000px max dimension). The result reports the applied scale — rendered pixels = node size × scale."New value: +"Scale (default 0.5; 2000px cap); returns exact pixel/logical sizes."
- Changed
figma_selection1 field changed- changed
Input schema / properties / fileKey / descriptionPrevious value: -"Target connected file: bare key or Figma URL."New value: +"Figma key/URL."
- Changed
figma_spec10 fields changed- added
Input schema / anyOfAdded value: +[ + { + "required": [ + "nodeId" + ] + }, + { + "required": [ + "nodeIds" + ] + } +] - changed
Input schema / properties / dedup / descriptionPrevious value: -"false (default): inline every layer; true: compact S<n>/repeat refs."New value: +"false: inline every layer; true: compact repeats." - changed
Input schema / properties / depth / descriptionPrevious value: -"Max tree depth (default 12). Use 0 for the requested node itself, complete and without descendants."New value: +"Tree depth; 0 = node only, default 12, max 30." - changed
Input schema / properties / format / descriptionPrevious value: -"tree (default) is readable; yaml/json are lossless canonical adapters."New value: +"tree (default), yaml, or json." - changed
Input schema / properties / includeHidden / descriptionPrevious value: -"Also list invisible nodes, marked hidden (default false). Useful to understand what a variant toggle would reveal."New value: +"Include invisible nodes (default false)." - changed
Input schema / properties / nodeId / descriptionPrevious value: -"Frame node id, URL-form id, or full Figma URL."New value: +"Node id or Figma URL." - added
Input schema / properties / nodeIdsAdded value: +{ + "description": "Node ids for one bounded batch; phase/depth/format options apply to every id.", + "items": { + "type": "string" + }, + "maxItems": 8, + "minItems": 1, + "type": "array" +} - changed
Input schema / properties / phase / descriptionPrevious value: -"structure = hierarchy + real content; style = layout/paint/typography detail; all (default) = both."New value: +"all (default), structure, or style." - changed
Input schema / properties / section / descriptionPrevious value: -"Optional child layer name from the structure map. Prefer its node id when names repeat."New value: +"Optional child layer name." - removed
Input schema / requiredRemoved value: -[ - "nodeId" -]
- Changed
figma_status3 fields changed- changed
Input schema / properties / fileKey / descriptionPrevious value: -"File key/URL for REST file-access validation fallback."New value: +"Figma key/URL for REST validation." - changed
Input schema / properties / probePlugin / descriptionPrevious value: -"Plugin eval probe (default true); false = socket only."New value: +"Probe plugin eval; false checks only its socket." - changed
Input schema / properties / validateRest / descriptionPrevious value: -"Also validate the optional REST token remotely."New value: +"Validate the optional REST token."
12 tool updates
v0.5.1- First observed
figma_comments - First observed
figma_connect - First observed
figma_history - First observed
figma_inspect - First observed
figma_pairing - First observed
figma_reference - First observed
figma_render - First observed
figma_run - First observed
figma_screenshot - First observed
figma_selection - First observed
figma_spec - First observed
figma_status
TDQS
Most tools are distinct, but figma_inspect and figma_spec both target node/layer facts, and figma_connect, figma_status, and figma_pairing all relate to setup/connection. The descriptions provide enough detail to choose correctly in most cases, but the boundaries are not fully crisp.
All tools share the figma_ prefix and snake_case, but the suffix pattern is mixed: connect, run, render, and inspect are actions, while status, pairing, comments, history, and spec are nouns. The convention is readable and consistent in prefix, but not consistently verb_noun.
Twelve tools is a reasonable size for a Figma bridge covering setup, rendering, inspection, comments, screenshots, and history. Some tools overlap slightly and could be consolidated, but the count is neither excessive nor too thin.
The surface covers connection/setup, status, rendering, selection, comments, node inspection, specs, screenshots, history, and an offline reference. Minor gaps like comment update/delete or explicit file-scoped operations exist, but core workflows appear supported.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
The Figma MCP server brings Figma design context directly into your AI workflow.
MCP server for secureFlows: token-free URL builders and integration-linting tools for AI agents.
MCP server that lets AI assistants use all OneSchema features exposed via the public API.
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
Related MCP Servers
- AlicenseAqualityDmaintenanceLocal-first MCP server that connects AI coding agents to the currently open Figma file through a local plugin bridge, requiring no Figma API token.8MIT
- AlicenseNot gradedqualityCmaintenanceAn open-source MCP server that gives AI assistants full read-write access to Figma, enabling creation, editing, and deletion of designs directly without plugins or API keys.2410MIT
- FlicenseNot gradedqualityAmaintenanceA local MCP server that gives AI agents live access to open Figma files for design handoff and UX writing without API tokens or rate limits.3-
- FlicenseNot gradedqualityBmaintenanceA self-hosted MCP server that enables AI agents to retrieve Figma design data for generating code, templates, or custom prompts.2,160-
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/KaiUweHella/figma-bridge-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server