Skip to main content
Glama
hadi21k

Figma MCP Server

by hadi21k
README.md
# Figma MCP Server

An MCP (Model Context Protocol) server that gives AI assistants like Claude full control over Figma — create frames, components, variables, export assets, and build complete UI designs through natural language.

## How It Works

```
Claude / Cursor
      ↕ stdio (JSON-RPC)
  MCP Server (Node.js)
      ↕ WebSocket
  WS Bridge (127.0.0.1:9001)
      ↕ Plugin API
  Figma Plugin (Figma Desktop)
```

1. You ask Claude to create or modify a design
2. Claude calls an MCP tool — the MCP server validates the arguments
3. The server sends a command over a local WebSocket to the bridge
4. The bridge forwards it to the Figma plugin running in Figma Desktop
5. The plugin executes `figma.*` API calls and returns the result
6. Claude receives the result and continues building

## Prerequisites

- **Node.js** v18 or later
- **Figma Desktop** — the web version does not support plugins with network access
- **Claude Desktop** or **Cursor** (or any MCP-compatible client)

## Installation

### 1. Clone and install dependencies

```bash
git clone https://github.com/your-username/figma-mcp.git
cd figma-mcp
npm install
```

### 2. Build the project

```bash
npm run build
```

This compiles `src/mcp-server/` and `src/websocket-server/` to `dist/`. The Figma plugin (`src/figma-plugin/`) is plain JavaScript and needs no compilation.

### 3. Start the WebSocket bridge

```bash
npm run socket
```

For development with auto-reload:

```bash
npm run dev:socket
```

You should see:
```
{"level":"info","component":"bridge","msg":"Listening on 127.0.0.1:9001"}
```

### 4. Load the plugin in Figma Desktop

1. Open **Figma Desktop**
2. Go to **Plugins → Development → Import plugin from manifest...**
3. Select `src/figma-plugin/manifest.json`
4. Run it from **Plugins → Development → Figma MCP Bridge**

The plugin UI shows **"Connected"** when the bridge connection is established.

### 5. Connect your AI client

#### Claude Desktop

