Skip to main content
Glama
anuragfolio

figma-jev-console

by anuragfolio
README.md
# Figma JEV Console MCP

[![MCP](https://img.shields.io/badge/MCP-compatible-blue)](https://modelcontextprotocol.io/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Node.js](https://img.shields.io/badge/node-%3E%3D20-339933?logo=node.js&logoColor=white)](https://nodejs.org)
[![Figma plugin](https://img.shields.io/badge/Figma-Jev%20Bridge%20plugin-F24E1E?logo=figma&logoColor=white)](#install-with-one-prompt)
[![Claude Code plugin](https://img.shields.io/badge/Claude%20Code-plugin-D97757?logo=anthropic&logoColor=white)](#install-with-one-prompt)
[![Fork of Figma Console MCP](https://img.shields.io/badge/fork%20of-figma--console--mcp-0D9488)](https://github.com/southleft/figma-console-mcp)
[![Local model](https://img.shields.io/badge/local%20model-Kev--0.8B%20%28MLX%29-555)](#-local-model)
[![GitHub stars](https://img.shields.io/github/stars/anuragfolio/figma-jev-console-mcp?style=social)](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

- **Figma Desktop** (the plugin needs it; the web app cannot reach localhost)
- **Node.js 20+**, **Google Chrome** and **git**
- **Claude Code**, or another AI coding assistant with MCP support (Cursor, Windsurf, Claude Desktop)
- 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.

### Install with one prompt

Paste this into Claude Code (or your AI coding assistant). It installs everything and hands
you the file to load in Figma.

```text
Install Figma JEV Console MCP for me (https://github.com/anuragfolio/figma-jev-console-mcp).

1. Check that Node.js 20 or newer, git and Google Chrome are installed. If one is missing,
   stop and tell me how to install it.
2. Clone the repository into ~/figma-jev-console-mcp (if the folder exists, run git pull
   there instead), then run npm install in it.
3. Register the MCP server under the name figma-jev-console so it starts with my AI client.
   In Claude Code run:
   claude mcp add figma-jev-console -s user -- node ~/figma-jev-console-mcp/bin/figma-jev-console-mcp.js
   (use the full path of my home folder). For another client, add the same command to its MCP
   config file.
4. If you are Claude Code, copy the folders in ~/figma-jev-console-mcp/skills/ into
   ~/.claude/skills/.
5. Run: node ~/figma-jev-console-mcp/bin/figma-jev-console-mcp.js --setup
   It prints the path of the Figma plugin's manifest.json. Show me that full path, and open
   the folder that contains it (macOS: open -R <path>, Windows: explorer /select,<path>).
6. Tell me the last steps: in Figma Desktop choose Plugins → Development → Import plugin
   from manifest…, pick that manifest.json, then run Plugins → Development → Jev Bridge.
   Restart the AI client so it loads the new MCP server.
```

The plugin's `manifest.json` lives at `~/.figma-jev-console/plugin/manifest.json`. The server
refreshes that copy every time it starts, so the path never changes.

<details>
<summary><b>Manual install</b> (the same steps by hand)</summary>

Not on npm yet. Clone and install:

```bash
git clone https://github.com/anuragfolio/figma-jev-console-mcp.git ~/figma-jev-console-mcp
```

```bash
cd ~/figma-jev-console-mcp && npm install
```

Add it to Claude Code, either as a **Claude Code plugin** (MCP server + both skills):

```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 ~/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": ["/Users/you/figma-jev-console-mcp/bin/figma-jev-console-mcp.js"]
    }
  }
}
```

Write the Figma plugin files and print the manifest path:

```bash
node ~/figma-jev-console-mcp/bin/figma-jev-console-mcp.js --setup
```

In Figma Desktop: **Plugins → Development → Import plugin from manifest…**, pick that
`manifest.json`, then run **Plugins → Development → Jev Bridge**. It finds the server on
ports 9340–9344.

</details>

> **Plugin updates.** Figma caches plugin files. After updating (`git pull`), 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.

### 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.

### 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_prefs` | What the edit check has learned for this project, and where it is stored |
| `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`) |
| Check the edit in the running app; roll back and try the next place if it missed | code (`verify.js`) |
| Anything left over | the changelog, then Claude on request |

**Every edit is checked.** After a Figma edit is written, the screen is reloaded in the
headless browser and the element is measured again:

- **Did it land?** The element must now show the Figma value (within 0.5 px for lengths,
  1/255 per colour channel, exact for text, direction, wrap and alignment).
- **Nothing else changed?** No other element's own style may change (padding, colours, type,
  radius, borders, gap, direction). Elements drawn from the same template that change the same
  way are fine, and so are position and size changes caused by the edit.

If the check fails, the file is put back exactly as it was (byte for byte) and the next
place is tried: another source line, the next class list, a CSS rule, an inline style. At
most 4 tries. If none works, the code stays as it started and the changelog gets an item
listing every try and why it failed, ready for Fine-tune with LLM.

The first write happens at once, so the app updates as fast as before. The panel's LIVE line
then shows "Verifying…", followed by ✓ verified, "Corrected: <new place>", or a note that
the edit is in the changelog. Creates, deletes, reorders and moves are not checked yet.

**Learned preferences.** Each try is recorded: for this component (or page) and property,
which kind of write passed or failed. Next time, the kind that works for that component is
tried first (score = passes − 2 × fails). It is a lookup table, not machine learning, and
the local model is not asked anything new. Two copies are kept, and either one rebuilds the
other:

- `~/.figma-jev-console/prefs/<figma-file>/<project>--<hash>.json`
- `<project>/.figma-jev-console/prefs.json` (added to `.git/info/exclude`, never to your
  `.gitignore`; the git safety notice does not count it)

`jev_prefs` shows what was learned and where both copies are; `{ reset: true }` clears them.

**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 |
| `JEV_VERIFY` | on | `0` writes edits without checking them in the running app |
| `JEV_HOME` | `~/.figma-jev-console` | Where the log and learned preferences are kept (tests use a temp folder) |

## 🧪 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.

**An edit was "corrected" or ended in the changelog.** Every edit is checked in the running
app. "Corrected" means the first place it was written did not show the change, so it was put
back and written somewhere else. A changelog item means no place worked, so the code was left
unchanged; the item lists each try. The log (`~/.figma-jev-console/sync.log`) has one
`verify` line per try with the value expected and the value found. `JEV_VERIFY=0` turns the
check off.

**The app shows an edit that Figma no longer matches.** Checking reloads the page in a
headless browser, not in yours. If your browser tab does not hot-reload, refresh it.

**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

A3.6/5.0

Scored across 12 tools

Disambiguation2/5

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.

Naming Consistency5/5

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.

Tool Count5/5

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.

Completeness3/5

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.

Maintenance

ActivityMaintained
ResponsivenessNo issues