web-design-harvester
Allows exporting and replaying Figma Sites and Make files, including unpublished sites, by capturing preview bundles and syncing wire scenegraphs.
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., "@web-design-harvesterharvest the design spec for https://example.figma.site"
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.
Figma Sites / Make Exporter
Chrome MV3 extension + Kiwi wire toolkit that reverse-packs a Figma Sites or Make file — including sites that have never been published.
Three acquisition channels
Channel | Gets you | Needs |
A. Editor wire ( | full scenegraph, | login cookie |
B. Preview bundle ( | the renderer's own scene bundle + unpublished assets | login cookie |
C. Published mirror |
| public URL |
Channel B is the one that reproduces an unpublished site exactly. See
docs/rehearsal/REPORT.md for the measured result.
Related MCP server: figma-context-mcp-server
Why channel B reproduces exactly
Published sites and the editor Preview share one renderer, SitesRuntime. Only
the data source differs:
env: 'published' → fetch(`/_json/${bundleId}${route}.json`)
env: 'preview' → sendMessage('getPage', {url}) over a MessagePort → { website, cmsBundle }website is shape-compatible with a published _index.json. Capture it, write
it where the published path expects it, boot the same runtime — and the page
renders identically. Measured against a live site:
Run | Pixel difference |
published bundle replayed locally (control) | 0.0000 % |
preview bundle replayed locally | 0.053 % — entirely unpublished edits |
Unpublished binary assets never reach a CDN; the editor pushes them to the
preview as pushAssetData messages carrying Blobs, which we intercept too.
Quick start — restore an unpublished site
npm install
# Chrome with the Sites file open and Figma logged in
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 --user-data-dir=/tmp/figma-profile
npm run kiwi:preview -- <FILE_KEY> # every route: bundles + asset Blobs
npm run kiwi:replay # build rehearsal/replay/
node rehearsal/replay/server.mjs # serves with SPA fallback -> :8900
npm run kiwi:sync -- <FILE_KEY> # wire sync, for CODE_FILE source
npm run kiwi:source # editable source packagekiwi preview is deterministic — it does not depend on knowing the site's
layout in advance:
Arms a
MessagePort.prototype.postMessagehook atdocument_start, then reloads so the hook is guaranteed to precede the first message.Captures the first
getPagereply. ItsguidToUrlenumerates every route, including unpublished ones.For each route: restores the editor via
?node-id=<guid>, polls for the preview control to mount (itsdata-testiddisappears in a narrow window, soaria-label="Present"is matched too), clicks, then polls until a bundle arrives whoseroots[0]matches that route's guid.Labels each bundle from its own
roots→guidToUrl. Replies carry no request arguments, so labels are derived from data, never guessed.Drains that round's
pushAssetDataBlobs before the next reload wipes them.Writes every route's outcome — captured or failed with the reason — to
capture-meta.json. Failures are recorded, never silently skipped.
Restrict the run with ROUTES='/,/page-2'; tune with CONTROL_WAIT_MS,
RENDER_WAIT_MS, WIN_W/WIN_H.
Extension
npm run extension:build
# Chrome → chrome://extensions → Developer mode → Load unpacked → ./extensionOpen /site/, /make/ or /design/ and click Export ZIP:
site/— Preview WYSIWYG capture (HTML + inlined computed styles + assets)published/— offline mirror of*.figma.site, when the site is publishedcode/—CODE_FILETSX from the wire (editable source truth)scenegraph.full.json.gz,vectors.json,animations/,assets/
Capture preview → ZIP (all routes) (v0.5.0) runs the same deterministic
walk as kiwi preview inside the extension: the first bundle's guidToUrl
enumerates every route, the bridge visits each via ?node-id=<guid> with its
plan in sessionStorage and data in IndexedDB so both survive the navigation,
and every bundle labels itself from its own payload. The ZIP holds a
preview-bundle/ folder with server.mjs.
The ZIP is not double-clickable. Unzip, then:
cd preview-bundle
node server.mjs # Published URL saved in popup
PUBLISHED_URL=https://<slug>.figma.site node server.mjs # otherwise
# → http://127.0.0.1:8900/page-2Three reasons a server is needed: *.figma.site sends no CORS headers so the
extension cannot fetch the runtime (and it must be the site's own runtime —
runtime and bundle are versioned together), the boot is an ES module that
file:// refuses, and client routes like /page-2 need an SPA fallback.
server.mjs fetches runtime, fonts and missing videos once, then serves
everything locally.
CLI
npm run kiwi -- sync <fileKey> # multiplayer full sync → decoded scenegraph
npm run kiwi -- pack # wire frames → lossless package
npm run kiwi -- preview <fileKey> # every route's bundle + unpublished assets
npm run kiwi -- replay # capture → standalone multi-route replay
npm run kiwi -- source # capture + wire → editable source packageSource for secondary development
The replay is exact but it is data plus runtime, not code you can refactor.
kiwi source packs what is genuinely editable:
Folder | Contents |
|
|
|
|
| Every node carrying |
| Per-route node inventory with text, geometry and type styles. |
Files importing figma:react only run inside Figma's runtime; ones importing
plain react are portable as-is. Interactions need no reimplementation in the
replay — it runs the same SitesRuntime, so hover, click and smart-animate
behave as designed.
Tests
npm run test:kiwi # wire/decoder units, offline fixtures
npm run test:e2e:replay -- <fileKey> [publishedUrl] # capture → replay → pixel gate
npm run test:e2e:preview # older DOM-clone restore path (plateaus ~16% diff)test:e2e:replay needs ffmpeg for the pixel comparison; test:kiwi is hermetic.
Reverse notes
docs/make-reverse-notes.md covers Make vs Sites
formats, the framing/compression breakdown (there is no AES — payloads are
zstd + Kiwi), the preview MessagePort protocol, and the preview Service Worker's
asset-URL map.
Scope
Extension-first. The old DOM-only CLI harvest paths were removed in favour of the wire and preview-bundle channels, which carry data rather than rendered markup.
This server cannot be deployed
Maintenance
Related MCP Connectors
- miromiroOAuthapp.miromiro
Turn any live website into brand colors, fonts, design tokens, SVGs, Lottie and paste-ready code.
UI design from prompts, screenshots, and URLs for AI coding agents and theme tokens.
Serves your design system and coding standards to coding agents, so they stop guessing.
Desktop and mobile website screenshots plus page context for AI agents and automation workflows.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to see, analyze, and visually verify web page changes through pixel-perfect diffing, theme extraction, layout analysis, and interactive element detection.1 npmMIT
- FlicenseAqualityDmaintenanceConverts Figma design data into structured semantic analysis JSON and screenshots for AI agents to use in design-to-code workflows.5-
- AlicenseNot gradedqualityBmaintenanceDeterministic screenshot diffing for AI coding agents. Extract design tokens, diff implementations vs reference, get CSS fix suggestions.29 npm5MIT
- FlicenseAqualityCmaintenanceReverse-engineers design videos and images into structured frontend implementation specifications using vision LLMs and FFMPEG for frame-level analysis.11-