FreeMCP for Figma
Enables AI coding agents to read and write a user's Figma file through the Figma Plugin API, offline and privately, without API tokens or rate limits.
README
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_runapplies 100+ ops in one consented request without blocking the Figma UI;undoreverts the last write(s).
Transport: the bridge is HTTP long-poll (
POST /register,GET /next,POST /result) on127.0.0.1:3700, not WebSocket. The Figma plugin sandbox has noWebSocketglobal — onlyfetch— 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.
Quickstart (< 5 min)
1. Install
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)
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)
{
"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.
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 globalfetchto reject any non-loopback request, and anOpLogrecords 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
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 for the wiring and reproduction. See docs/DEVELOPMENT.md for the developer guide (add a tool, build pipeline, sandbox constraints) and 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 |
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.