smart-figma-mcp
Reads Figma nodes via the Figma REST API, compiles Figma components to Tailwind/CSS, performs variant mapping, and can fetch raw Figma nodes.
Scans local MUI component library and maps Figma designs to MUI components using component metadata.
Integrates OpenAI as a BYOK LLM provider for semantic and visual compilation paths.
Scans local shadcn/ui components and auto-aligns cva variant definitions, enabling variant-level mapping from Figma designs to shadcn/ui components.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@smart-figma-mcp Map this Figma frame to our shadcn/ui components and write the files."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
smart-figma-mcp
Map Figma designs to your local component library — shadcn/ui, antd, mui, or Astryx — generate code, and write files.
BYOK · Variant-level mapping · Deterministic write · Zero external dependencies
Why smart-figma-mcp?
When you paste a Figma link into Cursor / Claude Code / Codex, AI doesn't know your project components. It generates raw <div> soup every time — wrong styles, no variants, garbage data-node-id attributes. Ask for a <Button variant="ghost-primary"> and you'll get a prop that doesn't exist.
smart-figma-mcp bridges this gap:
Figma Official MCP | smart-figma-mcp | |
Read Figma / suggest mapping | ✅ Native | ✅ |
Write to your codebase | ❌ | ✅ Deterministic write |
Variant mapping (zero config) | ❌ (needs Code Connect) | ✅ shadcn/ui + cva auto-align |
Authoritative component contract | ❌ | ✅ shadcn/ui, antd, mui, Astryx (166 components) |
Token cost attribution | Unclear | ✅ BYOK — key-validated, no server token spend |
data-node-id cleanup | ❌ | ✅ Stripped on save |
We don't compete on "read and suggest". We win on "write and land".
Related MCP server: Component MCP Server
Design-System Contract Engines
The core problem with AI-generated UI isn't layout — it's that the model
guesses at your component API. smart-figma-mcp resolves variants against a
real source of truth instead, and each supported library has its own engine:
Library | Contract source | How variants are resolved |
shadcn/ui |
| Parsed from the variant definition by |
radix-ui | local | Primitives + your own wrappers |
antd / mui | shipped |
|
Astryx (Meta) | CLI JSON contract |
|
Astryx: zero-hallucination mapping
Astryx is Meta's open-source design
system, built to be consumed by AI agents. It publishes its component API as
JSON, so smart-figma-mcp can validate props deterministically instead of
inferring them:
npm install @astryxdesign/core @stylexjs/stylex
npm install -D @astryxdesign/cli{
"name": "Button",
"importPath": "@astryxdesign/core/Button",
"variants": {
"variant": ["primary", "secondary", "ghost", "destructive"],
"size": ["sm", "md", "lg"],
"elevation": ["none", "low", "med", "high"]
},
"requiredProps": ["label"]
}That variants map is the shipped contract, not a scrape — so
variant="ghost-primary" is rejected because it genuinely isn't in the enum,
not because a heuristic guessed wrong.
Two properties worth knowing:
Beta-contained. Astryx is v0.x and may break between minors. Contract
reads are cached per CLI version, and any failure degrades to "no Astryx
components" — it never breaks component scanning for your other libraries.
Lazy by design. The 166-component index is enumerated once; props are
fetched per component on demand rather than all up front.
Quick Start (5 minutes)
npx smart-figma-mcpIDE Setup
smart-figma-mcp uses MCP (Model Context Protocol). Add this to your IDE's MCP config:
Cursor / Kiro / Windsurf — same JSON, different config paths:
IDE | Config File |
Cursor |
|
Kiro |
|
Windsurf |
|
{
"mcpServers": {
"smart-figma": {
"command": "npx",
"args": ["smart-figma-mcp"],
"env": {
"SMART_FIGMA_LICENSE": "<your-license-token>",
"FIGMA_ACCESS_TOKEN": "<your-figma-personal-access-token>",
"LLM_PROVIDER": "anthropic",
"LLM_API_KEY": "sk-..."
}
}
}
}Codex CLI / IDE extension — config is TOML, not JSON. Add a[mcp_servers.smart-figma] table to ~/.codex/config.toml (or.codex/config.toml in a trusted project):
[mcp_servers.smart-figma]
command = "npx"
args = ["smart-figma-mcp"]
startup_timeout_sec = 30
# "writes" prompts before any tool that mutates the filesystem, which is what
# you want for save_component; "auto" trusts the server without prompting.
default_tools_approval_mode = "writes"Codex spawns the command directly rather than through a shell, so command
takes the bare program name and everything else belongs in args — putting a
whole command line in command is the most common setup error. Secrets are
best passed by reference so they never land in the file:
[mcp_servers.smart-figma]
command = "npx"
args = ["smart-figma-mcp"]
env_vars = ["SMART_FIGMA_LICENSE", "FIGMA_ACCESS_TOKEN", "LLM_API_KEY"]Or let the CLI write the block for you:
codex mcp add smart-figma --env SMART_FIGMA_LICENSE=<token> -- npx smart-figma-mcp
codex mcp list # verify it registeredTwo Codex-specific notes:
Startup timeout. A cold
npxdownloads the package on first run, which
can exceed the 10s default and surface as a handshake failure. Thestartup_timeout_sec = 30above avoids it.IDE extension. Servers registered via
config.tomlwork in the CLI; the
VS Code extension has a known issue where it does not always pick them up
(openai/codex#6465). If tools
are missing in the extension but/mcplists them in the TUI, this is why.
Claude Code — use shell env variables instead of inline env:
{
"mcpServers": {
"smart-figma": {
"command": "npx",
"args": ["smart-figma-mcp"]
}
}
}export SMART_FIGMA_LICENSE="<your-license-token>"
export FIGMA_ACCESS_TOKEN="<your-figma-personal-access-token>"Tools
Tool | Description |
| Compile Figma node → Tailwind/CSS + variant mapping + assembly context |
| Fetch raw Figma node via Figma REST API |
| Deterministic write: clean data-node-id, save file, update index.ts |
| Save user-confirmed mapping → private asset library |
| Auto-scan project component library (shadcn/ui / antd / mui / Astryx) |
| Check whether the Astryx contract is available (CLI + core, version) |
| Fetch authoritative Astryx components + props (types, enums, required) |
| Check if mapped component files still exist |
| Export mapping assets as JSON |
| Import mapping assets from JSON (merge, non-destructive) |
| Clear compilation cache |
| Refresh License revocation list from remote |
Modes
Free Tier (Hacker)
Pure digital compilation (PURE_DIGITAL path)
No LLM call, zero token consumption
Auto Layout nodes → Tailwind/CSS output
Good for: well-structured Figma files with Auto Layout
BYOK (Bring Your Own Key)
$9/month or $19 lifetime (early bird)
Plug your own API key (OpenAI / Anthropic / DeepSeek / Zhipu)
Usage is attributed to your key; the server makes no LLM calls (code generation runs on your host LLM — already your subscription)
Semantic + Visual compilation paths enabled
All tools unlocked
Supported LLM Providers
Provider |
| Base URL |
OpenAI |
|
|
Anthropic |
|
|
DeepSeek |
|
|
Zhipu (GLM) |
|
|
Gemini |
|
|
Set LLM_PROVIDER + LLM_API_KEY to enable BYOK. The server reads only these two variables — provider-specific vars like OPENAI_API_KEY are not consumed.
Compilation Strategy
The compiler auto-detects node complexity with an additive score and routes accordingly:
Score | Level | Strategy | Server Credit Cost |
| SIMPLE | PURE_DIGITAL — direct math computation | 0 |
| MODERATE | SEMANTIC — coordinate-flow clustering | ~1 credit |
| COMPLEX | VISUAL — multi-modal LLM | ~5 credits |
In BYOK mode the server makes no LLM calls, so this column is always $0 — generation runs on your own host LLM. The costs above apply only to the Credits model.
Score terms: no Auto Layout +30, absolute-positioned child +15 each,
coordinate-flow child +5 each (capped at 50, +20 more beyond 10),
depth beyond 4 +8 per level.
Non-standard layouts don't fail — they return SUGGEST_AUTOLAYOUT, guiding you to fix at the Figma source.
Architecture
┌─────────────┐ JSON-RPC ┌──────────────────────┐
│ Cursor / │ over stdio │ smart-figma-mcp │
│ Claude Code│◄────────────►│ │
│ Codex │ │ ┌────────────────┐ │
└─────────────┘ │ │ Offline │ │
│ │ Ed25519 License │ │
│ │ (zero network) │ │
│ └────────────────┘ │
│ ┌────────────────┐ │
│ │ Variant Mapping │ │
│ │ + Compiler │ │
│ └────────────────┘ │
│ ┌────────────────┐ │
│ │ Daemon (cache) │ │
│ │ Unix Socket IPC │ │
│ └────────────────┘ │
└──────────────────────┘
│
Figma REST API
(fetch_figma_node)Engineering Notes
What follows is the reasoning behind the parts that are not obvious from the
tool list. If you only want to use it, skip this section.
~4,200 lines, no runtime dependencies
Module | Lines | Responsibility |
| 890 | MCP surface, tool routing, Figma REST orchestration |
| 525 | Tiered compile strategy, recursive subtree compile |
| 480 | Astryx CLI contract adapter, union-type → enum resolution |
| 286 | antd / mui |
| 404 | Variant resolution against the local component library |
| 387 | Long-lived cache process, socket IPC |
| 311 | REST fetch + node normalization |
| 413 | Entitlement, quota accounting, provider routing |
| 309 | Compile cache, screenshot fallback |
| 336 | Key generation, license issuing, quota service |
| 341 | End-to-end smoke coverage |
| 288 | Astryx adapter + mapping contract tests (62 assertions) |
| 101 | antd / mui |
No runtime dependencies field in package.json — MCP over stdio plus fetch
and node:crypto are all that is required. This keeps install fast and avoids
the supply-chain surface a dependency tree would add.
Why a tiered compile strategy
A single strategy wastes money and produces bad output. compiler.js scores
every node and routes it by additive score (the table above). Two details in
that scoring are deliberate:
**Coordinate-flow children are counted separately from absolute-positioned
ones.** A child with x/y but no Auto Layout is a grid the compiler can
recover; a child tagged layoutPositioning === "ABSOLUTE" is an overlay it
cannot. Treating the two the same is what makes naive implementations fail on
real design files.
Depth is a hard limit, not a hint. MAX_DEPTH = 6; compileRecursive()
records a depthWarning and skips rather than recursing deeper. Unbounded
recursion on a pathological Figma tree is a real failure mode, and a skipped
subtree is visible in the output instead of silently truncating.
Degrade to advice, not to a guess. When a node is too complex to compile
reliably, the compiler returns SUGGEST_AUTOLAYOUT and points at the
figma_auto_layoutify plugin — a source-side fix. It does not emit code it
cannot stand behind. The free tier is PURE_DIGITAL only, which means the free
path is genuinely deterministic rather than quietly degraded.
Naming reflects what actually ran. The router reports PURE_DIGITAL /
SEMANTIC / VISUAL, while the recursive compiler emits PURE_DIGITAL /
COORDINATE_FLOW / LEAF. The first is a cost decision; the second is the
executed path. They line up in practice but are not the same vocabulary, and
MappingResult.strategy reflects the executed path.
Variant mapping without a config file
mapping.js resolves a Figma node to a local component by inspecting the
project's own component library (shadcn/ui, antd, mui, Astryx). Variants come
from Figma's own variant properties reconciled against the library's contract,
so adding a component to the project is enough — there is no mapping file to
maintain.
Three contract engines, one descriptor
All three supported paths end at the same shape — { name, importPath, variants } —
so mapToVariant() and everything downstream stays library-agnostic. Only the
way the contract is obtained differs:
Source-derived (shadcn/ui).
parseCVA()reads thecva()call out ofthe component's
.tsx. It is a heuristic over source that happens to bereliable because shadcn has a fixed convention.
Typed-declaration-derived (antd / mui).
mui-antd-contract.jsreads thelibrary's shipped
.d.tsand extracts the literal-union values from each<Component>Propstype — it resolves named type aliases (type ButtonType = ...)and peels mui's
OverridableStringUnion<T>wrapper (which otherwise decays tostringand loses the literals). Nocva, no node_modules source scan needed.Contract-derived (Astryx).
astryx-contract.jsshells out to@astryxdesign/cliand reads the component's props table directly. The enumvalues are the ones Meta ships, so nothing is inferred.
The interesting part is what the CLI actually hands back: a prop's type is a
raw TypeScript union of string literals, e.g.
"'primary' | 'secondary' | 'ghost' | 'destructive'". parseUnionValues()
turns that into the allowed set, and that set becomes the variants dimension.
A union that contains no literals (string | number) yields an empty set, which
mapToVariant() already handles by leaving that dimension unmapped — the
parser fails closed rather than inventing values.
Beta dependencies are contained, not ignored
Astryx is v0.x and documents that breaking changes may land in any minor
release. Three decisions follow:
Degrade, never throw. Every CLI call resolves to a tagged error. A missing
or broken Astryx install reduces the component list to empty; it does not
propagate into component scanning for the other libraries.
Version-stamped cache. Contracts are cached under the CLI version that
produced them, so an upgrade transparently invalidates rather than serving a
stale enum that would produce wrong code.
Distinguish missing states. The
componentcommand requires@astryxdesign/coreto be present even if the CLI is installed — without itthe call returns
ERR_CORE_NOT_FOUND.probe_astryxreportscliInstalled/coreInstalledseparately and returns the exact installcommand, instead of a generic failure the caller has to interpret.
If you use a different node runtime and the CLI cannot be spawned, set
ASTRYX_NODE_BIN to the interpreter to use.
Writing to the filesystem is the point
save_component is the only tool that mutates anything. Three guarantees:
strips
data-node-idbefore writing (Figma scaffolding, meaningless in code)updates the
index.tsbarrel export so the component is actually importableremember_mappingstores a user-confirmed mapping into a private assetlibrary, so a known component resolves without re-derivation
check_mapping_health detects when a previously mapped component file has been
deleted or moved, so the library degrades visibly instead of silently producing
dead references.
Long-lived process, bounded memory
Compilation is CPU- and network-bound with low per-request cost, so a
per-invocation process would pay Node startup on every tool call. A daemon
holds the cache and is reached over a socket; daemon-client.js handles
reconnection so a dead daemon degrades to a cold compile instead of an error.
Entitlement without a network call
License validation is offline Ed25519 signature checking (license.js) against
keys/public.pem. The private key is never committed or published.
refresh_crl pulls a revocation list so a revoked token stops working without
shipping new code.
Disclosure: the license issuing tools (
tools/keygen.js,
tools/issue-license.js,tools/batch-issue.js) andtools/quota-server.jsare in this repository and in the published npm tarball. They are the
operational side of a self-serve product, not secrets — the signing key is not
here. If that trade-off stops being right, the fix is to move entitlement
server-side, not to hide these files.
Verification status
npm test runs test/smoke.mjs on every push (.github/workflows/test.yml).
It is a smoke suite, not a full test pyramid: it exercises tool routing, tier
selection, and the write path end-to-end. Coverage of the LLM assembly path is
inherently bounded — it needs live Figma files and a provider key, so CI does
not exercise it. Treat the free-tier compile path as the tested surface.
The Astryx contract has its own suite (62 assertions across three files), and the typed-dts contract has a fourth suite:
Test | Covers |
| Union-type parsing and the props → variants projection, including malformed and non-literal input |
| The full parse path against a real CLI capture, including that an out-of-contract variant is rejected |
|
|
|
|
test/astryx-contract-fixtures.mjs is a captured Astryx CLI response (166
components) rather than a hand-written mock, so the tests fail if the upstream
contract shape changes. Regenerate it when bumping the Astryx version.
FAQ
Q: Does it work without Figma API access?
A: Yes — you can pass figmaNode JSON directly to compile_figma_component. But fetch_figma_node needs FIGMA_ACCESS_TOKEN.
Q: How do I get a license?
A: Purchase from the payment link → receive your license token → paste in MCP config. License is validated offline (Ed25519, zero network calls).
Q: Can I share my license across devices?
A: Yes, up to 2 device fingerprints. Contact support for more.
Q: Does it work with Kiro + Figma Power?
A: Yes — smart-figma-mcp complements Kiro's built-in Figma Power. Figma Power reads designs; smart-figma-mcp deterministically writes code to your project. Use both side-by-side.
Q: Does BYOK mode still consume credits?
A: No — when using your own API key, credits are not deducted. Only the quotas service (if enabled) records usage counts. Note: the server does not call an LLM under BYOK either — code generation runs on your host LLM (Cursor / Claude Code / Codex), which is already covered by your own subscription.
Q: My Figma file has no Auto Layout — will it work?
A: The compiler returns SUGGEST_AUTOLAYOUT for complex non-layout nodes. We strongly recommend using our Auto Layoutify Figma plugin for best results.
Platform Support
IDE / Tool | Support |
Cursor | ✅ Full |
Kiro | ✅ Full |
Claude Code | ✅ Full |
Codex CLI | ✅ Full |
Codex IDE extension | ⚠️ See note |
Windsurf | ✅ Full |
VS Code (MCP extension) | ✅ |
Continue.dev | ⚠️ Limited |
Cline | ⚠️ Limited |
Codex is supported via config.toml (TOML, not JSON) — see
IDE Setup for the exact block and two gotchas that produce
confusing failures: a cold npx exceeding the 10s startup timeout, and the IDE
extension not picking up servers that the CLI loads fine
(openai/codex#6465). The CLI is
the reliable path today.
Continue.dev and Cline are Limited because their MCP clients implement the
base protocol but have historically been inconsistent about long-running stdio
servers and tool-result streaming. The limitation is in their clients, not in
this server — if it works for you, it is worth reporting upstream.
Pricing
Tier | Price | Quota | BYOK | Visual Compilation |
Hacker | Free | 10/day | ❌ | ❌ |
Maker | $9/mo | 300/mo | ✅ | ✅ |
Pro | $29/mo | 1,500/mo | ✅ | ✅ |
Early Bird Lifetime | $19 once | Maker plan | ✅ | ✅ |
License
Source available, not open source. The code is published so it can be read,
studied, and evaluated — but you may not redistribute it, build a competing
product from it, or use it under an open source license. See
LICENSE for the granted rights and restrictions. Commercial
licensing is available separately via the
Questions about the license: https://github.com/drake-yuan/smart-figma-mcp/issues
Available Tools
12 toolscache_clearA
Clear all compile caches (L1 memory + L2 disk); use to force a refresh after the data source changes.
Inputs: no named parameters.
check_mapping_healthB
Check whether component files referenced by the mapping asset library still exist, and flag stale entries.
Inputs: projectRoot (required).
compile_figma_componentB
Compile a Figma node into a Tailwind + variant-mapping context. Accepts an offline figmaNode JSON or a figmaUrl (live Figma API). In BYOK mode the token bills the user.
Inputs: mode, figmaUrl, siblings, figmaNode, +4 more.
export_mappingsA
Export the .smart-figma/mappings.json mapping assets (for team sharing or migration).
Inputs: projectRoot (required).
fetch_astryx_contractA
Fetch the authoritative Astryx component contract as JSON: component index (categories + import paths) or, when components is given, the exact props table per component (types, required flags, enum values). Use the enum values to avoid inventing props — this is the machine-readable source of truth, so generated code contains no hallucinated variants. Max 100 components per call.
Inputs: projectRoot (required), refresh, components, includeManifest.
fetch_figma_nodeB
Fetch a design node from the Figma API and normalize it into the compiler input format. Returns structured node data (including Auto Layout info).
Inputs: figmaUrl (required), action, figmaToken.
import_mappingsA
Import mapping assets from external JSON and merge them into the local .smart-figma/mappings.json. Existing keys are not overwritten.
Inputs: data (required), projectRoot (required).
probe_astryxA
Check whether the Astryx design-system contract is available in a project (CLI + core installed, version). Use this before fetch_astryx_contract. Degrades gracefully when Astryx is absent.
Inputs: projectRoot (required).
refresh_crlA
Refresh the license revocation list (CRL) from a remote URL and cache it locally to .smart-figma/crl.json.
Inputs: url.
remember_mappingC
Persist a user-confirmed Figma->local-component mapping as a private asset, forming a switching-cost moat.
Inputs: target (required), projectRoot (required), figmaComponentKey (required), figmaName.
save_componentC
Deterministic write op: strip data-node-id, write to the exact path, and auto-update the index.ts barrel export.
Inputs: code (required), projectRoot (required), componentName (required), featureDir.
scan_componentsB
Auto-scan the project's local component library (shadcn/ui / radix / antd / mui) and extract component names, CVA variant definitions, and import paths.
Inputs: projectRoot (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 12 tools
Most tools target distinct resources and actions, and descriptions clarify borderline cases (e.g. probe_astryx before fetch_astryx_contract, remember_mapping vs import_mappings). There is mild overlap around mapping persistence, but nothing severely ambiguous.
Predominantly a consistent verb_noun snake_case pattern (save_component, fetch_figma_node, export_mappings, refresh_crl). Minor deviations come from prefixed domain names (compile_figma_component, fetch_astryx_contract) but the convention is readable and predictable.
12 tools is well within the ideal 3-15 range and each tool maps to a concrete step in the Figma-to-component workflow. No obvious padding.
Covers the core lifecycle: fetch node, compile, save component, scan library, and mapping management (remember/export/import/health). Minor gaps exist, such as no explicit update/delete for components or individual mapping entries, but agents can work around these.
Maintenance
Related MCP Connectors
Design System Manager for shadcn/ui: tokens, fonts, radius, components, icons, blocks, Figma sync.
Build and manage your design system with AI: tokens, themes, components, icons, Figma and code.
Access and maintain design system docs, tokens, components, skills, and contexts across any project.
Live React design-system APIs, patterns, and code validation so AI agents build real UI, not slop.
Related MCP Servers
- AlicenseAqualityDmaintenanceConverts Figma designs into production-ready React components with design token extraction, widget registry integration, and micro-frontend module generation.386 npm1MIT
- AlicenseNot gradedqualityDmaintenanceConnects Figma designs to React components using your actual component library, enabling AI tools to generate production-ready code with proper imports.1MIT
- FlicenseAqualityDmaintenanceSyncs design tokens and components from Figma to a Design System codebase, enabling automated generation of React components and Storybook stories.5-
- AlicenseAqualityDmaintenanceConverts Figma designs into structured code context with token-aware styling, enabling AI agents to generate production-level frontend code.18 npmMIT