Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Custom Browser

БесплатноНе проверен

MCP server that extracts accessibility tree, design tokens, screenshots, Claude DSL, and Figma JSON from any URL using a persistent Chromium browser with zero L

GitHubEmbed

Описание

MCP server that extracts accessibility tree, design tokens, screenshots, Claude DSL, and Figma JSON from any URL using a persistent Chromium browser with zero LLM cost.

README

Eclectique Browser MCP — turns any live URL or pasted HTML / local file / zip into a multi-output design bundle: a11y tree, design tokens, screenshot, Claude-consumable DSL, Figma REST JSON. Zero LLM API cost. Persistent Chromium context.

v0.10.3 (S67): Phase 3 v0.3 — to_figma_script real bitmap import via upload_assets. The use_figma sandbox does not expose figma.createImageAsync (verified live S66), so the v0.2 runtime feature-check has been replaced with a server-side path. Emit now emits IMAGE-paint nodes as RECT placeholders with name prefix IMAGE:, and surfaces a top-level image_targets: [{var, src, scaleMode, chunkIndex}] aggregate across chunks. Recipe documents the client loop : per target, resolve var → real node id via the accumulated idMap, call upload_assets({fileKey, nodeId, count: 1, scaleMode}) (returns {uploads: [{submitUrl}]}), fetch source bytes from target.src, POST raw bytes (or multipart file field) to submitUrl with the matching Content-Type. The Figma server binds the resulting imageHash as a fill on the existing node automatically — no further use_figma call required. Single-use URLs expire after 10 min ; supported MIME : png / jpeg / gif / webp ; max 10 MB per asset. emit_warnings renamed image_hybrid_inlineimage_deferred_upload. Also fixes pre-existing GRADIENT_LINEAR/GRADIENT_RADIAL emit bugs surfaced live this session : (a) Plugin API rejects gradientHandlePositions → now emits gradientTransform 2x3 affine matrix computed from handle positions (identity [[1,0,0],[0,1,0]] fallback if handles are missing/malformed) ; (b) ColorStop.color requires all four channels — alpha now defaults to 1 when missing from the bundle. Live E2E PASS (partial + headline) : 94 of 181 Awwwards SOTD nodes seeded into fresh Eclectique team file SibdtGErI5YRcxarqBmkFo page 2:2 via chunk 0 ; image target n87 (Awwwards 404 hero bg, 101874 bytes JPEG) uploaded live via upload_assets + raw-bytes POST, server returned {success:true, imageHash:"cf1bc00ca2fb21aa2fe538f6ad59728016dbf83d"}, fill bound to node 2:157 automatically. Chunk 1 retry deferred S68+ (gradient + ColorStop fixes now in emit, full multi-chunk live re-verification a re-paste exercise). Smokes 8/8 PASS (181-node Awwwards → 2 chunks, 43.6 KB + 30.4 KB ; 1 image target collected, well-formed).

v0.10.2 (S66): Phase 3 v0.2 — to_figma_script chunking + image hybrid graceful-degrade. Chunking : new flat-emit + idMap re-parent pass splits emit into self-contained chunks under max_bytes_per_chunk (default 40KB, under use_figma 50KB cap). chunks[0] creates the page ; chunks[i>0] re-attach via figma.getNodeByIdAsync(__EBM_PAGE_ID__) + re-resolve any parent vars from __EBM_ID_MAP_JSON__ substitution. 181-node Awwwards SOTD → 2 chunks (42KB + 32KB), both new Function(...) parse-valid post-substitution. Image hybrid (Q2-D pivot) : emit emits runtime feature-check if (typeof figma.createImageAsync === "function") for image preload. The use_figma sandbox does NOT currently expose createImageAsync (verified live S66), so emit gracefully degrades to v0.1.1 behavior — IMAGE paint skipped, node name prefixed IMAGE: as placeholder marker. Real bitmap import deferred S67+ via upload_assets MCP or inline base64. SVG transport : SVG outerHTML newlines collapsed to single space in emit (SVG is whitespace-tolerant) to avoid transport mangling of \n escapes in multi-KB use_figma code arg. New smoke:script:full test + simulates __EBM_PAGE_ID__ + __EBM_ID_MAP_JSON__ substitution on each chunk and parses each via new Function(...) — passes for 181-node bundle. Live slim-emit E2E (v0.2 single-chunk pattern) verified in test file. Full 181-node multi-chunk live demo deferred — multi-chunk parse simulation + slim v0.2 live E2E suffice as v0.10.2 ship criterion.

