FreeMCP for Figma
by w4ndaja
README.md
# FreeMCP for Figma — Universal MCP Bridge
A local MCP bridge that lets AI coding agents **read and write a user's Figma
file through the Figma Plugin API** (native, in-desktop) instead of the public
REST API. No official-MCP rate limits, no Figma API token, and no design data
ever leaves the machine.
- **Offline & private** — everything runs on `127.0.0.1`; a startup guard blocks
any outbound HTTP from the server process.
- **Native design-system writes** — create frames/shapes/text, author components,
insert library component instances, bind variables & styles, configure Auto
Layout, constraints and effects — all executed inside Figma via the Plugin API.
- **Design-to-code** — generate React (Tailwind), Vue 3, or HTML/CSS from a
selected node.
- **Batched & undoable** — `batch_run` applies 100+ ops in one consented request
without blocking the Figma UI; `undo` reverts the last write(s).
> **Transport**: the bridge is **HTTP long-poll** (`POST /register`, `GET /next`,
> `POST /result`) on `127.0.0.1:3700`, not WebSocket. The Figma plugin sandbox
> has **no `WebSocket` global** — only `fetch` — so the plugin long-polls a
> loopback HTTP server hosted by the local MCP process.
## Architecture
```
┌──────────────┐ MCP/STDIO ┌────────────────────────────┐ HTTP long-poll ┌──────────────────┐
│ AI client │ ◀──────────▶ │ local MCP server (Node) │ ◀───────────────▶ │ Figma plugin │
│ Claude/VS │ JSON-RPC │ packages/server │ /register │ packages/plugin │
│ Cursor/Ollama│ │ 127.0.0.1:3700 │ /next /result │ (Plugin API) │
└──────────────┘ └────────────────────────────┘ └──────────────────┘
```
Every MCP tool is a thin dispatch. The server validates arguments (Zod),
translates the call into a typed bridge frame, and forwards it over loopback
HTTP to the Figma plugin, which executes the actual Plugin API call and returns
the result. Deep-dive: [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md).
## Quickstart (< 5 min)
### 1. Install
```bash
npm install
npm run build:plugin
```
### 2. Load the plugin in Figma
Figma → **Plugins → Development → Import plugin from manifest…** → select
`packages/plugin/manifest.json`. The plugin auto-connects to
`http://127.0.0.1:3700` (a small status panel shows connection state).
### 3. Start the server (own terminal)
```bash
npm run start:server
```
Expect: `FreeMCP Figma bridge on http://127.0.0.1:3700. Open the plugin in Figma to connect.`
### 4. Point your AI client at the MCP server (STDIO)
```json
{
"mcpServers": {
"figma-free-mcp": {
"command": "node",
"args": ["C:/path/to/repo/packages/server/dist/index.js"]
}
}
}
```
Claude Desktop: Settings → Developer → Edit Config. VS Code / Cursor: `.mcp.json`.
You can also run the built bundle directly: `node packages/server/dist/index.js`.
## Using it
| Intent | What the agent does |
|--------|---------------------|
| "Describe this design" | `get_design_context` / `get_selection` → full node tree, fills, typography, effects, Auto Layout |
| "What tokens exist?" | `get_variables` / `get_styles` → local variables, collections, text/effect/grid styles |
| "Create a card component" | `create_frame` (Auto Layout) + `create_text` children → native Figma nodes |
| "Insert our primary button" | `create_component_instance` by key → reuses the design system (local or library) |
| "Make 100 variants" | `batch_run` → many ops in one consented request, no UI block |
| "Undo last change" | `undo` → reverts creates/duplicates/fills/layout |
| "Generate React for this" | `to_react` / `to_vue` / `to_html` |
**Consent**: the **first write always prompts for consent** — every write tool
requires `confirm: true` in its params (FR-504). Nothing is written silently.
## Tool reference
Every input is validated by the Zod schema in
`packages/contracts/src/tool-schemas.ts` (single source of truth) before
dispatch (FR-404). The per-tool contract lives in
[specs/001-freemcp-figma-bridge/contracts/tools.md](specs/001-freemcp-figma-bridge/contracts/tools.md).
### Read
| Tool | Purpose | Key params |
|------|---------|-----------|
| `get_selection` | Current selection → `DesignContext[]` (optionally a node by id) | `nodeId?`, `maxDepth?` |
| `get_design_context` | Deep hierarchical context for a node subtree | `nodeId`, `maxDepth?` |
| `get_metadata` | Lightweight tree: ids, types, names, bounds | `nodeId?` |
| `get_screenshot` | Raster/SVG export of a node (base64) | `nodeId`, `format` (`png\|jpg\|svg`), `scale?` |
| `get_variables` | Local variables + collections + mode values | — |
| `get_styles` | Local text/effect/grid styles | — |
| `get_components` | Local components + component sets with importable keys | — |
| `get_page_children` | Top-level nodes on the current page (id/name/type/bounds) | — |
### Write (all require `confirm: true`)
| Tool | Purpose | Key params |
|------|---------|-----------|
| `create_frame` | Frame at position/size, optional Auto Layout + sizing | `x`, `y`, `width`, `height`, `layout?`, `parent?`, `primaryAxisSizing?`, `counterAxisSizing?`, `stroke?`, `strokeWeight?`, `cornerRadius?` |
| `create_shape` | Rectangle / ellipse / polygon | `type`, `x`, `y`, `width`, `height`, `fill?`, `stroke?`, `layoutAlign?`, `layoutGrow?`, `parent?` |
| `create_text` | Text node with font props + text polish | `characters`, `x`, `y`, `fontFamily?`, `fontSize?`, `fontWeight?`, `color?`, `lineHeight?`, `letterSpacing?`, `textCase?`, `textDecoration?`, `textAutoResize?`, `layoutAlign?`, `layoutGrow?`, `parent?` |
| `apply_fill` | Apply solid/gradient fill to a node (image fills deferred — need a Figma imageHash) | `nodeId`, `fill` |
| `set_auto_layout` | Auto Layout direction, padding, spacing, alignment | `nodeId`, `mode`, `itemSpacing?`, `padding?`, `primaryAxisAlignItems?`, `counterAxisAlignItems?` |
| `duplicate_node` | Clone preserving properties + children | `nodeId`, `offset?` |
| `delete_node` | Delete a node (not undoable) | `nodeId` |
| `create_component_instance` | Insert an instance by key (local or published library) | `componentKey`, `x?`, `y?`, `parent?`, `overrides?` |
| `create_component` | Author a reusable `ComponentNode` (design-system library) | `x`, `y`, `width`, `height`, `layout?`, `parent?` |
| `set_variable` | Bind a variable to a node property; `""` unbinds | `nodeId`, `property` (`fill\|strokes\|cornerRadius\|opacity`), `variableId` |
| `apply_style` | Apply a text/effect/grid style; `""` clears | `nodeId`, `kind` (`text\|effect\|grid`), `styleId` |
| `set_constraints` | Layout constraints (align/grow + frame sizing) | `nodeId`, `layoutAlign?`, `layoutGrow?`, `primaryAxisSizing?`, `counterAxisSizing?` |
| `set_effect` | Set arbitrary effect array (e.g. DROP_SHADOW); `[]` clears | `nodeId`, `effects` |
### Batch
| Tool | Purpose | Key params |
|------|---------|-----------|
| `batch_run` | Run many write ops in one consented batch (100+ < 3s) | `ops: { tool, params }[]`, `confirm` |
### Code generation
| Tool | Purpose | Key params |
|------|---------|-----------|
| `to_react` | JSX + Tailwind classes | `nodeId`, `includeStyles?` |
| `to_vue` | Vue 3 Composition API SFC | `nodeId` |
| `to_html` | Semantic HTML + CSS | `nodeId` |
### Utility
| Tool | Purpose | Key params |
|------|---------|-----------|
| `undo` | Revert last executed write op(s) | `count?` (1–100) |
| `ping` | Health check; returns bridge connectivity + latency | — |
**Error envelope** (FR-405): every tool result is `{ ok: true, data }` or
`{ ok: false, error: { code, message, suggestion } }`, serialized as a JSON
string in the MCP text content. `suggestion` carries a fixable hint.
## Security & privacy model
- **No tokens, no cloud** (FR-501/502/503) — the server binds only to
`127.0.0.1`; a startup guard (`installOfflineGuard`) monkey-patches global
`fetch` to reject any non-loopback request, and an `OpLog` records every
dispatch so an operator can assert only local calls happened.
- **Consent before writes** (FR-504) — write tools require `confirm: true`;
the plugin never re-prompts for a single consented batch.
- **Sandbox-bound** — design data only ever crosses loopback HTTP between the
MCP process and the Figma plugin. Nothing is transmitted off-machine.
## Configuration
| Env | Default | Purpose |
|-----|---------|---------|
| `FREEMCP_PORT` | `3700` | Server port. Note: the **plugin's target port is compiled in** (`DEFAULT_PORT` in `packages/contracts/src/messages.ts`), so changing this also requires rebuilding the plugin. |
## Troubleshooting
| Symptom | Fix |
|---------|-----|
| `NO_PLUGIN: No plugin connected` | Open the plugin in Figma (Plugins → Development) so it registers with the server. |
| `Connection refused` | Start the server first, then load the plugin. |
| Port conflict | Set `FREEMCP_PORT` on the server and rebuild the plugin with the matching constant. |
| Text ops fail | Text nodes need a loaded font — the plugin calls `figma.loadFontAsync` automatically before setting text. |
## Development
```bash
npm install # workspaces: contracts, server, plugin
npm test # 32 Vitest unit tests (schemas, codec, bridge, undo, codegen)
npm run typecheck # tsc --noEmit (strict)
npm run build:server # esbuild bundle → packages/server/dist/index.js (npx-runnable)
npm run build:plugin # esbuild bundle → packages/plugin/dist/main.js
npm run start:server # tsx dev run of the MCP server + bridge
```
Coding agents in this repo additionally have **codebase-memory MCP, an LSP
provider, and obscura browser tools** connected for Spec Kit workflow runs —
see **[docs/TOOLCHAIN.md](./docs/TOOLCHAIN.md)** for the wiring and
reproduction. See **[docs/DEVELOPMENT.md](./docs/DEVELOPMENT.md)** for the
developer guide (add a tool, build pipeline, sandbox constraints) and
**[docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)** for the deep technical
reference.
## Repository layout
```
packages/
├── contracts/ # Shared, types-only: DesignContext, bridge frames, Zod tool schemas (source of truth)
├── server/ # MCP server (STDIO) + HTTP long-poll bridge host + codegen + command stack
└── plugin/ # Figma plugin: outbound long-poll client + Plugin API read/write/design-system logic
specs/001-freemcp-figma-bridge/ # Spec Kit artifacts: spec, plan, research, data-model,
# contracts (protocol + tools), quickstart, tasks
```
## Docs index
| Doc | What it covers |
|-----|----------------|
| `docs/ARCHITECTURE.md` | Components, request lifecycle, long-poll internals, undo, codegen pipeline, security |
| `docs/DEVELOPMENT.md` | Adding a tool, build/test workflow, Figma sandbox constraints, conventions |
| `docs/TOOLCHAIN.md` | Connected agent tooling for Spec Kit runs: codebase-memory MCP, LSP provider, obscura, routing skill — setup + verification |
| `skills.md` | Production-ready Figma design workflow for the bridge MCP: audit → tokens → components → screens, Auto Layout rules, verification gates, bridge limits |
| `specs/001-freemcp-figma-bridge/spec.md` | Feature specification (FRs, user stories, success criteria) |
| `specs/001-freemcp-figma-bridge/contracts/protocol.md` | HTTP long-poll bridge protocol contract |
| `specs/001-freemcp-figma-bridge/contracts/tools.md` | MCP tool surface contract |
| `specs/001-freemcp-figma-bridge/quickstart.md` | Manual end-to-end validation scenarios |
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues