AgentStack
MCP unreviewed MIT Self-run

Figma Ui Mcp

mcp-tranhoaihung-figma-ui-mcp · by TranHoaiHung

Bidirectional Figma MCP — AI draws UI on Figma canvas, reads designs back

No reviews yet
0 installs
1 views
0.0% view→install

Install

$ agentstack add mcp-tranhoaihung-figma-ui-mcp

Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.

Are you the author of Figma Ui Mcp? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.

About

figma-ui-mcp

Claude Code to Figma · Antigravity to Figma · Cursor to Figma · Any MCP IDE to Figma

✅ Tested: Claude Code, Antigravity  |  🔧 Compatible: Cursor, VS Code, Windsurf, Zed (any MCP stdio client)

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 localhost HTTP 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 Claude Code (CLI)

# 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

Claude Desktop

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"]
    }
  }
}

Cursor

Edit .cursor/mcp.json (project) or ~/.cursor/mcp.json (global):

{
  "mcpServers": {
    "figma": {
      "command": "npx",
      "args": ["-y", "figma-ui-mcp"]
    }
  }
}

VS Code / GitHub Copilot

Edit .vscode/mcp.json (project) or add to settings.json (global):

{
  "mcp": {
    "servers": {
      "figma": {
        "command": "npx",
        "args": ["-y", "figma-ui-mcp"]
      }
    }
  }
}

Windsurf

Edit ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "figma": {
      "command": "npx",
      "args": ["-y", "figma-ui-mcp"]
    }
  }
}

Antigravity (Google)

  1. Open "..." dropdown at the top of the agent panel
  2. Click "Manage MCP Servers""View raw config"
  3. Add to mcp_config.json:
{
  "mcpServers": {
    "figma": {
      "command": "npx",
      "args": ["-y", "figma-ui-mcp"]
    }
  }
}

From source (any client)

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

  1. Download and unzip plugin.zip anywhere on your machine
  2. Open Figma Desktop (required — web app cannot access localhost)
  3. Go to Plugins → Development → Import plugin from manifest...
  4. Select manifest.json from the unzipped folder
  5. 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:

  1. Call figma_docs to load the API reference and design rules
  2. Call figma_read get_page_nodes to understand the current canvas
  3. Call figma_write to create the design on your Figma canvas
  4. Call figma_read screenshot to verify the result

Prompt examples

| Prompt | What happens | |--------|-------------| | "Draw a mobile login screen with social login buttons" | Creates a 390×844 frame with email/password inputs, Apple/Google buttons | | "Read the selected frame and describe the design" | Extracts colors, typography, spacing from your selection | | "Take a screenshot of the current frame" | Returns an inline image the AI can analyze | | "Create a dark theme dashboard with KPI cards" | Draws a full dashboard layout with charts and stats | | "Design an e-commerce product card" | Creates a product card with image, price, rating, CTA | | "Draw a settings page with toggle switches" | Creates grouped settings with icons and toggles |

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

| Layer | Protection | |-------|-----------| | VM sandbox | vm.runInContext() — blocks require, process, fs, fetch | | Localhost only | Bridge binds localhost:38451, never exposed to network | | Operation allowlist | 56 predefined operations accepted (WRITEOPS + READOPS) | | Timeout | 30s VM execution + 60-90s per plugin operation (adaptive by op type) | | Body size limit | 5 MB max per request | | Session isolation | Multi-instance sessions scoped by Figma file ID |


Available Write Operations (figma_write)

Core CRUD

| Operation | Description | |-----------|-------------| | figma.create({ type, ... }) | Create FRAME / RECTANGLE / ELLIPSE / LINE / TEXT / SVG / VECTOR / IMAGE | | figma.modify({ id, ... }) | Update node properties (fill, size, text, layout, etc.) | | figma.delete({ id }) | Remove a single node | | figma.delete({ ids: [...] }) | Batch delete multiple nodes in one call | | figma.query({ type?, name?, id? }) | Find nodes by type, name, or ID | | figma.append({ parentId, childId }) | Move node into parent |

create / modify — advanced props available on any node:

