Skip to main content
Glama
ChenYCL

web-design-harvester

by ChenYCL

web-design-harvester

Verwandle eine gerenderte Webseite in ein Design-Spezifikationsdokument, aus dem ein LLM bauen kann: Abschnitts-Screenshots, destilliertes berechnetes CSS, Design-Tokens, responsive Deltas und Assets, die bis auf ihren Alphakanal analysiert wurden.

Gebaut für die Reproduktion von Figma-Sites-Seiten, aber nichts daran ist Figma-spezifisch – es funktioniert mit jeder URL, die rendert.

npm install
node bin/harvest.mjs https://example.figma.site --out ./spec --clean
# then point a model at ./spec/README.md

Warum es das gibt

Eine Design-Reproduktion lief früher so ab: Block in Figma auswählen → Copy all CSS → 2000+ Zeilen in einen Chat einfügen → das Modell gräbt die paar relevanten Werte heraus → Screenshot der Abweichung → wiederholen, sieben oder acht Mal.

Jeder Schritt davon ist mechanisch, und das Ausgangsmaterial ist schlechter, als es aussieht: Figmas CSS-Export enthält gedrehte Frames mit unlesbaren negativen Koordinaten, display: none-Platzhalter-Layer und Desktop- und Mobile-Varianten, die ineinander verschachtelt sind.

Das gerenderte DOM hat keines dieser Probleme. getComputedStyle() auf einer Live-Seite ist die aufgelöste Wahrheit, an jedem gewünschten Breakpoint, mit der Asset-Liste des Netzwerks. Dieses Tool liest das und schreibt es auf.

Figmas REST-API ist keine Option

GET /v1/files/{key} gibt 400 "File type not supported by this endpoint" für Dateien mit editorType: "sites" oder "make" zurück. Nur klassische design-Dateien sind lesbar. Prüfe /v1/files/{key}/meta, bevor du eine Extraktion planst – /meta und /styles funktionieren für alle Typen, die Node-Endpunkte nicht.


Die Seite vor den Browser bringen

Das ist der Teil, an dem Leute scheitern, daher lohnt es sich, präzise zu sein.

Was du hast

Funktioniert es?

Wie

Veröffentlichte Site https://<name>.figma.site

✅ Ja

Einfach die URL übergeben. Es ist eine normale öffentliche Seite.

Vorschau-iframe https://<uuid>-v2-figmaiframepreview.figma.site

Nein

Siehe unten.

Unveröffentlichte Site, geöffnet in deinem Chrome

✅ Ja

--cdp – siehe unten.

Jede andere Site, localhost, Staging

✅ Ja

Einfach die URL übergeben.

Die Vorschau-iframe-URL funktioniert nicht eigenständig

Sie sieht aus wie eine Seite und liefert HTTP 200, aber beim Abrufen erhältst du eine ~3,6 KB große Hülle, die nur einen postMessage-Listener enthält. Sie hat keinen eigenen Inhalt:

// 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

Der Code der Site kommt über einen MessagePort von einem angemeldeten figma.com-Tab. Wenn du die URL direkt lädst, erhältst du ein leeres Dokument, egal wie lange du wartest. Es gibt kein Token, das du bereitstellen könntest, und keinen Header, den du setzen könntest – der Inhalt ist einfach nicht da.

Für eine unveröffentlichte Site: an deinen eigenen Browser anhängen

Starte Chrome mit Remote-Debugging, melde dich bei Figma an, öffne die Vorschau der Site und richte den Harvester auf diesen Tab aus:

# 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

Mit --cdp hängt sich das Tool an deinen Browser an und startet oder schließt nichts. Die einfachere Alternative, falls möglich: Veröffentliche die Site und ernte die öffentliche URL.

Für Sites hinter einem Login, den du nicht bei jedem Lauf neu eingeben möchtest, behält --persist ein Profil zwischen den Läufen auf der Festplatte.


Verwendung

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

Option

Standard

--out <dir>

./out

Ausgabeverzeichnis

--widths <list>

1440,375

Breakpoints, z. B. 1440,768,375

--selector <css>

auto

Blockgrenzen erzwingen

--settle <ms>

800

zusätzliche Wartezeit nach der Stabilisierung

--max-nodes <n>

