figma-connect
A local Figma MCP server, and a pixel-level oracle that tells an AI agent whether the code it wrote actually matches the design
I build a lot of UI from Figma files, often with an AI agent doing the first pass. The agent reads the design, writes the markup, and it looks roughly right. "Roughly right" is the problem. The spacing is four pixels off, a font weight is wrong, a gradient stop drifted, and nobody notices until it's in review, because there's nothing checking the output against the source. The existing Figma tooling hands you JSON about a node. None of it tells you whether what you shipped looks like what the designer drew.
So I built two things into one tool. First, full-fidelity read access to a live Figma file from inside an AI coding agent: geometry, auto-layout, fills, strokes, effects, type, tokens, components. Second, and the part that actually matters: a verification oracle. It takes the code the agent generated, renders it in a real browser, and pixel-diffs it against the live Figma node. The agent stops guessing whether it got the design right and gets a pass or fail with the diff attached.
It runs entirely on my machine. Browser Figma works, with no Desktop app, no cloud REST API, no design data leaving the laptop. A plugin runs inside Figma, a local bridge does the indexing and the talking, and the whole thing speaks MCP so any compatible agent can drive it.
Four packages in a pnpm workspace, each with one job. The plugin lives in Figma and is the only thing that can read the document. The bridge is the brain: it runs as a background daemon that holds the cache, runs the search, and exposes the tools. Claude Code talks to it through a small MCP shim over stdio, so a file stays indexed across sessions instead of being re-walked every time the agent restarts. The harness is the renderer and judge. A shared package carries the protocol types so the plugin and bridge can't drift out of sync.
Security is layered, and I'm honest in the code about where the line sits. The bridge binds to 127.0.0.1 only. Nothing it does is reachable from the network, and that's the real gate. On top of that it checks the WebSocket origin against an allowlist: Figma's web origin, plus the empty/null origin that sandboxed plugin iframes send. Allowing null is a deliberate, commented tradeoff: the loopback binding is what actually keeps a stray page out, so the origin check is defence-in-depth, not the sole lock.
This is the reason the project exists. verify_node takes a node ID and the candidate code, mounts the code in headless Chromium with Playwright, exports the matching node from Figma, and compares them across three channels at once:
The point is to turn "looks right" into a gate. Before this, an agent reproducing a design would declare victory and move on. Now there's a number and an image standing between "done" and done, and the agent has to actually pass it. re_verify re-runs a check after the cache has caught a design edit, and reports whether it had to resync, so a stale pass can't masquerade as a fresh one.
A real Figma file is enormous. You can't shove it into a model's context and you can't re-walk it on every question. So the bridge indexes the file once into a local SQLite database (nodes, images, styles, semantics) and answers from there. Search is backed by an FTS5 virtual table, which makes lexical lookups land in well under 50ms even on a large file.
Two honest constraints fall out of that design, and I document both rather than pretend they don't exist. The search is lexical, not semantic: it matches tokens that literally appear in a node's name, text, or styles, so a concept query won't surface a generically-named layer. And the digest the agent reads is a budgeted view of the node: it can't carry every property at full precision without blowing the context window, so I made deliberate calls about what fidelity to keep and what to flag as lossy. When a design changes, a delta-sync pass invalidates only the nodes that moved instead of rebuilding the whole cache.
-- the bridge's cache, in one glance files -- one row per indexed Figma file nodes -- the node tree + serialized IR images -- exported fills / screenshots, content-addressed node_semantics -- derived role/relationship hints change_log -- what moved, for delta-sync fts_nodes -- FTS5 virtual table → sub-50ms lexical search
SQLite schema (better-sqlite3). One index pass, then everything answers locally.
The headline features were the easy part. The work that actually mattered was making the render-and-diff loop trustworthy, because a verification tool that fails for the wrong reasons is worse than none. It teaches you to ignore it.
Most of the recent history on the project is exactly this kind of work: reliability fixes, additive database migrations, and a limitations document I keep current on purpose. I'd rather ship a tool that says "I can't represent this accurately yet" than one that quietly lies.
35 MCP tools, grouped by what they're for. Most are read-only by design: the server inspects a design, it doesn't mutate it.