Add to `claude_desktop_config.json` (find it at `~/Library/Application Support/Claude/` on macOS or `%APPDATA%\Claude\` on Windows):

```json
{
  "mcpServers": {
    "figma": {
      "command": "node",
      "args": ["/ABSOLUTE/PATH/TO/figma-mcp/dist/mcp-server/index.js"]
    }
  }
}
```

#### Cursor

Add to your MCP config (`.cursor/mcp.json` or via **Cursor Settings → MCP**):

```json
{
  "mcpServers": {
    "figma": {
      "command": "node",
      "args": ["/ABSOLUTE/PATH/TO/figma-mcp/dist/mcp-server/index.js"]
    }
  }
}
```

Replace `/ABSOLUTE/PATH/TO/figma-mcp` with the actual path to this repo.

Restart your AI client after saving the config. The Figma tools will appear automatically.

## Available Tools

67 tools organized into categories:

### Reading & Inspection

| Tool | Description |
|------|-------------|
| `get_document_info` | Get document structure: all pages, top-level frames, and metadata |
| `get_selection` | Get all currently selected nodes with their properties |
| `get_node` | Get detailed properties of a specific node by ID |
| `find_nodes` | Search for nodes by type and/or name pattern across the document |

### Creating Shapes & Elements

| Tool | Description |
|------|-------------|
| `create_frame` | Create a layout frame — the primary container for UI design |
| `create_rectangle` | Create a rectangle (background fills, dividers, placeholder shapes) |
| `create_ellipse` | Create an ellipse or circle (avatars, status dots, decorative circles) |
| `create_text` | Create a text node with typography settings |
| `create_line` | Create a line (dividers, separators, strokes) |
| `create_polygon` | Create a regular polygon (triangle, hexagon, etc.) |
| `create_star` | Create a star shape with configurable point count and inner radius |
| `create_vector` | Create a vector node from SVG path data |
| `create_svg_node` | Create a node from an SVG markup string |

### Modifying Nodes

| Tool | Description |
|------|-------------|
| `update_node` | Update position, size, opacity, visibility, name, or fills of any node |
| `update_text` | Update text content or typography of an existing text node |
| `set_auto_layout` | Apply auto layout to a frame for automatic spacing and alignment |
| `set_node_layout_properties` | Set child-level layout properties inside an auto-layout frame |
| `set_fill` | Set solid or gradient fills on any node |
| `set_stroke` | Set stroke (border/outline) with color, dash patterns, and line caps |
| `set_corner_radius` | Set individual corner radii for asymmetric rounding |
| `set_effects` | Set visual effects: drop shadow, inner shadow, blur, background blur |
| `add_shadow` | Add a drop shadow for depth and elevation |
| `set_rotation` | Set the rotation angle of a node in degrees |
| `set_blend_mode` | Set layer blend mode (multiply, screen, overlay, etc.) |
| `set_constraints` | Set responsive pinning/resize constraints |
| `set_layout_grids` | Add column, row, or pixel grids to a frame |
| `set_text_decoration` | Set underline or strikethrough on a text node |
| `set_text_case` | Set text case transformation (upper, title, small caps, etc.) |
| `set_text_list` | Apply bullet or numbered list formatting to a text node |
| `lock_node` | Lock or unlock a node to protect it from accidental edits |

### Organizing Nodes

| Tool | Description |
|------|-------------|
| `group_nodes` | Group multiple nodes into a visual group |
| `ungroup_nodes` | Ungroup a group and move its children to the parent |
| `delete_node` | Permanently remove a node and its children |
| `clone_node` | Duplicate a node with all its properties and children |
| `reorder_node` | Move a node to a specific z-order index within its parent |
| `flatten_node` | Merge a node and its children into a single vector |
| `create_boolean_operation` | Combine shapes with union, intersect, subtract, or exclude |

### Components & Instances

| Tool | Description |
|------|-------------|
| `create_component` | Convert a node into a reusable Figma component |
| `create_component_instance` | Place an instance of an existing component |
| `get_local_components` | List all components defined in the document |
| `combine_as_variants` | Combine multiple components into a variant set |
| `detach_instance` | Detach an instance from its master component |
| `swap_component` | Swap the component an instance references |
| `import_component_by_key` | Import a component from a shared library by key |

### Style System

| Tool | Description |
|------|-------------|
| `create_paint_style` | Create a reusable color/paint style (design token) |
| `create_text_style` | Create a reusable text style (typography token) |
| `create_effect_style` | Create a reusable effect style (elevation/shadow token) |
| `get_local_styles` | List all paint, text, and effect styles in the document |
| `apply_style` | Apply a style to a node by style ID |

### Variables & Design Tokens

| Tool | Description |
|------|-------------|
| `create_variable_collection` | Create a variable collection with support for multiple modes (e.g. Light/Dark) |
| `create_variable` | Create a design token variable: color, number, string, or boolean |
| `bind_variable` | Bind a variable to a node property for theme-aware designs |
| `get_variables` | List all variable collections and their variables |

### Images

| Tool | Description |
|------|-------------|
| `set_image_from_url` | Fetch an image from a public URL and apply it as a fill |
| `set_image_from_path` | Read a local image file and apply it as a fill |
| `set_image_fill` | Apply an image fill using raw base64 data |

### Export

| Tool | Description |
|------|-------------|
| `export_node` | Export a node as PNG, JPG, SVG, or PDF — auto-saved and returned as an image |

### Pages & Sections

| Tool | Description |
|------|-------------|
| `create_page` | Create a new page in the Figma document |
| `create_section` | Create a section on the canvas to visually organize frames |
| `set_current_page` | Navigate to a specific page by ID |

### Workflow & Utility

| Tool | Description |
|------|-------------|
| `zoom_to_node` | Scroll and zoom the canvas to center on a node |
| `set_selection` | Set the canvas selection to specific nodes |
| `list_available_fonts` | List all fonts available in the Figma environment |
| `batch_create` | Create multiple nodes in a single call |
| `batch_update` | Update properties of multiple nodes in a single call |
| `notify` | Show a toast notification in the Figma UI |

## Example Prompts

### Landing Page Hero Section

> "Create a 1440×900 hero section frame. Add a dark navy background, a large white heading 'Build faster with AI', a subtitle below it in lighter gray, and a blue rounded CTA button. Center everything with auto layout."

### Card Component

> "Create a card component — 320×400 frame, 12px corner radius, subtle drop shadow, 16px padding. Include a gray image placeholder at the top, a bold title, a description, and a 'Learn more' link at the bottom."

### Design System Setup

> "Set up a basic design system in this document. Create color styles for Primary (#4F46E5), Background (#F9FAFB), and Text (#111827). Create text styles for Heading H1 (32px bold Inter), Body (16px regular Inter), and Caption (12px regular Inter)."

### Component with Variants

> "Create a button component with three variants: Primary (blue fill, white text), Secondary (white fill, blue border), and Destructive (red fill, white text). Combine them into a variant set."

### Dark/Light Theme with Variables

> "Create a variable collection called 'Theme' with Light and Dark modes. Add a 'Background' color variable: white (#FFFFFF) in Light, dark gray (#1A1A2E) in Dark. Add a 'Text/Primary' color variable: dark (#111827) in Light, white (#F9FAFB) in Dark."

### Image-Based Layout

> "Create a 1200×600 feature section. Fetch the image from https://images.unsplash.com/photo-example and apply it to the left half. On the right half, add a heading and description with auto layout."

## Configuration

### Environment Variables

Copy `.env.example` to `.env` to customize the defaults:

| Variable | Default | Description |
|----------|---------|-------------|
| `WS_HOST` | `127.0.0.1` | Host the bridge binds to — keep loopback-only |
| `WS_PORT` | `9001` | Port the bridge listens on |
| `WS_URL` | `ws://127.0.0.1:9001?role=mcp-client` | WebSocket URL the MCP server connects to |
| `WS_TIMEOUT_MS` | `30000` | How long (ms) the MCP server waits for a plugin response |
| `LOG_LEVEL` | `info` | `trace` / `debug` / `info` / `warn` / `error` / `silent` |
| `LOG_PRETTY` | `false` | Set `true` for human-readable colored log output |
| `LOG_FILE` | _(stderr)_ | Absolute path to write logs to a file instead of stderr |

### Export Directory

Exported nodes are auto-saved to `./exports/` by default. Override with the `FIGMA_EXPORT_DIR` environment variable:

```bash
FIGMA_EXPORT_DIR=/my/designs/exports npm run socket
```

## Development

```bash
# Build TypeScript
npm run build

# Watch mode (auto-reload bridge)
npm run dev:socket

# Run all tests
npm test

# Watch tests
npm run test:watch

# Coverage report (80% threshold)
npm run test:coverage
```

## Project Structure

```
figma-mcp/
├── src/
│   ├── shared/                    # Wire protocol & constants (shared between server and bridge)
│   │   ├── constants.ts           # COMMAND_NAMES (single source of truth), defaults
│   │   ├── protocol.ts            # Message types, error codes
│   │   ├── index.ts               # Barrel re-export
│   │   └── logger/                # Structured logging (pino-based)
│   │       ├── logger.ts          # createLogger() factory
│   │       ├── metrics.ts         # MetricsCollector (counters, histograms)
│   │       └── redact.ts          # Sensitive field redaction
│   │
│   ├── mcp-server/                # MCP server (Claude ↔ bridge)
│   │   ├── index.ts               # Server setup, tool registration, server-side tools
│   │   ├── ws-client.ts           # WebSocket client with reconnect logic
│   │   ├── request-tracker.ts     # Promise-based request/response matching
│   │   └── tools/                 # Tool schemas split by category
│   │       ├── index.ts           # Merges all partial registries → TOOL_REGISTRY
│   │       ├── shared-schemas.ts  # Reusable Zod schemas (RGBAColor, Fill, Typography…)
│   │       ├── read-tools.ts
│   │       ├── create-tools.ts
│   │       ├── modify-tools.ts
│   │       ├── organize-tools.ts
│   │       ├── viewport-tools.ts
│   │       ├── style-tools.ts
│   │       ├── layout-tools.ts
│   │       ├── component-tools.ts
│   │       ├── style-system-tools.ts
│   │       ├── image-tools.ts
│   │       ├── export-tools.ts
│   │       ├── typography-tools.ts
│   │       ├── constraint-tools.ts
│   │       ├── batch-tools.ts
│   │       ├── vector-tools.ts
│   │       ├── page-tools.ts
│   │       ├── traversal-tools.ts
│   │       ├── variable-tools.ts
│   │       ├── workflow-tools.ts
│   │       ├── design-system-tools.ts
│   │       ├── manipulation-tools.ts
│   │       └── extra-shape-tools.ts
│   │
│   ├── websocket-server/          # WebSocket bridge (routes commands between MCP and plugin)
│   │   ├── index.ts               # FigmaBridge class and entry point
│   │   ├── config.ts              # BridgeConfig + loadConfig()
│   │   └── validation.ts          # Message validation, ProtocolError
│   │
│   └── figma-plugin/              # Figma plugin (plain JS, no build step)
│       ├── manifest.json          # Plugin manifest
│       ├── code.js                # Plugin sandbox — handles all figma.* API calls
│       └── ui.html                # Hidden iframe — holds the WebSocket connection
│
├── tests/
│   ├── mcp-server/
│   │   ├── tools.test.ts          # Zod schema validation for all tools
│   │   └── request-tracker.test.ts
│   ├── websocket-server/
│   │   └── bridge.test.ts         # Bridge routing and client management
│   ├── integration/
│   │   └── roundtrip.test.ts      # Full MCP → bridge → mock plugin round-trips
│   └── shared/
│       ├── logger.test.ts
│       └── metrics.test.ts
│
├── exports/                       # Auto-created when export_node is used
├── logs/                          # Optional log output directory
├── .env.example                   # Environment variable reference
├── package.json
├── tsconfig.json
└── vitest.config.ts
```

## Troubleshooting

### Plugin shows "Disconnected" or never connects

1. Make sure `npm run socket` is running and shows "Listening on 127.0.0.1:9001"
2. Confirm the plugin is active in **Plugins → Development → Figma MCP Bridge**
3. If port 9001 is in use by another process, change the port:
   ```bash
   WS_PORT=9002 npm run socket
   ```
   Then update `WS_URL` in `.env` to match.

### Commands time out

1. Confirm the plugin shows "Connected" — commands cannot reach Figma without an active connection
2. Font operations (text nodes) load fonts asynchronously — the default 30s timeout handles most cases
3. For slow operations, increase the timeout: `WS_TIMEOUT_MS=60000`
4. Check that Figma Desktop is responsive and not showing any error dialogs

### Bridge port already in use (EADDRINUSE)

A previous bridge process is still running. On macOS/Linux:

```bash
lsof -i :9001
kill <PID>
```

On Windows (PowerShell):

```powershell
Get-Process -Id (Get-NetTCPConnection -LocalPort 9001).OwningProcess | Stop-Process
```

### MCP tools do not appear in Claude / Cursor

1. Make sure `npm run build` completed without TypeScript errors
2. Verify the absolute path in your MCP config points to `dist/mcp-server/index.js`
3. Restart your AI client after every config change
4. Check your client's MCP logs for connection errors

### Images not loading (`set_image_from_url`)

- The URL must be publicly accessible (no auth, no paywalls)
- Supported formats: PNG, JPEG, GIF, WebP, SVG
- Maximum file size: 5 MB (oversized images are auto-optimized before being sent)

## Architecture Notes

- **Security:** The bridge binds to `127.0.0.1` only — it is never exposed to the network. The plugin's `networkAccess.allowedDomains` is set to `none` in production.
- **Message size:** Maximum WebSocket message is 8 MB to support high-resolution image fills.
- **Reconnection:** The plugin auto-reconnects with exponential backoff (1s base, 2× multiplier, 30s max) if the bridge restarts.
- **Server-side tools:** `set_image_from_url` and `set_image_from_path` run entirely in Node.js (fetch + encode) and forward to `set_image_fill` — they never require extra plugin code.
- **Logging:** All Node.js logs go to stderr to avoid corrupting MCP's JSON-RPC on stdout. Set `LOG_PRETTY=true` for readable development output.

## Contributing

1. Add the command name to `COMMAND_NAMES` in `src/shared/constants.ts`
2. Create the tool schema in the appropriate file under `src/mcp-server/tools/`
3. Add the handler in `src/figma-plugin/code.js`
4. Add the command to `ALLOWED_COMMANDS` in both `code.js` and `ui.html`
5. Add tests in `tests/mcp-server/tools.test.ts`

See `CLAUDE.md` for the full contributor guide.