400

Knotenlimit pro Block

--skip-assets

Herunterladen und Analysieren von Assets überspringen

--clean

Ausgabeverzeichnis vorher leeren

--headed

Browserfenster anzeigen

--persist [dir]

Profil wiederverwenden, Logins zwischen Läufen behalten

--cdp <endpoint>

an einen laufenden Chrome anhängen (Port oder ws://-URL)

--json

maschinenlesbare Standardausgabe

Beginne mit outline oder blocks auf einer unbekannten Seite. Sie sind schnell und zeigen dir, ob die automatische Segmentierung sinnvolle Abschnitte gefunden hat, bevor du dich auf einen vollständigen Lauf festlegst.

node bin/harvest.mjs blocks https://figma.site --widths 1440
strategy: 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…
  …

Wenn die Grenzen falsch sind, übergib --selector "main > section".


Ausgabe

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 hash

Eine block.md sieht so aus:

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

plus Geometrie pro Breakpoint, den Text, die verwendeten Assets und eine Tabelle mit responsiven Deltas. Das JSON daneben enthält die ungekürzten Werte, falls etwas seltsam aussieht.


Was es tut, was ein Screenshot nicht tut

Screenshots sind 1 CSS-Pixel = 1 Bild-Pixel. deviceScaleFactor: 1 plus scale: 'css' bedeutet, dass eine auf dem PNG gemessene Entfernung ein CSS-Pixel ist. Kein Umrechnungsfaktor, also keine Umrechnungsfehler. (Figmas @2x-Exporte setzen 1798 Bildpixel gegen ein 1440px-Design – jede Messung musste zuerst durch 1,2486 geteilt werden, und ein Fehler dabei erzeugte plausible, falsche Zahlen.)

Berechnete Stile werden destilliert, nicht abgeladen. Drei Filter laufen über jedes Element: UA-Standardwerte für dieses Tag werden verworfen, geerbte Werte, die das Elternelement bereits angibt, werden verworfen, und Deklarationen, die auf fast jedem Knoten erscheinen (box-sizing: border-box und ähnliche), werden herausgehoben und einmal angegeben. Was übrig bleibt, ist das, was sich unterscheidet – und genau das musst du schreiben. In der Praxis ist das etwa eine Größenordnung kleiner als ein roher Dump.

Assets werden gemessen, nicht angenommen. Für jedes Bild und jedes Video dekodiert das Tool einen Frame und findet die Inhalts-Bounding-Box aus dem Alphakanal. Design-Assets werden routinemäßig als 1200×1200-Quadrat geliefert, wobei das Kunstwerk eine außermittige 1049×677-Region einnimmt – allein aus den Dateiabmessungen ist das unsichtbar, und sowohl object-contain (toter Raum) als auch object-cover + Zentrum (beschneidet außerhalb der Achse) liegen falsch. Die Ausgabe gibt die zu verwendende object-position an.

VP9 WebM mit Alpha wird speziell behandelt: ffprobe meldet pix_fmt=yuv420p und zeigt keinen Alphakanal, aber Browser komponieren es korrekt. Das Alpha erscheint nur, wenn du den libvpx-vp9-Decoder erzwingst.

Unmögliche Layouts werden beim ersten Lauf gemeldet. Wenn das Inhaltsverhältnis eines Assets und die Box, in der es sitzt, stark voneinander abweichen, kann kein object-fit-Wert das beheben – das Asset muss neu exportiert werden. Das README weist sofort darauf hin, mit dem Prozentsatz an Kunstwerk, den cover verwerfen würde, statt dich sechs Runden lang CSS-Tweaks machen zu lassen, um ein Exportproblem zu beheben.

Beide Breakpoints, weil die halbe Spezifikation im Unterschied liegt. Kartenradius 12px → 6px, Titel 20/30 → 16/24, Header-Padding 32px → 32px (unverändert). Nichts davon ist durch Skalierung ableitbar; responsive.md listet jeden Wert auf, der sich bewegt.

Beliebig viele Breakpoints. --widths 1440,768,375 erfasst drei, und alles skaliert damit: ein Screenshot und ein Stilbaum pro Breakpoint, Nutzungszahlen pro Breakpoint in tokens.md und Deltatabellen für jedes benachbarte Paardesktop → tablet, dann tablet → mobile. Benachbart statt alles-gegen-Desktop, weil das die Art widerspiegelt, wie Media Queries geschrieben sind: Jeder Schritt wiederholt nur, was sich seit dem vorherigen geändert hat. responsive.md beginnt mit einer Matrix, welche Blöcke sich bei welchem Schritt ändern.

Nichts wird stillschweigend verworfen. Nach der Segmentierung prüft das Tool, ob die Blöcke die Seite kacheln, sucht nach einem Element, das eine Lücke abdeckt, und meldet das Abdeckungsverhältnis. Regionen, die es wirklich nicht beanspruchen kann, werden aufgelistet statt ignoriert. Screenshot-Abmessungen werden gegen das Angeforderte verifiziert, denn ein abgeschnittener Screenshot ist schlimmer als ein fehlgeschlagener – er sieht gut aus, und jede Messung daran ist stillschweigend falsch.


Einem Modell servieren

Ein kalter Lauf verbringt die meiste Zeit mit Browserstart und erstem Paint. Wenn ein Modell iteriert – prüfe diesen Block nochmal, jetzt bei 768px, welche Farbe hat dieses Icon – macht das Bezahlen pro Frage das Tool unbrauchbar. Beide Servermodi halten einen Browser und seine geladenen Seiten warm.

Gemessen auf https://figma.site: 26,8 s kalt → 0,03 s warm.

MCP (stdio)

{
  "mcpServers": {
    "web-design-harvester": {
      "command": "node",
      "args": ["/absolute/path/to/web-design-harvester/bin/harvest.mjs", "mcp"]
    }
  }
}

Tools: harvest_outline, harvest_blocks, harvest_block, harvest_tokens, harvest_assets, harvest_screenshot, harvest_analyse_asset, harvest_site, harvest_status.

Die typische Schleife ist harvest_blocks, um den Abschnitt zu finden, dann harvest_block für seine Struktur – der zweite Aufruf landet in zig Millisekunden, weil die Seite bereits geöffnet ist.

HTTP-Daemon

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"

Bindet nur an Loopback – er ruft beliebige URLs ab und schreibt Dateien dorthin, wo es ihm gesagt wird, also sollte er nicht von außerhalb erreichbar sein. Übergib --host, wenn du das wirklich willst. Leerlaufseiten werden nach 10 Minuten geschlossen.


Anforderungen

  • Node 18+

  • Playwright Chromiumnpm install lädt es über den Postinstall-Hook herunter

  • ffmpeg / ffprobe (optional) – benötigt für Inhaltsboxen, Paletten und Videoanalyse. Ohne sie läuft alles andere weiter; Asset-Intelligenz wird mit einer Warnung übersprungen. brew install ffmpeg

npm test    # 46 tests, ~10s, hermetic (local fixture, no network)

Bekannte Grenzen

  • Cross-Origin-Iframes sind Löcher sowohl im DOM als auch im Screenshot. Das Tool erkennt sie und listet ihre Größe, Herkunft und src in block.md, kann aber nicht hineinsehen. Was der Frame rendert, muss separat behandelt werden.

  • Hover- und Fokus-Stile werden nicht erfasst – sie erfordern Live-Interaktion. interactions.md gibt dir die Übergangseigenschaft und -dauer jedes Elements, was dir sagt, was animiert und wie schnell, aber nicht den Endzustand.

  • Streaming-Video (DASH/fMP4) kommt als Segmente an, die keine gültigen eigenständigen Dateien sind. Diese werden gekennzeichnet statt als beschädigt gemeldet.

  • Canvas- und WebGL-Inhalte werden als Pixel im Screenshot erfasst; es gibt keine Struktur zu extrahieren.

  • Scrollgesteuerte Animation wird an einem Punkt abgetastet. Die Seite wird zuerst von Anfang bis Ende gescrollt, um Enthüllungen auszulösen, dann zum Anfang zurückgebracht; ein Abschnitt, dessen Erscheinung von der Scrollposition abhängt, ist möglicherweise nicht in seinem endgültigen Zustand.

-
license - not tested
Not graded
quality - not tested
C
maintenance

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.

View all MCP Connectors

Latest Blog Posts

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