figma-mcp-py
A small Figma MCP server in Python, built with FastMCP. It gives Claude a design it can build from: the layout as flexbox, the colour and text styles as CSS variables, the icons as SVG, and a picture of the frame to compare against.
It is five read-only tools in about 280 lines of Python, so you can read all of it, change what Claude sees, and run it on Figma's free plan.

Tallyhive is a made-up product. The mock above is drawn by the plugin in mock/, so you can draw the same file in your own Figma account and follow along.
Why write your own instead of using an existing one
- You decide what Claude reads. Figma's file JSON is big: the one-page mock above is about 100 KB, roughly 20k tokens, and Claude Code warns when a tool returns more than 10k tokens (it cuts off at 25k unless you raise
MAX_MCP_OUTPUT_TOKENS).get_layoutturns the same frame into about 17 KB of what a developer needs: auto layout asflex(direction, gap, padding, justify, align), text with its font, colours as hex, sizes in px, and style names. Everything else is dropped. - It works on the free plan. Figma's REST rate limits put file reads and image exports in Tier 1, and their docs list View and Collab seats at up to 20 of those calls a month. This server makes at most three calls per file (the file, a PNG of the frame, the icons as SVG) and saves each response under
.cache/. Every tool reads from the cache after that. - It's read-only. No tool writes to Figma, so there's nothing to approve call by call.
The tools
| Tool | What it returns |
|---|---|
list_frames(file_url) |
The top-level frames in the file: page, id, name, size. Start here. |
get_layout(file_url, frame_id) |
One frame as a compact layout tree. Icons show up as one icon node each (name, id, size), not their vector paths. |
get_tokens(file_url) |
Colour and text styles as CSS custom properties, for example --color-brand: #3B5CF6 and --font-display: 700 56px/64px Inter. |
export_icons(file_url, frame_id) |
Every icon in the frame (any node named Icon / ...) as inline SVG, keyed by name. |
render_frame(file_url, frame_id) |
A PNG of the frame, returned as an MCP image so Claude can see the design. |
file_url is the link you copy from Figma's address bar (figma.com/design/<file key>/...); the bare file key works too.
The picture and the tokens do different jobs. From the PNG alone, Claude gets the look right but has to guess exact values (it read our brand blue as roughly #3D5AFE); get_tokens gives it the real #3B5CF6.
How the code is laid out
figma_mcp/
figma.py the two Figma REST endpoints (/v1/files, /v1/images), every response cached on disk
simplify.py Figma JSON → the compact layout, tokens and icon list (pure functions, no network)
server.py the five @mcp.tool() functions; their docstrings are what Claude reads to pick a tool
mock/ a Figma development plugin that draws the Tallyhive mock
tests/ pytest, offline, against a saved copy of the mock
Setup
You need Python 3.12+, Claude Code, and Node.js (only for the Playwright MCP server, see below).
git clone https://github.com/stacksprint-io/figma-mcp-py.git
cd figma-mcp-py
python3 -m venv .venv
.venv/bin/pip install -e '.[dev]'
.venv/bin/pytest # 21 tests, all offline
1. A design to read
Use any Figma file you can open, or draw the Tallyhive mock:
- Create a new design file in the Figma desktop app.
- Plugins → Development → Import plugin from manifest, pick
mock/manifest.json, then run Tallyhive mock. - Copy the file's link from the address bar.
The server works best on designs built with auto layout and named styles, because those are what turn into flex and CSS variables.
2. A Figma token
In Figma: Settings → Security → Personal access tokens → Generate new token, with read-only access to file content. Then:
cp .env.example .env # and paste the token after FIGMA_TOKEN=
.env and .cache/ are both in .gitignore. The token is only needed the first time a file is read; once a file is cached, the server runs without it. To fetch a file again after you change the design, delete its files from .cache/.
3. Register it with Claude Code
This repo ships a project .mcp.json with two servers: this one (figma) and Microsoft's official Playwright MCP (playwright), which lets Claude open the page it built, take a screenshot and compare it with the design. Start Claude Code in the repo folder, approve the two project servers when it asks, and run /mcp to check both are connected.
To add only the Figma server, or to use it from another project:
claude mcp add figma -- /full/path/to/figma-mcp-py/.venv/bin/python -m figma_mcp.server
The Playwright server runs headless at 1440×900. Remove --headless from .mcp.json if you want to watch the browser.
Build a page from the mock
Serve an empty site/ folder (cd site && python3 -m http.server 8765), then give Claude Code one prompt:
Build the landing page in this Figma file as site/index.html:
<your Figma link>. Use the figma tools to read the frames, the layout, the tokens, the icons and the picture. Plain HTML with Bootstrap 5 from the CDN and a small style block using the tokens as CSS variables; no build step. A server is already serving the site folder at http://127.0.0.1:8765/ (don't start one). When it's written, open it with the playwright tools at 1440 wide, take a screenshot, compare it with the Figma picture, fix what differs, and list what you fixed.
On the Tallyhive mock this produces one HTML file whose sections match the frame's heights to the pixel at 1440 wide (102, 518, 338, 640 and 92 px, 1690 in all).

In the run recorded for the video, the compare step found and fixed two things: a negative letter-spacing Claude had added itself, which made the headline about 27 px narrower than in Figma, and a favicon.ico 404 in the console. Runs differ; an earlier run found the featured pricing card's 2 px border instead.
Limits
- It reads one frame at a time and only solid fills. Gradients, images, effects (shadows, blur), components and variants aren't simplified yet; add them in
simplify.nodeif your designs need them. - Fonts are reported by family, size, weight and line height. The page still has to load the font itself (the Tallyhive mock uses Inter).
- Icons are found by name: a node named
Icon / <something>is exported as one SVG. - Text nodes keep their characters and font but not their box size, so Claude picks text widths itself (it capped the Tallyhive headline at 800 px). If your design needs exact text widths, keep
sizefor text nodes insimplify.node. - Claude only sees the frames you draw. With a desktop frame alone, the narrow-screen layout is its own guess; draw a mobile frame if you want that right.
License
MIT