figma-ui-mcp
Bidirectional Figma MCP bridge — let AI assistants (Claude Code, Cursor, Windsurf, Antigravity, VS Code Copilot, or any MCP-compatible IDE) draw UI directly on Figma canvas and read existing designs back as structured data, screenshots, or code-ready tokens. No Figma API key needed — works entirely over localhost.
Requires Figma Desktop — the plugin communicates with the MCP server over
localhostHTTP polling. Figma's web app does not allow localhost network access, so Figma Desktop is required.
Claude ──figma_write──▶ MCP Server ──HTTP (localhost:38451)──▶ Figma Plugin ──▶ Figma Document
Claude ◀─figma_read──── MCP Server ◀──HTTP (localhost:38451)── Figma Plugin ◀── Figma Document
How the localhost bridge works
The MCP server starts a small HTTP server bound to localhost:38451. The Figma plugin (running inside Figma Desktop) uses long polling — the server holds requests up to 8s until work arrives, flushing immediately when new ops are queued (near-realtime latency <100ms). All traffic stays on your machine — nothing is sent to any external server.
Multi-instance support (v2.3.0+): Multiple Figma files/tabs can connect simultaneously. Each plugin instance sends a sessionId, and the bridge routes operations to the correct session. Use the optional sessionId param in figma_write/figma_read to target a specific file.
Features
What's new in v2.5
Full version history: see CHANGELOG.md.
Quick Start
Step 1 — Add the MCP server to your AI client
Choose your platform:
# Project scope (default)
claude mcp add figma-ui-mcp -- npx figma-ui-mcp
# Global scope (all projects)
claude mcp add --scope user figma-ui-mcp -- npx figma-ui-mcp
Edit config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"figma": {
"command": "npx",
"args": ["-y", "figma-ui-mcp"]
}
}
}
Edit .cursor/mcp.json (project) or ~/.cursor/mcp.json (global):
{
"mcpServers": {
"figma": {
"command": "npx",
"args": ["-y", "figma-ui-mcp"]
}
}
}
Edit .vscode/mcp.json (project) or add to settings.json (global):
{
"mcp": {
"servers": {
"figma": {
"command": "npx",
"args": ["-y", "figma-ui-mcp"]
}
}
}
}
Edit ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"figma": {
"command": "npx",
"args": ["-y", "figma-ui-mcp"]
}
}
}
- Open "..." dropdown at the top of the agent panel
- Click "Manage MCP Servers" → "View raw config"
- Add to
mcp_config.json:
{
"mcpServers": {
"figma": {
"command": "npx",
"args": ["-y", "figma-ui-mcp"]
}
}
}
git clone https://github.com/TranHoaiHung/figma-ui-mcp
cd figma-ui-mcp
npm install
# Then point your MCP client to: node /path/to/figma-ui-mcp/server/index.js
⚠️ IMPORTANT: After adding the MCP server, you MUST restart your IDE / AI client (quit and reopen). The MCP server only loads on startup — simply saving the config file is not enough. This applies to Claude Code, Cursor, VS Code, Windsurf, and Antigravity.
Step 2 — Install the Figma plugin
⬇ Download plugin.zip — no git clone needed
- Download and unzip
plugin.zipanywhere on your machine - Open Figma Desktop (required — web app cannot access localhost)
- Go to Plugins → Development → Import plugin from manifest...
- Select
manifest.jsonfrom the unzipped folder - Run Plugins → Development → Figma UI MCP Bridge
The plugin UI shows a green dot when the MCP server is connected.
Updating to a newer version
# Step 1 — get the new version + plugin path
npx figma-ui-mcp@latest --version
# figma-ui-mcp v2.5.12 — plugin: /.../.npm/_npx/.../figma-ui-mcp/plugin
# Step 2 — restart Claude / your IDE so the MCP server reloads
# Step 3 — re-link the Figma plugin (manual, one-time per update)
# Figma Desktop → Plugins → Development → Manage plugins in development
# Remove old "Figma UI MCP Bridge" → "+" → Import plugin from manifest...
# Select manifest.json from the plugin path printed in Step 1
# Step 4 — verify
# Ask your AI: "figma_status"
# pluginVersion in the response should match the npm version above
The Figma plugin does not auto-update — re-linking (Step 3) is required whenever the plugin changes.
Step 3 — Connect AI to Figma
Tell your AI assistant to connect:
"Connect to figma-ui-mcp"
The AI will call figma_status and confirm:
✅ Connected — File: "My Project", Page: "Page 1", Plugin v2.5.5
If you see "Plugin not connected", make sure the Figma plugin is running (Step 2).
Step 4 — Start designing with prompts
Once connected, just describe what you want in natural language:
"Use figma-ui-mcp to draw a login screen for mobile"
The AI will automatically:
- Call
figma_docsto load the API reference and design rules - Call
figma_read get_page_nodesto understand the current canvas - Call
figma_writeto create the design on your Figma canvas - Call
figma_read screenshotto verify the result
Prompt examples
Tips for better results
- Be specific about style:
"dark theme","glassmorphism","minimal white"gives the AI clear direction - Mention platform:
"mobile"(390×844),"tablet"(768×1024),"desktop"(1440×900) - Iterate: After the first draw, say
"fix the spacing"or"make the buttons bigger"— the AI reads and modifies existing nodes - Use selection: Select a frame in Figma and ask
"improve this design"— the AI reads your selection first - Multi-screen flows:
"Now draw the signup screen next to the login screen"— the AI positions frames side by side
Workflow summary
You: "Connect to figma-ui-mcp"
AI: ✅ Connected to Figma
You: "Draw a mobile onboarding screen with 3 steps"
AI: [calls figma_docs → figma_write → figma_read screenshot]
AI: ✅ Done — here's what I created: [inline screenshot]
You: "The title text is not centered"
AI: [calls figma_read get_selection → figma_write modify → screenshot]
AI: ✅ Fixed — text is now centered
You: "Now draw the next onboarding screen beside it"
AI: [reads page_nodes to find position → draws at x+440]
AI: ✅ Done — 2 screens side by side
figma_status — check connection (always call first)
figma_docs — load API reference (call before drawing)
figma_write — draw / modify UI on canvas
figma_read — extract design data, screenshots, SVG
Usage Examples
Draw a screen
Ask Claude: "Draw a dark dashboard with a sidebar, header, and 4 KPI cards"
Claude calls figma_write with code like:
await figma.createPage({ name: "Dashboard" });
await figma.setPage({ name: "Dashboard" });
const root = await figma.create({
type: "FRAME", name: "Dashboard",
x: 0, y: 0, width: 1440, height: 900,
fill: "#0f172a",
});
const sidebar = await figma.create({
type: "FRAME", name: "Sidebar",
parentId: root.id,
x: 0, y: 0, width: 240, height: 900,
fill: "#1e293b", stroke: "#334155", strokeWeight: 1,
});
await figma.create({
type: "TEXT", name: "App Name",
parentId: sidebar.id,
x: 20, y: 24, content: "My App",
fontSize: 16, fontWeight: "SemiBold", fill: "#f8fafc",
});
// ... continue
Read a design
Ask Claude: "Read my selected frame and convert it to Tailwind CSS"
Claude calls figma_read with operation: "get_selection", receives the full node tree,
then generates corresponding code.
Screenshot a frame
figma_read → operation: "screenshot" → nodeId: "123:456"
Returns a base64 PNG Claude can analyze and describe.
Architecture
figma-ui-mcp/
├── server/
│ ├── index.js MCP server (stdio transport)
│ ├── bridge-server.js HTTP bridge on localhost:38451 (long-poll, multi-session)
│ ├── code-executor.js VM sandbox — safe JS execution + 7-lib icon fetcher
│ ├── tool-definitions.js MCP tool schemas (figma_status / _write / _read / _docs)
│ └── api-docs.js API reference text (served to AI via figma_docs)
├── src/plugin/ Plugin source (concat-built into plugin/code.js)
│ ├── utils.js, svg-path-helpers.js, paint-and-effects.js, read-helpers.js
│ ├── handlers-write.js, handlers-read.js, handlers-read-detail.js,
│ │ handlers-library.js, handlers-tokens.js, handlers-write-ops.js
│ └── main.js
└── plugin/
├── manifest.json Figma plugin manifest
├── code.js Plugin main (auto-generated — 3600+ LOC)
└── ui.html Plugin UI — long-poll client + status dot
Security
Available Write Operations (figma_write)
Core CRUD
create / modify — advanced props available on any node:
Page Management
Node Operations
Components
Design Tokens & Styles
applyVariable supported fields — bind FLOAT/COLOR/STRING/BOOLEAN variables to:
- Color:
fill,stroke - Geometry:
opacity,width,height,strokeWeight - Corner radius:
cornerRadius+ individualtopLeftRadius/topRightRadius/bottomLeftRadius/bottomRightRadius - Spacing (auto-layout):
paddingTop,paddingBottom,paddingLeft,paddingRight,itemSpacing,counterAxisSpacing - Typography (TEXT nodes):
fontSize,letterSpacing,lineHeight,paragraphSpacing,paragraphIndent - Font swap (STRING):
fontFamily,fontStyle,characters— swap Inter → SF Pro via 1 variable - Visibility (BOOLEAN):
visible
Image & Icon Helpers (server-side)
loadIcon fallback priority (filled-first, iOS style preferred):
Ionicons (iOS filled) → Fluent UI (Win11 filled) → Bootstrap (filled) → Phosphor (filled) → Tabler Filled (4,500+) → Tabler Outline → Lucide (outline fallback)
Free replacement for paid Icons8 ios-filled. Ionicons naming quirks: Bell→notifications, Back→chevron-back, Clock→time, Fire→flame, Lightning→flash, Lock→lock-closed.
Prototyping & Interactions
Supported transitions: SMART_ANIMATE, DISSOLVE, MOVE_IN, MOVE_OUT, PUSH, SLIDE_IN, SLIDE_OUT, INSTANT
Supported easings: LINEAR, EASE_IN, EASE_OUT, EASE_IN_AND_OUT, CUSTOM_BEZIER
Scroll Behavior
Variant & Component Swapping
Available Read Operations (figma_read)
includeHidden param (boolean, default false) — available on get_selection, get_design, search_nodes, scan_design. When false (default), nodes with visible: false are skipped. Pass true to include hidden layers.
Working with an Existing Project
When opening a Figma file that already has a design system, always read before drawing.
Step 1 — Read what exists
figma_read get_variables → load variable IDs (Design Tokens)
figma_read get_styles → load paint/text style IDs and hex values
figma_read get_local_components → load component IDs
figma_read get_page_nodes → load top-level frame IDs
Step 2 — Build lookup maps in figma_write
// Load variables → build varMap: name → id
var vars = await figma.get_variables();
var varMap = {};
for (var ci = 0; ci < vars.collections.length; ci++) {
var col = vars.collections[ci];
for (var vi = 0; vi < col.variables.length; vi++) {
var v = col.variables[vi];
varMap[v.name] = v.id;
}
}
// Load styles → build colorMap: name → hex, textMap: name → {fontSize, fontWeight}
var styles = await figma.get_styles();
var colorMap = {}, textMap = {};
styles.paintStyles.forEach(function(s) { colorMap[s.name] = s.hex; });
styles.textStyles.forEach(function(s) { textMap[s.name] = s; });
// Load components → build compMap: name → id
var comps = await figma.get_local_components();
var compMap = {};
comps.components.forEach(function(c) { compMap[c.name] = c.id; });
Step 3 — Create nodes using discovered values
// Use colorMap for fill (hex from existing styles)
var card = await figma.create({
type: "FRAME", name: "Card",
fill: colorMap["color/bg-surface"] || "#FFFFFF",
width: 360, height: 200,
layoutMode: "VERTICAL", paddingTop: 16, paddingLeft: 16,
paddingBottom: 16, paddingRight: 16, itemSpacing: 12,
});
// Then bind variables so light/dark mode switches propagate
if (varMap["bg-surface"])
await figma.applyVariable({ nodeId: card.id, field: "fill", variableId: varMap["bg-surface"] });
if (varMap["radius-md"])
await figma.applyVariable({ nodeId: card.id, field: "cornerRadius",variableId: varMap["radius-md"] });
if (varMap["spacing-md"])
await figma.applyVariable({ nodeId: card.id, field: "paddingTop", variableId: varMap["spacing-md"] });
Step 4 — Instantiate components with overrides
// Prefer existing components over drawing from scratch
if (compMap["btn/primary"]) {
await figma.instantiate({
componentId: compMap["btn/primary"],
parentId: card.id,
overrides: { "Label": { text: "Confirm", fill: "#FFFFFF" } }
});
}
Step 5 — Pin frames to Light / Dark mode
var collection = vars.collections.find(function(c) { return c.name === "Design Tokens"; });
// Duplicate frame and preview both modes side by side
var frames = await figma.get_page_nodes();
var homeId = frames.find(function(f) { return f.name === "Home"; }).id;
var light = await figma.clone({ id: homeId, x: 0, name: "Preview/Light" });
var dark = await figma.clone({ id: homeId, x: 1540, name: "Preview/Dark" });
await figma.setFrameVariableMode({ nodeId: light.id, collectionId: collection.id, modeName: "light" });
await figma.setFrameVariableMode({ nodeId: dark.id, collectionId: collection.id, modeName: "dark" });
Full API reference and all design rules: run
figma_docsin your AI client.
Star History
If figma-ui-mcp helps you, please give it a star — it helps others discover the project!
License
MIT © TranHoaiHung — free to use, modify, and distribute. See LICENSE for details.
Keywords
figma mcp, claude code to figma, cursor to figma, ai to figma, figma ai plugin, figma mcp bridge, figma mcp server, figma design to code, code to figma design, ai ui design, figma automation, figma plugin ai, model context protocol figma, claude figma, windsurf figma, vs code figma, antigravity figma, ai design tool, figma api alternative, figma localhost plugin, draw ui with ai, ai generate figma design, figma design system ai, mcp server figma, figma read design, figma write design, bidirectional figma, figma desktop plugin, npx figma-ui-mcp