web-design-harvester
web-design-harvester
Convierte una página web renderizada en una especificación de diseño que un LLM pueda usar para construir: capturas de pantalla por sección, CSS computado destilado, tokens de diseño, deltas responsivos y activos analizados hasta su canal alfa.
Creado para reproducir páginas de Figma Sites, pero nada en él es específico de Figma: funciona con cualquier URL que se renderice.
npm install
node bin/harvest.mjs https://example.figma.site --out ./spec --clean
# then point a model at ./spec/README.mdPor qué existe esto
Reproducir un diseño solía ser: seleccionar un bloque en Figma → Copiar todo el CSS → pegar más de 2000 líneas en un chat → el modelo extrae el puñado de valores que importan → captura de pantalla del desajuste → repetir, siete u ocho veces.
Cada paso de eso es mecánico, y el material de origen es peor de lo que parece: la exportación CSS de Figma trae marcos rotados con coordenadas negativas ilegibles, capas de marcador de posición con display: none, y variantes de escritorio y móvil intercaladas.
El DOM renderizado no tiene ninguno de esos problemas. getComputedStyle() en una página viva es la verdad resuelta, en cualquier punto de interrupción que quieras, con la lista de activos de la red adjunta. Esta herramienta lee eso y lo escribe.
La API REST de Figma no es una opción
GET /v1/files/{key} devuelve 400 "File type not supported by this endpoint" para archivos con editorType: "sites" o "make". Solo los archivos clásicos de design son legibles. Comprueba /v1/files/{key}/meta antes de planificar cualquier extracción: /meta y /styles funcionan para todos los tipos, los endpoints de nodos no.
Poner la página delante del navegador
Esta es la parte que hace tropezar a la gente, así que vale la pena ser precisos.
Lo que tienes | ¿Funciona? | Cómo |
Sitio publicado | ✅ Sí | Simplemente pasa la URL. Es una página pública normal. |
Iframe de vista previa | ❌ No | Ver abajo. |
Sitio no publicado, abierto en tu Chrome | ✅ Sí |
|
Cualquier otro sitio, localhost, staging | ✅ Sí | Simplemente pasa la URL. |
La URL del iframe de vista previa no funciona de forma independiente
Parece una página y devuelve HTTP 200, pero al obtenerla obtienes un shell de ~3.6KB que contiene solo un listener de postMessage. No tiene contenido propio:
// 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 parentEl código del sitio llega a través de un MessagePort desde una pestaña de figma.com con sesión iniciada. Carga la URL directamente y obtienes un documento en blanco, no importa cuánto esperes. No hay token que proporcionar ni cabecera que establecer: el contenido simplemente no está ahí.
Para un sitio no publicado: conéctate a tu propio navegador
Inicia Chrome con depuración remota, inicia sesión en Figma, abre la vista previa del sitio y luego apunta el harvester a esa pestaña:
# 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 ./specCon --cdp la herramienta se conecta a tu navegador y nunca lanza ni cierra nada. La alternativa más simple, si puedes: publica el sitio y cosecha la URL pública.
Para sitios detrás de un inicio de sesión que prefieras no volver a introducir en cada ejecución, --persist mantiene un perfil en disco entre ejecuciones.
Uso
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 stdioOpción | Por defecto | |||||
|
| directorio de salida | ||||
|
| puntos de interrupción, p. ej. |
| auto | forzar límites de bloque | |
|
| espera extra después de que la página se estabilice | ||||
|
| límite de nodos por bloque | ||||
| omitir descarga y análisis de activos | |||||
| limpiar el directorio de salida primero | |||||
| mostrar la ventana del navegador | |||||
| reutilizar un perfil, manteniendo inicios de sesión entre ejecuciones | |||||
| conectarse a un Chrome en ejecución (puerto o URL ws://) | |||||
| salida estándar legible por máquina |
Empieza con outline o blocks en una página desconocida. Son rápidos y te dicen si la segmentación automática encontró secciones sensatas antes de comprometerte a una ejecución completa.
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…
…Si los límites son incorrectos, pasa --selector "main > section".
Salida
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 hashUn block.md se ve así:
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/newmás geometría por punto de interrupción, el texto, los activos utilizados y una tabla de deltas responsivos. El JSON junto a él tiene los valores completos si algo parece raro.
Lo que hace que una captura de pantalla no hace
Las capturas de pantalla son 1 píxel CSS = 1 píxel de imagen. deviceScaleFactor: 1 más scale: 'css' significa que una distancia medida en el PNG es un píxel CSS. Sin factor de conversión, así que no hay errores de conversión. (Las exportaciones @2x de Figma ponen 1798 píxeles de imagen contra un diseño de 1440px — cada medición necesitaba dividirse por 1.2486 primero, y equivocarse producía números plausibles pero incorrectos.)
Los estilos computados se destilan, no se vuelcan. Tres filtros se ejecutan sobre cada elemento: se eliminan los valores por defecto de UA para esa etiqueta, se eliminan los valores heredados que el padre ya declara, y las declaraciones que aparecen en casi todos los nodos (box-sizing: border-box y similares) se extraen y se declaran una vez. Lo que sobrevive es lo que difiere, que es lo que realmente tienes que escribir. En la práctica, esto es aproximadamente un orden de magnitud más pequeño que un volcado crudo.
Los activos se miden, no se asumen. Para cada imagen y video, la herramienta decodifica un fotograma y encuentra el cuadro delimitador de contenido a partir del canal alfa. Los activos de diseño suelen venir como un cuadrado de 1200×1200 con la obra ocupando una región descentrada de 1049×677 — desde las dimensiones del archivo solo eso es invisible, y tanto object-contain (espacio muerto) como object-cover + centro (recorta fuera de eje) lo hacen mal. La salida indica el object-position a usar.
VP9 WebM con alfa se maneja especialmente: ffprobe informa pix_fmt=yuv420p y no muestra canal alfa, pero los navegadores lo componen correctamente. El alfa solo aparece si fuerzas el decodificador libvpx-vp9.
Los diseños imposibles se señalan en la primera ejecución. Cuando la relación de contenido de un activo y la caja en la que se encuentra discrepan mucho, ningún valor de object-fit lo arregla: el activo necesita re-exportarse. El README lo señala de inmediato, con el porcentaje de obra que cover descartaría, en lugar de dejarte pasar seis rondas ajustando CSS para arreglar un problema de exportación.
Ambos puntos de interrupción, porque la mitad de la especificación está en la diferencia. Radio de tarjeta 12px → 6px, título 20/30 → 16/24, padding de cabecera 32px → 32px (sin cambios). Nada de eso es derivable escalando; responsive.md lista cada valor que se mueve.
Cualquier número de puntos de interrupción. --widths 1440,768,375 captura tres, y todo escala con ello: una captura de pantalla y un árbol de estilos por punto de interrupción, recuentos de uso desglosados por punto de interrupción en tokens.md, y tablas de deltas para cada par adyacente — desktop → tablet, luego tablet → mobile. Adyacente en lugar de todo contra escritorio, porque refleja cómo se escriben las media queries: cada paso solo reafirma lo que cambió desde el anterior. responsive.md abre con una matriz de qué bloques cambian en qué paso.
Nada se descarta en silencio. Después de la segmentación, la herramienta comprueba que los bloques cubren la página, busca un elemento que cubra cualquier hueco e informa la proporción de cobertura. Las regiones que genuinamente no puede reclamar se listan en lugar de ignorarse. Las dimensiones de la captura de pantalla se verifican contra lo solicitado, porque una captura truncada es peor que una fallida: parece bien y cada medición sobre ella está silenciosamente mal.
Servirlo a un modelo
Una ejecución en frío pasa la mayor parte del tiempo en el arranque del navegador y la primera pintura. Si un modelo está iterando — revisa ese bloque, ahora a 768px, ¿de qué color es ese icono? — pagar eso por pregunta hace que la herramienta sea inutilizable. Ambos modos de servidor mantienen un navegador y sus páginas cargadas calientes.
Medido en https://figma.site: 26.8s en frío → 0.03s en caliente.
MCP (stdio)
{
"mcpServers": {
"web-design-harvester": {
"command": "node",
"args": ["/absolute/path/to/web-design-harvester/bin/harvest.mjs", "mcp"]
}
}
}Herramientas: harvest_outline, harvest_blocks, harvest_block, harvest_tokens, harvest_assets, harvest_screenshot, harvest_analyse_asset, harvest_site, harvest_status.
El bucle típico es harvest_blocks para encontrar la sección, luego harvest_block para su estructura: la segunda llamada llega en decenas de milisegundos porque la página ya está abierta.
Daemon 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"Se vincula solo a loopback: obtiene URLs arbitrarias y escribe archivos donde se le indique, así que no debería ser accesible desde fuera. Pasa --host si realmente lo quieres. Las páginas inactivas se cierran después de 10 minutos.
Requisitos
Node 18+
Playwright Chromium —
npm installlo descarga mediante el hook de postinstallffmpeg / ffprobe (opcional) — necesario para cajas de contenido, paletas y análisis de video. Sin él, todo lo demás sigue funcionando; la inteligencia de activos se omite con una advertencia.
brew install ffmpeg
npm test # 46 tests, ~10s, hermetic (local fixture, no network)Limitaciones conocidas
Los iframes de origen cruzado son agujeros tanto en el DOM como en la captura de pantalla. La herramienta los detecta y lista su tamaño, origen y
srcenblock.md, pero no puede ver dentro. Lo que el frame renderice tiene que manejarse por separado.Los estilos de hover y focus no se capturan: necesitan interacción en vivo.
interactions.mdte da la propiedad de transición y duración de cada elemento, lo que te dice qué anima y qué tan rápido, pero no el estado final.Video en streaming (DASH/fMP4) llega como segmentos que no son archivos independientes válidos. Se etiquetan en lugar de informarse como corruptos.
El contenido de Canvas y WebGL se captura como píxeles en la captura de pantalla; no hay estructura que extraer.
La animación impulsada por scroll se muestrea en un punto. La página se desplaza de principio a fin primero para activar revelaciones, luego se devuelve a la parte superior; una sección cuya apariencia depende de la posición de scroll puede no estar en su estado final.
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