| Prop | Example | Notes | |------|---------|-------| | fill (solid) | "#6C5CE7" or "#6C5CE780" (8-digit hex with alpha) or "rgba(108,92,231,0.5)" | Alpha auto-extracted into paint opacity | | fill (gradient) | { type: "LINEAR_GRADIENT", angle: 135, stops: [{ pos: 0, color: "#7C3AED" }, { pos: 1, color: "#EC4899" }] } | Also RADIAL_GRADIENT | | stroke, strokeWeight, strokeOpacity | Same hex/rgba rules | | | cornerRadius uniform | 12 | All 4 corners | | Individual corners | topLeftRadius: 20, topRightRadius: 20, bottomLeftRadius: 0, bottomRightRadius: 0 | Rounded top sheet pattern | | effects array | [{ type: "DROP_SHADOW", color: "#00000026", offset: {x:0,y:8}, radius: 24, spread: 0 }] | Types: DROP_SHADOW, INNER_SHADOW, LAYER_BLUR, BACKGROUND_BLUR | | TEXT center | textAlign: "CENTER" with explicit width | Auto-infers textAutoResize: "NONE" so centering works | | VECTOR path | d: "M 150 7 A 143 143 0 1 1 29.26 226.62" | SVG A arc auto-converted to cubic Bézier; commas accepted |

Page Management

| Operation | Description | |-----------|-------------| | figma.status() | Current Figma context info | | figma.listPages() | List all pages | | figma.setPage({ name }) | Switch active page | | figma.createPage({ name }) | Add a new page |

Node Operations

| Operation | Description | |-----------|-------------| | figma.clone({ id, x?, y?, parentId? }) | Duplicate a node with optional repositioning | | figma.group({ nodeIds, name? }) | Group multiple nodes | | figma.ungroup({ id }) | Ungroup a GROUP/FRAME | | figma.flatten({ id }) | Flatten/merge vectors into single path | | figma.resize({ id, width, height }) | Resize any node | | figma.set_selection({ ids }) | Programmatically select nodes | | figma.set_viewport({ nodeId?, x?, y?, zoom? }) | Navigate viewport | | figma.batch({ operations }) | Execute up to 50 ops in one call (10-25x faster) |

Components

| Operation | Description | |-----------|-------------| | figma.listComponents() | List all components in document | | figma.createComponent({ nodeId, name? }) | Convert FRAME/GROUP → reusable Component | | figma.instantiate({ componentId/Name, parentId, x, y }) | Create component instance | | figma.instantiate({ ..., overrides: { "LayerName": { text, fill, fontSize, visible, ... } } }) | Instantiate with per-layer overrides |

Design Tokens & Styles

| Operation | Description | |-----------|-------------| | figma.setupDesignTokens({ colors, numbers, fontSizes, fonts, textStyles, modes }) | Bootstrap complete token system (idempotent) — colors + spacing + typography + text styles + multi-mode in 1 call | | figma.createVariableCollection({ name }) | Create variable collection ("Colors", "Spacing") | | figma.createVariable({ name, collectionId, resolvedType, value }) | Create COLOR/FLOAT/STRING/BOOLEAN variable | | figma.addVariableMode({ collectionId, modeName }) | Add mode (e.g. "dark", "compact") | | figma.renameVariableMode({ collectionId, modeId, newName }) | Rename a mode | | figma.removeVariableMode({ collectionId, modeId }) | Remove a mode | | figma.setVariableValue({ variableId/Name, modeId/Name, value }) | Set per-mode value | | figma.modifyVariable({ variableName, value }) | Change variable value — all bound nodes update | | figma.applyVariable({ nodeId, field, variableId/Name }) | Bind variable to a node property | | figma.applyTextStyle({ nodeId, styleName }) | Apply a local text style to a TEXT node by name (auto-loads font) | | figma.setFrameVariableMode({ nodeId, collectionId, modeName }) | Pin frame to a variable mode (Light/Dark, Compact/Large) | | figma.clearFrameVariableMode({ nodeId, collectionId }) | Reset frame to document default mode | | figma.createPaintStyle({ name, color }) | Create reusable paint style | | figma.createTextStyle({ name, fontFamily, fontSize, ... }) | Create reusable text style (manual — prefer setupDesignTokens.textStyles) | | figma.ensure_library() | Create/get Design Library frame | | figma.get_library_tokens() | Read library color + text tokens |

applyVariable supported fields — bind FLOAT/COLOR/STRING/BOOLEAN variables to:

  • Color: fill, stroke
  • Geometry: opacity, width, height, strokeWeight
  • Corner radius: cornerRadius + individual topLeftRadius / 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)

| Operation | Description | |-----------|-------------| | figma.loadImage(url, opts) | Download image → place on canvas | | figma.loadIcon(name, opts) | Fetch SVG icon with 7-library fallback (iOS-filled first) | | figma.loadIconIn(name, opts) | Icon inside centered circle background |

loadIcon fallback priority (filled-first, iOS style preferred): Ionicons (iOS filled) → Fluent UI (Win11 filled) → Bootstrap (filled) → Phosphor (filled) → Tabler Filled (4,500+) → Tabler OutlineLucide (outline fallback)

Free replacemen

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v1.5.1 Imported from the upstream source.