v0.10.1 (S63): 2 fixes — (1) figma_emit.ts v0.1.1 made use_figma-compatible : removed (async () => {})() IIFE wrap (use_figma auto-wraps), figma.setCurrentPage(page)await figma.setCurrentPageAsync(page), removed figma.notify(…) (throws "not implemented"), SOLID paint color stripped to {r,g,b} (alpha lifted to paint-level opacity if < 1), return now includes createdNodeIds per figma-use rule #15. Live E2E PASS — 16-node slim Awwwards SOTD bundle rendered into Eclectique team file QGGVEcTSvHEHxf7g6zp9PB (text + SVGs + frames at correct absolute coords). (2) Caveat #1 (Patchright UDD-reuse hang after 3+ consecutive launchPersistentContext on same USER_DATA_DIR) reproduced + worked around. getSharedContext no longer closes + relaunches on viewport mismatch ; new newPageForViewport() helper applies viewport per-page via page.setViewportSize(). Probe test/probe-caveat1-fixed.ts 5/5 PASS post-fix. Upstream Patchright issue #201 filed.

v0.10.0 (S62): 5th MCP tool to_figma_script — Phase 3 v0.1 emit-only. Bundle → use_figma-pasteable Plugin API JS string + recipe.md (create_new_fileuse_figma 2-step dance). Pure-TS emitter src/lib/figma_emit.ts covers FRAME/GROUP/TEXT/RECTANGLE/VECTOR + SOLID/GRADIENT_LINEAR/GRADIENT_RADIAL paints + cornerRadius + clipsContent + lazy loadFontAsync + Inter fallback. Image fills SKIPPED v0.1 (Brain Q2=A) — emitted RECT placeholders named IMAGE:src=.... No multi-call chunking v0.1. Defer v0.2 hybrid image capture S63+.

v0.9.0 (S62): Patchright NodeJS drop-in adopted per RFC-EBM-HYBRID-S61 verdict B. All playwright imports swapped to [email protected] (Apache-2.0, Vinyzu). API parity preserved (launchPersistentContext, Page, types). Anti-detect uplift: Runtime.enable CDP leak patched, --disable-blink-features=AutomationControlled, isolated ExecutionContexts. UDD doctrine + Bun runtime preserved. Smokes 5/5 PASS. Caveat: viewport mid-session switch + file:// goto via toClaude wrapper hangs FIXED v0.10.1 — see above.

