web-design-harvester
web-design-harvester
Превращает отрендеренную веб-страницу в дизайн-спецификацию, по которой LLM может собрать сайт: посекционные скриншоты, дистиллированные вычисленные CSS-стили, дизайн-токены, адаптивные дельты и ассеты, проанализированные вплоть до альфа-канала.
Создан для воспроизведения страниц Figma Sites, но ничего в нём не специфично для Figma — он работает с любым URL, который рендерится.
npm install
node bin/harvest.mjs https://example.figma.site --out ./spec --clean
# then point a model at ./spec/README.mdЗачем это существует
Раньше воспроизведение дизайна выглядело так: выделить блок в Figma → Copy all CSS → вставить 2000+ строк в чат → модель выуживает несколько важных значений → скриншот несоответствия → повторять семь-восемь раз.
Каждый шаг этого процесса механический, а исходный материал хуже, чем кажется: CSS-экспорт Figma содержит повёрнутые фреймы с нечитаемыми отрицательными координатами, слои-заглушки с display: none и перемешанные десктопные и мобильные варианты.
У отрендеренного DOM нет ни одной из этих проблем. getComputedStyle() на живой странице — это разрешённая истина, на любом брейкпоинте, с приложенным списком ассетов из сети. Этот инструмент читает это и записывает.
REST API Figma — не вариант
GET /v1/files/{key} возвращает 400 "File type not supported by this endpoint" для файлов с editorType: "sites" или "make". Читаются только классические файлы design. Проверьте /v1/files/{key}/meta перед планированием любой выгрузки — /meta и /styles работают для всех типов, а node-эндпоинты — нет.
Как показать страницу браузеру
Это та часть, на которой все спотыкаются, поэтому стоит быть точным.
Что у вас есть | Работает? | Как |
Опубликованный сайт | ✅ Да | Просто передайте URL. Это обычная публичная страница. |
Превью-iframe | ❌ Нет | См. ниже. |
Неопубликованный сайт, открытый в вашем Chrome | ✅ Да |
|
Любой другой сайт, localhost, staging | ✅ Да | Просто передайте URL. |
URL превью-iframe не работает сам по себе
Он выглядит как страница и возвращает HTTP 200, но при загрузке вы получаете оболочку размером ~3,6 КБ, содержащую только слушатель postMessage. Своего контента в ней нет:
// what that URL actually serves, in full:
window.addEventListener('message', (e) => {
if (isAllowedOrigin(e.origin)) { // only figma.com and friends
if (e.data.type === 'iframe-init') {
script.src = e.data.initScriptURL // ← the real app comes from the parentКод сайта приходит через MessagePort из вкладки figma.com, где выполнен вход. Загрузите URL напрямую — и получите пустой документ, сколько бы ни ждали. Никакого токена для передачи и заголовка для установки нет — контента просто нет.
Для неопубликованного сайта: подключитесь к своему браузеру
Запустите Chrome с удалённой отладкой, войдите в Figma, откройте превью сайта, затем укажите харвестеру на эту вкладку:
# 1. Chrome with a debugging port (use a separate profile to avoid clobbering yours)
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 --user-data-dir=/tmp/figma-profile
# 2. Log into figma.com in that window, open your Sites file, hit Preview.
# 3. Harvest the rendered iframe
node bin/harvest.mjs "https://<uuid>-v2-figmaiframepreview.figma.site" \
--cdp 9222 --out ./specС --cdp инструмент подключается к вашему браузеру и никогда ничего не запускает и не закрывает. Более простой вариант, если он возможен: опубликуйте сайт и соберите данные с публичного URL.
Для сайтов за логином, который не хочется вводить каждый раз, --persist сохраняет профиль на диске между запусками.
Использование
harvest <url> [options] full harvest -> spec directory
harvest outline <url> print the DOM outline (recon)
harvest blocks <url> list blocks that would be captured
harvest asset <file...> analyse local media files
harvest serve [--port 8787] HTTP daemon, browser stays warm
harvest mcp MCP server on stdioОпция | По умолчанию | |||||
|
| выходная директория | ||||
|
| брейкпоинты, напр. |
| auto | принудительные границы блоков | |
|
| дополнительное ожидание после стабилизации страницы | ||||
|
| лимит узлов на блок | ||||
| пропустить загрузку и анализ ассетов | |||||
| сначала очистить выходную директорию | |||||
| показать окно браузера | |||||
| переиспользовать профиль, сохраняя логины между запусками | |||||
| подключиться к запущенному Chrome (порт или ws:// URL) | |||||
| машиночитаемый stdout |
Начните с outline или blocks на незнакомой странице. Они быстрые и показывают, нашла ли автоматическая сегментация разумные секции, прежде чем вы запустите полный прогон.
node bin/harvest.mjs blocks https://figma.site --widths 1440strategy: semantic-landmarks
coverage: 100% (11248 of 11248px)
01 header.fig-suku18 1440×78 @0 [sticky] What you can do in figma
02 section.fig-lqoz33 1440×1109 @78 Figma Sites
03 section.fig-15ba1hq 1440×1117 @1187 Perfect websites every time…
…Если границы неверны, передайте --selector "main > section".
Вывод
spec/
README.md ← start here; index, warnings, token summary
index.json machine-readable manifest
tokens.md design tokens ranked by usage
tokens.css the same tokens as CSS custom properties
responsive.md every value that changes between breakpoints
interactions.md clickable/focusable elements and their transitions
warnings.json asset fit problems, in full
page-desktop.png full-page screenshot per breakpoint
page-mobile.png
blocks/
02-figma-sites/
block.md ← spec sheet for one section
desktop.png screenshot, exactly the block's size
mobile.png
tree.desktop.json exact computed values, full precision
tree.mobile.json
assets/
README.md every asset with content box and fit guidance
manifest.json
<files> deduplicated by content hashblock.md выглядит так:
section.fig-lqoz33 1440×1108.6 pad:0/0/32/0 relative bg:#ffffff
└─ div.fig-umtrpl 1440×1076.6 flex-col gap:64 pad:64/0/0/0
├─ h1.fig-6late5 660×72 mar:0/0/32/0 72/72 ls:-1.44 "Figma Sites"
└─ a.fig-1jz30fp 135×46.4 flex-row jc:center pad:12/22
#ffffff bg:#000000 r:8 href:/site/newплюс геометрия для каждого брейкпоинта, текст, использованные ассеты и таблица адаптивных дельт. JSON рядом содержит полные значения, если что-то выглядит подозрительно.
Что он делает такого, чего не делает скриншот
Скриншоты: 1 CSS-пиксель = 1 пиксель изображения. deviceScaleFactor: 1 плюс scale: 'css' означает, что расстояние, измеренное по PNG, — это CSS-пиксель. Никакого коэффициента пересчёта, а значит, и ошибок пересчёта. (Экспорт Figma @2x ставит 1798 пикселей изображения против дизайна 1440px — каждое измерение нужно было сначала делить на 1.2486, а ошибка давала правдоподобные, но неверные числа.)
Вычисленные стили дистиллируются, а не вываливаются. По каждому элементу прогоняются три фильтра: отбрасываются UA-значения по умолчанию для этого тега, отбрасываются наследуемые значения, которые родитель уже указал, а объявления, встречающиеся почти на каждом узле (box-sizing: border-box и подобные), выносятся и указываются один раз. Выживает то, что отличается, — а именно это и нужно писать. На практике это примерно на порядок меньше, чем сырой дамп.
Ассеты измеряются, а не предполагаются. Для каждого изображения и видео инструмент декодирует кадр и находит границы содержимого по альфа-каналу. Дизайн-ассеты часто поставляются как квадрат 1200×1200, где арт занимает смещённую область 1049×677 — по одним размерам файла это не видно, и оба варианта object-contain (мёртвая зона) и object-cover + центр (смещённые кадрирования) ошибаются. В выводе указывается object-position, который нужно использовать.
VP9 WebM с альфа-каналом обрабатывается особым образом: ffprobe сообщает pix_fmt=yuv420p и не показывает альфа-канал, но браузеры композитируют его корректно. Альфа появляется только при принудительном использовании декодера libvpx-vp9.
Невозможные макеты выявляются на первом же прогоне. Когда соотношение сторон содержимого ассета и контейнера, в котором он лежит, сильно расходятся, ни одно значение object-fit не исправит ситуацию — ассет нужно переэкспортировать. README сразу это помечает, с процентом арта, который cover отбросит, вместо того чтобы позволить вам потратить шесть раундов на подгонку CSS для исправления проблемы экспорта.
Оба брейкпоинта, потому что половина спецификации — в разнице. Радиус карточки 12px → 6px, заголовок 20/30 → 16/24, отступ шапки 32px → 32px (без изменений). Ничего из этого не выводится масштабированием; responsive.md перечисляет каждое изменяющееся значение.
Любое количество брейкпоинтов. --widths 1440,768,375 захватывает три, и всё масштабируется соответственно: скриншот и дерево стилей для каждого брейкпоинта, счётчики использований по брейкпоинтам в tokens.md, и таблицы дельт для каждой соседней пары — desktop → tablet, затем tablet → mobile. Соседние, а не всё-против-десктопа, потому что это отражает то, как пишутся медиазапросы: каждый шаг лишь повторяет, что изменилось с предыдущего. responsive.md открывается матрицей того, какие блоки меняются на каком шаге.
Ничего не отбрасывается молча. После сегментации инструмент проверяет, что блоки замощают страницу, ищет элемент, покрывающий любой зазор, и сообщает коэффициент покрытия. Области, которые он действительно не может охватить, перечисляются, а не игнорируются. Размеры скриншотов сверяются с запрошенными, потому что обрезанный захват хуже неудачного — он выглядит нормально, а каждое измерение по нему тихо неверно.
Подача модели
Холодный запуск тратит большую часть времени на запуск браузера и первую отрисовку. Если модель итерирует — перепроверь этот блок, теперь на 768px, какого цвета эта иконка — платить за это каждый вопрос делает инструмент непригодным. Оба серверных режима держат браузер и загруженные страницы тёплыми.
Замерено на https://figma.site: 26.8s холодный → 0.03s тёплый.
MCP (stdio)
{
"mcpServers": {
"web-design-harvester": {
"command": "node",
"args": ["/absolute/path/to/web-design-harvester/bin/harvest.mjs", "mcp"]
}
}
}Инструменты: harvest_outline, harvest_blocks, harvest_block, harvest_tokens, harvest_assets, harvest_screenshot, harvest_analyse_asset, harvest_site, harvest_status.
Типичный цикл — harvest_blocks для поиска секции, затем harvest_block для её структуры — второй вызов занимает десятки миллисекунд, потому что страница уже открыта.
HTTP-демон
node bin/harvest.mjs serve --port 8787
curl "http://127.0.0.1:8787/blocks?url=https://figma.site&width=1440"
curl "http://127.0.0.1:8787/block?url=https://figma.site&index=4"Привязывается только к loopback — он загружает произвольные URL и пишет файлы туда, куда указано, поэтому не должен быть доступен с других машин. Передайте --host, если вам это действительно нужно. Простаивающие страницы закрываются через 10 минут.
Требования
Node 18+
Playwright Chromium —
npm installзагружает его через postinstall-хукffmpeg / ffprobe (необязательно) — нужны для контент-боксов, палитр и анализа видео. Без них всё остальное работает; интеллект ассетов пропускается с предупреждением.
brew install ffmpeg
npm test # 46 tests, ~10s, hermetic (local fixture, no network)Известные ограничения
Кросс-доменные iframe — дыры и в DOM, и в скриншоте. Инструмент обнаруживает их и перечисляет их размер, origin и
srcвblock.md, но не может заглянуть внутрь. Всё, что рендерит фрейм, нужно обрабатывать отдельно.Стили наведения и фокуса не захватываются — им нужно живое взаимодействие.
interactions.mdдаёт вам свойство перехода и длительность для каждого элемента, что показывает, что анимируется и как быстро, но не конечное состояние.Стриминговое видео (DASH/fMP4) приходит сегментами, которые не являются валидными отдельными файлами. Они помечаются, а не сообщаются как повреждённые.
Canvas и WebGL контент захватывается как пиксели на скриншоте; структуры для извлечения нет.
Анимация, управляемая прокруткой, сэмплируется в одной точке. Страница сначала прокручивается от начала до конца, чтобы вызвать появления, затем возвращается вверх; секция, появление которой зависит от позиции прокрутки, может быть не в конечном состоянии.
This server cannot be installed
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
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.
Score any URL against a real design contract — 40 checks, A-F grade, token + motion validation.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ChenYCL/web-design-harvester'
If you have feedback or need assistance with the MCP directory API, please join our Discord server