figma-jev-console
# Figma JEV Console MCP
[](https://modelcontextprotocol.io/)
[](LICENSE)
[](https://nodejs.org)
[](#step-3-import-the-figma-plugin)
[](#step-2-add-it-to-claude-code)
[](https://github.com/southleft/figma-console-mcp)
[](#-local-model)
[](https://github.com/anuragfolio/figma-jev-console-mcp)
> **A live, two-way bridge between your running app and Figma.** Push the screens of a coded
> app into Figma as auto-layout frames. After that, a change you make in Figma is written into
> the code about 150 ms later, and a change in the code updates the same Figma frames in place.
Figma JEV Console MCP is a fork of [Figma Console MCP](https://github.com/southleft/figma-console-mcp)
by Southleft. It keeps the parts that made that project work well: an MCP server, a Figma
Desktop plugin that talks to it over a local WebSocket, a stable plugin install path, and
an escape hatch for raw Plugin API code. It adds one thing the original does not do: it
treats the app's code and the Figma file as one design, and keeps them in sync while you work.
## What is this?
It connects Claude (or any MCP client) to Figma and to your app's source code:
- **π₯ Code β Figma push.** Each route of a running app is rendered in headless Chrome and
rebuilt in Figma: every container is an auto-layout frame, sizes are hug / fill / fixed as
the CSS says, and coded components become Figma components with variants.
- **βοΈ Figma β code, live.** Change padding, gap, direction, alignment, sizing, fills, text,
type, radius, borders, shadows or opacity in the Design panel, and the edit lands in the right
file and line: a Tailwind class, a CSS declaration, an inline style, a JSX string or a
component prop at its usage site. Your dev server's hot reload shows it.
- **π Code β Figma, live.** Edit the code (you or Claude) and the Figma screens update in
place. Layer ids, comments, prototype links and layers the designer added survive.
- **π§± New layers become code.** Draw a frame, text, shape, vector or instance inside a pushed
screen and it is written into the code; a component made in Figma gets its own file.
Deleting, reordering and moving linked layers is written too.
- **πΈ No Claude tokens for small edits.** Rules write the edits. A small local model
([Kev](https://github.com/jaredpalmer/kev)-0.8B, runs on your Mac) only settles ties, such
as two code lines that match a layer equally well.
- **π€ Fine-tune with LLM.** Anything the rules cannot write is collected in a changelog. One
button in the plugin hands it to the Claude session running the server, and the plugin shows
Claude's progress.
- **π¨ Design-system build skill.** `figma-system-build` builds or tidies screens in Figma with
every color, text style, spacing, radius and icon bound to variables.
## How it differs from Figma Console MCP
| | Figma Console MCP | Figma JEV Console MCP |
|---|---|---|
| Main job | Give AI full access to Figma: extraction, creation, tokens, audits, debugging | Keep a running app and its Figma screens in sync, both ways |
| Source of truth | The Figma file | The app's code and the Figma screens, kept equal |
| Code β Figma | Tokens and components via tools | Whole screens, rendered from the running app, as auto-layout frames |
| Figma β code | Token export and sync | Every Design-panel edit written to the exact source line, live |
| Figma plugin | Figma Desktop Bridge, ports 9223β9232 | **Jev Bridge**, ports 9340β9344 |
| Stable plugin path | `~/.figma-console-mcp/plugin/` | `~/.figma-jev-console/plugin/` |
| Models | None needed | Optional local tie-breaker (Kev-0.8B, MLX); Claude only on request |
| Tool count | 120+ | 12, focused on sync |
The two run side by side without conflict: different plugin, ports, server name and skills.
Use Figma Console MCP to explore a file, manage variables or audit a design system; use Figma
JEV Console MCP when the screens already exist as code and you want to design on top of them.
## β
Works with
| | Status |
|---|---|
| Vite + React + Tailwind CSS v3/v4 | Tested (end-to-end test suite) |
| Plain HTML + CSS | Tested (end-to-end test suite) |
| Vite + vanilla JS with CSS files | Used on a real app |
| Next.js, Remix, Vue, Svelte, Angular | Not tested yet. The capture works on any page Chrome can render; writing edits back relies on finding the element in the source, which should work for JSX and HTML-like templates but is unproven. Reports welcome. |
| CSS-in-JS (styled-components, Emotion), CSS Modules | Not tested. Edits the rules cannot place go to the changelog. |
| macOS (Apple Silicon) | Tested, including the local model |
| macOS (Intel), Windows, Linux | Server not tested; the local model does not run there (rules only). |
---
## β‘ Quick start
### Prerequisites
- **Node.js 20+** (`node --version`)
- **Figma Desktop** (the plugin needs it; the web app cannot reach localhost)
- **Google Chrome** (headless capture uses it; set `JEV_CHROME_CHANNEL` for another channel)
- **Claude Code** or another MCP client
- For the local model: a **Mac with Apple Silicon**, about 2 GB of disk and 1.5 GB of free memory.
Without it, everything still works on rules; ties wait for your review instead.
### Step 1: Get the code
Not on npm yet. Clone the repository:
```bash
git clone https://github.com/anuragfolio/figma-jev-console-mcp.git
```
```bash
cd figma-jev-console-mcp && npm install
```
### Step 2: Add it to Claude Code
**As a Claude Code plugin** (MCP server + both skills in one install):
```bash
claude plugin marketplace add anuragfolio/figma-jev-console-mcp
```
```bash
claude plugin install figma-jev-console@figma-jev-console
```
**Or as an MCP server only:**
```bash
claude mcp add figma-jev-console -s user -- node "/path/to/figma-jev-console-mcp/bin/figma-jev-console-mcp.js"
```
**Cursor / Windsurf / Claude Desktop:** add to your MCP config file:
```json
{
"mcpServers": {
"figma-jev-console": {
"command": "node",
"args": ["/path/to/figma-jev-console-mcp/bin/figma-jev-console-mcp.js"]
}
}
}
```
The launcher installs dependencies on its first run if `node_modules` is missing.
### Step 3: Import the Figma plugin
1. Start your MCP client once, so the server runs and writes the plugin files.
2. In Figma Desktop: **Plugins β Development β Import plugin from manifestβ¦**
3. Select `~/.figma-jev-console/plugin/manifest.json`. The server refreshes this copy on every
start, so the path never changes.
4. Run **Plugins β Development β Jev Bridge**. It finds the server on ports 9340β9344.
> **Plugin updates.** Figma caches plugin files. After updating the server, close and reopen
> Jev Bridge to load the new `code.js` and `ui.html`. The panel says so when its version is
> behind the server's.
### Step 4: Install the local model (optional, Apple Silicon)
The first time Jev Bridge opens, it offers to install Kev-0.8B (about 1.7 GB) next to the
manifest, with a progress bar and log. After that the normal panel appears. The model starts
only when an edit needs it and stops after 5 idle minutes.
### Step 5: Try it
Start your app's dev server, then ask Claude:
```
Push this app to Figma
```
β The screens appear on the current page. Change a padding or a text in Figma and watch the
app update.
---
## π§ Using it
### The plugin panel
- **Header:** the app URL of the selected screen (click to open it), the server port, and an
arrow that collapses the panel to just the header.
- **LIVE:** on by default. Edits are written about 150 ms after you stop changing a layer; the
second line shows the last one, e.g. `β text β "Log in" in index.html:11 Β· 4 ms`. Switch it
off to pause; paused edits are written when you switch it back on.
- **Local model:** its state (off, starting, running) and how much memory it and the Mac use.
- **Screens:** every pushed screen whose app is running, with its URL. Click one to jump to it
in Figma. While a push or fine-tune runs, this area shows its progress.
- **Fine-tune with LLM:** hands the leftovers to Claude (see below).
### Live sync
- **Figma β code:** every Design-panel field is read and written (`jev_contract` lists the
table). Tailwind v3/v4 utilities, CSS rules, inline styles, JSX text and component props at
usage sites are all handled.
- **Code β Figma:** a code change redraws only what changed. A layer you are editing, or
edited in the last 4 seconds, is left alone.
- **The screen is the truth.** Sizes come from layers on the screen (instances in their real
parent), never from main components sitting alone in the Components section. Style edits on
a main component go to the component's file, so every instance follows.
- **Restarts are safe.** Pushed frames store their app URL, project folder and viewport. When
the plugin reconnects to a restarted server, live sync resumes by itself. An edit counts as
synced only after the server confirms it, so an edit is never lost to a restart.
- **Git is your undo.** Edits are written straight into your source files. When sync starts,
the panel warns if the project is not a git repository, or has uncommitted changes, so you
can commit first and undo any edit with git.
- **Several Claude sessions:** each runs its own server. The plugin checks every port and
uses the one with a live session, or else the newest.
### Fine-tune with LLM
Whatever the rules cannot write goes to a changelog (layer code, Figma node id, file:line
hint, reason; `jev_changelog`). **Fine-tune with LLM** compares every linked layer with what
was last synced, adds the changelog, and sends that brief to the Claude Code session running
the server. Only then are Claude tokens spent. Claude reports each step with `jev_progress`,
and the plugin shows those lines; when it calls `jev_progress { done: true }`, the changelog
is cleared and Figma is redrawn from the code.
The hand-off uses Claude Code **channels** (a research preview). With the server added by
`claude mcp add`, start Claude Code like this:
```bash
claude --dangerously-load-development-channels server:figma-jev-console
```
Without it the brief does not reach Claude, and after 60 seconds the panel says so.
`JEV_FINE_TUNE=headless` uses a separate `claude -p` run instead.
---
## π§© Skills
Two skills ship in `skills/`. The Claude Code plugin installs both; to use them without the
plugin, copy the folders into `~/.claude/skills/`.
| Skill | Use it for |
|---|---|
| `figma-jev-console` | Pushing an app to Figma, live sync, and what to do when an edit did not reach the code |
| `figma-system-build` | Building or tidying screens in Figma by hand, with every color, text style, spacing, radius and icon bound to variables; Phosphor icon set; a lint pass instead of screenshots |
`figma-system-build` runs its Plugin API scripts through `jev_execute` (or `figma_execute` from
Figma Console MCP). Pushed screens copy raw values from CSS; binding them to variables with
this skill is design work on top, and the sync compares values, so it writes nothing to the
code unless a value changes.
## π οΈ Tools
| Tool | Purpose |
|---|---|
| `jev_status` | Plugin connection, model backend, live session |
| `jev_push_screens` | Push routes to Figma and start live sync |
| `jev_sync_now` | Write queued edits, then refresh screens from code |
| `jev_list_pending` / `jev_resolve_pending` | Edits that need a decision |
| `jev_changelog` | What the rules could not write, with a ready prompt |
| `jev_progress` | Claude reports fine-tune steps to the plugin |
| `jev_sync_log` | Applied edits with file:line and who decided |
| `jev_locate` | Layer code β file:line |
| `jev_contract` | The CSS β Figma property table |
| `jev_stop_live` | Stop watching and close the browser |
| `jev_execute` | Run raw Figma Plugin API code (escape hatch) |
---
## βοΈ How it works
| Step | Done by |
|---|---|
| Render each route in headless Chrome, read layout and computed CSS | code (`capture-script.js`) |
| Copy padding, gap, colors, type, radius, borders, shadows into Figma | code (fixed table in `properties.js`) |
| Direction (HORIZONTAL / VERTICAL / WRAP), hug / fill / fixed, components | rules from CSS and measured layout |
| Find the source line or CSS rule for an edited layer | rules; the local model only on a tie |
| Write the edit (Tailwind class, CSS declaration, inline style, text) | code (`edits.js`, `tailwind.js`) |
| Anything left over | the changelog, then Claude on request |
**Layer names** are short codes such as `@a3.0.2.1`: screen `a3`, then the element's index
among its parent's element children at each DOM level (base 36). `:t` is the text inside a
boxed element. The code is also stored in plugin data, so moving a layer does not change what
it points to. Screen frames keep their route name.
**Updates are in place.** Layers are matched to elements by a content fingerprint (tag,
classes, text, component), then by code, then by position. Each update is one undo step.
**New layers:** a frame becomes a flex `div`, a text layer a text element, an instance of a
pushed component `<Button variant="primary">β¦</Button>` with its import, a component made in
Figma a new file in `src/components/`, and a frame wrapped around linked layers (Shift+A) a new
wrapper around the existing code.
**Deletes, reorders, moves, vectors:** a deleted layer's element (or data-list item) is
removed; reordered layers move their code blocks; a layer dragged into another linked frame
moves its code; vectors become inline `<svg>`; a variant swap driven by an expression maps to
its condition.
## π§ Local model
Ties go to [Kev](https://github.com/jaredpalmer/kev)-0.8B
([weights](https://huggingface.co/jaredpalmer/kev-0.8b)), a Jev-compatible decision model
running on the Mac with MLX, installed from the plugin's setup screen.
- Starts on demand (about 8 seconds the first time), stops after 5 idle minutes.
- Will not start when less than 12% of memory is free, and is stopped if memory stays that
low while the Mac starts swapping.
- Memory use is logged every 10 seconds to `~/.figma-jev-console/sync.log` while it runs.
- Any server that speaks the same `/v1/systemone` format can replace it (`JEV_LOCAL_URL`),
including TypeSafe's cloud Jev (`TYPESAFE_API_KEY`, optional).
## π§ Configuration
Set in `.env` next to `package.json`, in `~/.figma-jev-console/.env`, or in your MCP client's
`env`. Keep keys out of version control.
| Variable | Default | Meaning |
|---|---|---|
| `JEV_PORTS` | `9340,β¦,9344` | WebSocket ports for Jev Bridge |
| `JEV_MODE` | `local` after the model installs | `local`, `local-first` or `cloud` |
| `JEV_LOCAL_URL` | `http://127.0.0.1:8009` | Local Jev-compatible server |
| `TYPESAFE_API_KEY` | none | Optional cloud Jev key |
| `JEV_AUTO_APPLY` | `0.6` | Minimum confidence to write a tie-broken edit without review |
| `JEV_MODEL_IDLE_MIN` | `5` | Minutes before the idle local model stops |
| `JEV_MIN_FREE_PCT` | `12` | Memory floor for starting or keeping the local model |
| `JEV_CHROME_CHANNEL` | `chrome` | Chrome channel for headless capture |
| `JEV_FINE_TUNE` | channel | `headless` runs `claude -p` instead of the channel hand-off |
| `JEV_PLUGIN_DIR` | `~/.figma-jev-console/plugin` | Where the plugin files are kept |
| `JEV_MODEL_DIR` | `<plugin dir>/local-model` | Where the local model is installed |
## π§ͺ Tests
```bash
npm test
```
Runs every end-to-end scenario: the real MCP server on a test port (`JEV_PORTS=9390`, so a
Jev Bridge open in Figma is not touched), a fake plugin, a push, Figma-style edits, and the
resulting source files. One scenario:
```bash
FIXTURE=fixture-react SCENARIO=scenario-react.mjs node test/e2e.mjs
```
`test/live-harness.mjs` runs a fixture against real Figma.
## π©Ί Troubleshooting
**The panel says "Looking for serverβ¦" or "Server offline".** The MCP server is not running.
It starts with your MCP client: restart Claude Code (or your client) and check that
`figma-jev-console` is listed (`claude mcp list`). Ports 9340β9344 must be free for at least one
server; `lsof -i :9340` shows what holds a port.
**The plugin connected to the wrong server.** Every Claude session starts its own server.
The plugin prefers the one with a live session, else the newest, and re-checks every 15
seconds. If it still picks an old one, close the other Claude sessions or reopen the plugin.
**"Jev Bridge is out of date".** Figma is running a cached copy of the plugin. Close the
plugin and open it again (Plugins β Development β Jev Bridge). If the notice stays, re-import
`~/.figma-jev-console/plugin/manifest.json`.
**"Could not start Chrome".** Install Google Chrome, or point `JEV_CHROME_CHANNEL` at another
Chromium channel you have (`chrome-beta`, `msedge`).
**A Figma edit did not reach the code.** Ask Claude "why didn't my edit sync?": the skill
reads `jev_list_pending`. Common reasons are a design token (it asks whether to change the
token or just this element) or a value built from variables in the code. Anything left over
goes to Fine-tune with LLM.
**Screens are missing from the panel's list.** Only screens whose app answers at their URL
are listed. Start the app's dev server; screens pushed by early versions store no URL and stay
hidden until you push them again.
**Fine-tune with LLM says "No reply from Claude".** Claude Code must be started with
channels on: `claude --dangerously-load-development-channels server:figma-jev-console`. Or set
`JEV_FINE_TUNE=headless` to use a separate `claude -p` run.
**The local model does not start.** It needs Apple Silicon and at least 12% free memory.
The panel shows the reason; the log is `~/.figma-jev-console/sync.log`. **Start again**
retries without downloading anything.
**Undo an edit.** Figma's undo changes the layer back, and the sync writes that like any
other edit. For the code itself, use git (`git diff`, `git checkout -- <file>`).
## Known limits
- One viewport per session; hover and focus states, animations and icon fonts are not pushed.
`::before` / `::after` text (such as a placeholder) is drawn but not written back.
- Showing a hidden data item again, and moves or reorders across files, go to the changelog.
- Block layouts with uneven margins use the median spacing as the auto-layout gap, so a few
pixels can shift.
- Instances cannot change structure; a component rendered with different children becomes
separate variants.
- The local model needs Apple Silicon.
## Credits and license
MIT, see [LICENSE](LICENSE). A fork of [Figma Console MCP](https://github.com/southleft/figma-console-mcp)
by Southleft and its contributors (MIT). Tie-breaking model: [Kev](https://github.com/jaredpalmer/kev)
by Jared Palmer. Icons in `figma-system-build`: [Phosphor](https://phosphoricons.com).
TDQS
Scored across 12 tools
While each tool has a specific name, several overlap in function and timing: jev_sync_now, jev_push_screens, and jev_resolve_pending all trigger writes to code or Figma, and jev_status, jev_sync_log, jev_progress, jev_changelog, and jev_list_pending all report state, making it hard to select the right one. Descriptions are detailed but the boundaries are blurred.
All tool names start with 'jev_' followed by a simple verb (status, sync_now, list_pending, changelog, push_screens, resolve_pending, sync_log, locate, progress, contract, stop_live, execute), following a consistent, predictable pattern. All are lowercase with underscores, no mixing of conventions.
With 12 tools, the set is well-scoped for the Figma-Jev integration console. Each tool addresses a distinct aspect of the workflow (status, sync, pending, changelog, push, resolve, log, locate, progress, contract, stop, execute), and the count is within the ideal 3-15 range, with no redundancy.
The set covers core lifecycle operations: status, sync, pending resolution, push, stop, and a progress reporting mechanism. However, there is a notable gap: no explicit tool to start or manage a live session (only stop_live exists), and the fine-tune workflow relies on an external channel rather than a dedicated tool. The escape hatch jev_execute partially mitigates gaps, but the surface feels incomplete for the stated purpose.