v0.8.0 (S61): seed_auth_session headed-login tool (feature-to-steal #4 persistent UDD auth — bypass login-gated portals). Font extractor CSS-first upgrade (feature-to-steal #5): captures full font-family stack, font-style (italic), @font-face URLs, source: system|webfont|unknown classification, and Google-Fonts/Typekit link detection. Bundle schema → v1.2.0 (TokensPayload extended).

v0.7.0 (S61): paste-HTML inputs (html inline string · html_path for .html|.htm|.zip|.mhtml) — XOR with url. Closes html.to.design top gap #1: localhost / auth-gated / staging / email content bypass. Zip unpack to temp dir auto-cleanup. Bundle schema bumped to v1.1.0 (url now plain string).

v0.6.0 (S60): SVG outerHTML preserved · CSS gradients → GRADIENT_LINEAR/GRADIENT_RADIAL paints · ::before/::after pseudo-elements captured · max_nodes override · native JS click pattern documented for interactive-state forcing.

MCP Bun Patchright License: MIT


Tools (5)

Source inputs (v0.7.0) — all 3 tools

Provide exactly one of:

  • url — public http(s):// URL (SSRF-guarded against private/loopback ranges unless allow_private_urls).
  • html — inline raw HTML string (≤8MB). Rendered via page.setContent(). Optional base_url resolves relative refs.
  • html_path — absolute path to .html / .htm / .zip / .mhtml (zip ≤32MB; entry resolved as index.html{zipname}.html → first .html). Loaded via file://. Temp dir auto-cleaned.

XOR enforced server-side (zod refine). All extractors/walkers identical across the three modes.

analyze_page

Multi-output bundle. Pick any subset of {a11y, tokens, screenshot}.

// input — URL mode
{ "url": "https://example.com", "outputs": ["a11y", "tokens", "screenshot"] }
// input — inline HTML
{ "html": "<!doctype html><html>...</html>", "outputs": ["a11y"] }
// input — local file or zip
{ "html_path": "/abs/path/to/page.html" }
{ "html_path": "/abs/path/to/bundle.zip" }

Output: Bundle v1.1.0 (schema). Versioned snapshot_id (sha256 label+ts). bundle.url is now a string label — full inline:html#sha, file://..., or original URL.

Field Type Notes
a11y.yaml string Playwright ariaSnapshot() YAML
a11y.node_count / approx_tokens int metrics
tokens.colors_top array top 16 frequencies with confidence: high|medium|low
tokens.fonts array family + weights + sample_count
tokens.spacing_scale / type_scale_px number[] distinct values
screenshot.path / bytes string / int PNG full-page on disk
warnings string[] per-extractor failures (non-fatal)

to_claude

cbm/htmltoclaude/v0 — compact YAML DSL. Token-economical for Claude ingestion.

// input
{ "url": "https://example.com", "format": "yaml" }

Output: YAML with hoisted token refs (c0..cN colors, f0..fN fonts), 4-tuple boxes, node types FRAME|TEXT|IMAGE|SVG|GROUP|INPUT, conservative pruning of empty GROUPs, shadowRoot recursion, warnings for iframe_cross_origin / bg_gradient_unmapped / shadow_dom_skipped.

Sample: examples/awwwards-sotd.htmltoclaude.yaml (180 nodes, ~19.5K tokens).

to_figma

cbm/htmltofigma/v0 — Figma REST node JSON. Custom adapter (no proprietary API).

// input
{ "url": "https://example.com" }

Output: { document: { children: [{ type: "CANVAS", children: [FigmaNode...] }] }, warnings, metrics }. Mapping table:

ClaudeNode FigmaNode Coverage v0.6.0
FRAME FRAME fill SOLID + corner radius + border + clip + GRADIENT_LINEAR/RADIAL
TEXT TEXT characters + style (fontFamily/Size/Weight)
IMAGE RECTANGLE fill IMAGE (imageRef = src, scaleMode)
SVG VECTOR svgOuterHtml preserved (truncated >20KB)
GROUP GROUP container
INPUT FRAME placeholder
pseudo::before / pseudo::after FRAME / TEXT / IMAGE synthetic child from getComputedStyle(el, '::before')

Sample: examples/awwwards-sotd.htmltofigma.json (180 nodes, ~174 KB JSON).

Deferred Phase 3+: full SVG path parse to native VECTOR geometry, conic-gradient, box shadows (DROP_SHADOW emit), auto-layout (Grid/Flexbox → AUTO_LAYOUT), COMPONENT/INSTANCE detection.


Install

bun install
bunx patchright install chromium

Run

bun run serve              # stdio MCP server
bun run smoke [url]        # analyze_page smoke
bun run smoke:claude [url] # to_claude smoke (writes out/htmltoclaude-{N}n.yaml)
bun run smoke:figma [url]  # to_figma smoke   (writes out/htmltofigma-{N}n.json)
bun run consolidate [url]  # sequential 3-tool + assert htmltoclaude↔figma node parity
bun run typecheck          # tsc --noEmit

Wire into Claude Code (.mcp.json)

{
  "mcpServers": {
    "eclectique-browser": {
      "command": "bun",
      "args": ["run", "serve"],
      "cwd": "/absolute/path/to/eclectique-browser-mcp"
    }
  }
}

Restart Claude Code. Verify with /mcp — should list eclectique-browser with 3 tools.

Stack

  • Bun 1.3 — runtime + package manager
  • TypeScript 5
  • @modelcontextprotocol/sdk 1.29 — stdio transport
  • Patchright 1.59 — Chromium headless persistent context (anti-detect drop-in, Playwright API parity)
  • Zod 4 — input/output schema validation
  • yaml 2.9 — YAML serialization (to_claude)

Architecture

src/
├── lib/
│   └── browser.ts          ← shared Chromium persistent context (singleton, viewport-aware)
├── extractors/
│   ├── a11y.ts             ← page.locator(':root').ariaSnapshot() YAML
│   ├── tokens.ts           ← getComputedStyle walk → colors/fonts/spacing/type
│   ├── screenshot.ts       ← page.screenshot({ fullPage })
│   ├── htmltoclaude.ts     ← in-browser walker + token hoist + conservative pruning
│   └── figma.ts            ← ClaudeBundle → FigmaDocument pure transform
├── tools/
│   ├── analyze_page.ts
│   ├── to_claude.ts
│   └── to_figma.ts
├── schemas/
│   └── output.ts           ← Zod Bundle v1.0.0
└── server.ts               ← MCP stdio + 3 tool registrations

Single Chromium persistent context shared across all 3 tools (factory in src/lib/browser.ts). Profile: out/.chromium-profile/ (override via CBM_USER_DATA_DIR env). Warm context ≈ 25% speedup vs cold launch.

Browser mode (3 options)

Set via CBM_BROWSER_MODE env var. Defaults to chromium.

Mode What Pre-req Use when
chromium (default) Playwright-managed Chromium download (~92 MB once) none First install, CI, reproducible runs
chrome Use Google Chrome stable installed on machine Chrome installed Skip 92 MB download, auto-update via Chrome
cdp Connect to running Chrome via DevTools Protocol Launch Chrome with --remote-debugging-port=9222 Share user cookies/sessions (logged-in IG/Gmail), anti-bot maximal
# Mode chromium (default — managed download)
bun run smoke https://example.com

# Mode chrome (use system Chrome)
CBM_BROWSER_MODE=chrome bun run smoke https://example.com

# Mode cdp (connect to running Chrome with debug port)
# Step 1: launch Chrome with debug port
google-chrome --remote-debugging-port=9222
# Step 2: run with CDP mode
CBM_BROWSER_MODE=cdp CBM_CDP_URL=http://localhost:9222 bun run smoke https://example.com

CDP mode shares ALL browser state (cookies, localStorage, logged-in accounts) — useful for scraping behind login walls, but privacy risk : MCP sees all your tabs and sessions.

Cost & performance

Measured smoke baseline against Awwwards SOTD (1440×900, networkidle), Mac arm64:

Tool Cold Warm Output LLM cost
analyze_page (all outputs) ~6.5s ~2.3s 159 a11y nodes + 5 colors + 453 KB PNG $0
to_claude ~1.9s ~1.3s 180 nodes / 19.5K YAML tokens $0
to_figma ~1.9s ~1.3s 180 nodes / 174 KB JSON / 43K tokens $0
Consolidate (all 3) ~4.9s shared context warm $0

Phase 1 + 2 are 100% local Chromium. Optional LLM augmentation (vision tagging, code emit) gated Phase 3+.


License

MIT

Status

v0.4.2 — Phase 1 (analyze_page) + Phase 1b (to_claude) + Phase 2 v0 (to_figma) + cookie consent lifecycle shipped.

Deferred coverage (Phase 3+): SVG path traversal, gradient parser (GRADIENT_LINEAR), box-shadow effects (DROP_SHADOW), auto-layout heuristic detection (flex/grid), COMPONENT/INSTANCE detection, multi-tab, vision-LLM augmentation.

Issues / PRs welcome.

from github.com/theriz78/eclectique-browser-mcp

Установка Custom Browser

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/theriz78/eclectique-browser-mcp

FAQ

Custom Browser MCP бесплатный?

Да, Custom Browser MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Custom Browser?

Нет, Custom Browser работает без API-ключей и переменных окружения.

Custom Browser — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Custom Browser в Claude Desktop, Claude Code или Cursor?

Открой Custom Browser на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Custom Browser with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории design