Using Lightpanda
What Lightpanda is
Lightpanda is a headless browser written in Zig with V8 for JavaScript and
html5ever for HTML parsing. It is not a Chromium fork. It deliberately
omits the entire rendering pipeline (layout, paint, compositing, GPU, font
and image decoding).
Three modes of operation:
lightpanda fetch <URL> — one-shot CLI, dumps HTML or markdown to stdout.
lightpanda serve — CDP WebSocket server; target for Puppeteer/Playwright.
lightpanda mcp — native MCP stdio server exposing browser actions as tools.
Sub-100ms cold start. ~9–11× faster and ~9–16× less memory than headless
Chrome on equivalent workloads. Beta maturity — ~95% site compatibility.
Scope
- Interactive, stateful browsing flows where the agent must
goto, click,
and fill over multiple steps.
- JS-dependent pages where static HTTP fetches miss important content.
- Targeted extraction from rendered page state using
markdown,
structuredData, or a scoped node subtree.
Non-goals
- Open-web source discovery or ranking.
- Bulk multi-URL research extraction tasks.
- Generic URL-to-markdown conversion when no page interaction is needed.
- Non-HTML document conversion (PDF/DOCX/PPTX/audio/image workflows).
When Lightpanda won't work
Stop and use Chrome (or tell the user to) when:
- Screenshots, PDF export, or pixel inspection are needed — no renderer.
- Bot-protected sites (Cloudflare managed challenge, DataDome, Akamai,
PerimeterX) — Lightpanda's fingerprint is trivially detectable.
- Complex SPAs crash or stall — ~5% failure tail on heavy React/Angular/Vue
apps. Fall back rather than retrying.
- Layout-dependent APIs are required (
getBoundingClientRect returns
meaningless values, IntersectionObserver geometry is fake,
CanvasRenderingContext2D.getImageData returns no real pixels).
Starting Lightpanda
MCP mode (preferred for agents)
LIGHTPANDA_DISABLE_TELEMETRY=true lightpanda mcp
Configure in an MCP client (e.g. Claude Desktop claude_desktop_config.json):
{
"mcpServers": {
"lightpanda": {
"command": "lightpanda",
"args": ["mcp"],
"env": { "LIGHTPANDA_DISABLE_TELEMETRY": "true" }
}
}
}
CDP mode (for Puppeteer/Playwright escape hatch)
LIGHTPANDA_DISABLE_TELEMETRY=true lightpanda serve --host 127.0.0.1 --port 9222
Connect via puppeteer-core. See
references/01-cdp-and-configuration.md
for connection details, LP.* extensions, cookies, and proxies.
Key flags
| Flag |
Meaning |
Default |
--log-level |
debug, info, warn, error |
info |
--http_timeout |
Max HTTP transfer time in ms (0 = never) |
10000 |
--http_proxy |
Proxy URL (supports basic auth inline) |
— |
--proxy_bearer_token |
Bearer token for Proxy-Authorization |
— |
--obey-robots |
Respect robots.txt |
off |
--wait-until |
Lifecycle event (networkidle) — fetch only |
— |
--dump |
html or markdown — fetch only |
— |
Always set LIGHTPANDA_DISABLE_TELEMETRY=true when starting Lightpanda.
Pass it as an environment variable in every invocation — serve, mcp,
and fetch.
Core workflow: observe → decide → act
Every browser interaction follows three steps:
- Observe — read the page with
markdown, semantic_tree, or
interactiveElements.
- Decide — reason over the compact observation; plan the next action.
- Act — call
goto, click(node ID), fill(node ID, value).
After every goto, re-observe. Do not carry stale observations forward —
the page changed.
MCP tools
| Tool |
Purpose |
goto |
Navigate to a URL. Specify a wait strategy. |
markdown |
Page content as CommonMark. Optional target node ID for scoping. |
semantic_tree |
Semantic outline with stable node IDs. |
interactiveElements |
Only clickable/fillable elements with node IDs. |
structuredData |
JSON-LD, OpenGraph, schema.org metadata. |
click |
Click by node ID (not CSS selector). |
fill |
Fill a form field by node ID. |
evaluate |
Run arbitrary JS. Escape hatch for unusual cases. |
waitForSelector |
Wait until a CSS selector appears. |
Choosing the right output
| Goal |
Tool |
Typical tokens |
| Read an article or long text |
markdown |
200–800 |
| Understand page structure to plan actions |
semantic_tree |
400–1200 |
| Pick an element to click or fill |
interactiveElements |
100–300 |
| Extract product/article metadata |
structuredData |
100–400 |
| Custom extraction not covered above |
evaluate |
varies |
Output selection rules
- Start with
markdown for read-only research. Do not pull the semantic
tree unless planning to act on the page.
- Call
interactiveElements before any click or fill. It is cheaper
than semantic_tree and returns exactly the node IDs needed for action.
- Use
semantic_tree for structural reasoning — "find the section titled
X and extract the table under it."
- Use
structuredData first on product/article pages — JSON-LD and
OpenGraph are richer and more reliable than DOM extraction.
- Scope
markdown to a node ID (from semantic_tree) for long pages.
A full markdown on a dense page can consume thousands of tokens.
Interaction rules
- Ground actions in node IDs. MCP
click and fill take node IDs
returned by semantic_tree or interactiveElements — never hallucinate
CSS selectors.
- Wait before acting. After
goto, verify expected content is present
(call markdown or waitForSelector) before clicking. Networks are real,
JS is async.
- Re-observe after navigation. Every
goto or click that triggers
navigation invalidates prior observations.
- Abort on repeated failures. If three consecutive actions fail on the
same URL, stop and trigger fallback guidance. If five consecutive tool calls
fail overall, stop and report the problem. Do not loop on broken selectors
or crashed pages.
- Budget context. Prefer
interactiveElements (small) over
semantic_tree (medium) over full markdown (large). Scope to a subtree
via node ID when possible.
- Treat
evaluate as a last resort. Prefer the typed MCP tools; raw JS
is harder to debug and easier to get wrong.
Anti-patterns
- Expecting screenshots or PDF export. The rendering pipeline does not
exist. Do not attempt
screenshot — the tool is absent.
- Using CSS selectors from LLM reasoning for
click/fill. Always use
node IDs from interactiveElements or semantic_tree.
- Carrying stale observations across navigations. Re-observe after every
page change.
- Dumping full-page
markdown on every turn. Scope to a section or use
structuredData when structured data is the goal.
- Retrying endlessly on a failing page. Lightpanda has a ~5% SPA
incompatibility tail. After 3 consecutive failures on the same URL, report
the issue and suggest a Chrome fallback.
Chrome fallback
When a page fails (navigation timeout, JS crash, repeated empty output, or
3 consecutive failures on the same URL):
- Report the failure to the user with the URL and error.
- Suggest retrying with Chrome/Chromium if available.
- Do not silently retry indefinitely — the failure is likely a compatibility
gap, not a transient error.
Example: multi-step form submission
Task: Navigate to a site, find and fill a search form, read results.
Tool sequence:
goto → https://example.com (wait: networkidle)
interactiveElements → returns list including {id: 42, label: "Search", type: "input"} and {id: 43, label: "Submit", type: "button"}
fill → node ID 42, value "headless browser"
click → node ID 43
markdown → returns rendered search results as CommonMark
Key points: step 2 grounds the action in real node IDs before step 3–4
attempt interaction. Step 5 re-observes after the navigation triggered by
the form submission.
Reference material
- CDP escape hatch, cookies, headers, proxies: references/01-cdp-and-configuration.md
- Troubleshooting and limitations: references/02-troubleshooting-and-limitations.md
1---2name: using-lightpanda3description: Operates Lightpanda, a Zig-based headless browser with no rendering pipeline, as an AI agent's browser tool via native MCP or CDP. Covers MCP tool selection (goto, markdown, semantic_tree, interactiveElements, structuredData, click, fill), output mode strategy, the observe-decide-act loop, actions grounded in node IDs, token-efficient page reading, and Chrome fallback triggers. Use when tasks require interactive or stateful web navigation on JS-heavy sites (multi-step flows, form fill/click actions, DOM-aware extraction by node ID), or when the user explicitly asks to use Lightpanda.4---56# Using Lightpanda78## What Lightpanda is910Lightpanda is a headless browser written in Zig with V8 for JavaScript and11html5ever for HTML parsing. It is **not** a Chromium fork. It deliberately12omits the entire rendering pipeline (layout, paint, compositing, GPU, font13and image decoding).1415Three modes of operation:1617- `lightpanda fetch <URL>` — one-shot CLI, dumps HTML or markdown to stdout.18- `lightpanda serve` — CDP WebSocket server; target for Puppeteer/Playwright.19- `lightpanda mcp` — native MCP stdio server exposing browser actions as tools.2021Sub-100ms cold start. ~9–11× faster and ~9–16× less memory than headless22Chrome on equivalent workloads. Beta maturity — ~95% site compatibility.2324## Scope2526- Interactive, stateful browsing flows where the agent must `goto`, `click`,27 and `fill` over multiple steps.28- JS-dependent pages where static HTTP fetches miss important content.29- Targeted extraction from rendered page state using `markdown`,30 `structuredData`, or a scoped node subtree.3132## Non-goals3334- Open-web source discovery or ranking.35- Bulk multi-URL research extraction tasks.36- Generic URL-to-markdown conversion when no page interaction is needed.37- Non-HTML document conversion (PDF/DOCX/PPTX/audio/image workflows).3839## When Lightpanda won't work4041Stop and use Chrome (or tell the user to) when:4243- **Screenshots, PDF export, or pixel inspection** are needed — no renderer.44- **Bot-protected sites** (Cloudflare managed challenge, DataDome, Akamai,45 PerimeterX) — Lightpanda's fingerprint is trivially detectable.46- **Complex SPAs crash or stall** — ~5% failure tail on heavy React/Angular/Vue47 apps. Fall back rather than retrying.48- **Layout-dependent APIs** are required (`getBoundingClientRect` returns49 meaningless values, `IntersectionObserver` geometry is fake,50 `CanvasRenderingContext2D.getImageData` returns no real pixels).5152## Starting Lightpanda5354### MCP mode (preferred for agents)5556```bash57LIGHTPANDA_DISABLE_TELEMETRY=true lightpanda mcp58```5960Configure in an MCP client (e.g. Claude Desktop `claude_desktop_config.json`):6162```json63{64 "mcpServers": {65 "lightpanda": {66 "command": "lightpanda",67 "args": ["mcp"],68 "env": { "LIGHTPANDA_DISABLE_TELEMETRY": "true" }69 }70 }71}72```7374### CDP mode (for Puppeteer/Playwright escape hatch)7576```bash77LIGHTPANDA_DISABLE_TELEMETRY=true lightpanda serve --host 127.0.0.1 --port 922278```7980Connect via `puppeteer-core`. See81[references/01-cdp-and-configuration.md](references/01-cdp-and-configuration.md)82for connection details, LP.* extensions, cookies, and proxies.8384### Key flags8586| Flag | Meaning | Default |87| ---------------------- | -------------------------------------------- | ------- |88| `--log-level` | `debug`, `info`, `warn`, `error` | `info` |89| `--http_timeout` | Max HTTP transfer time in ms (0 = never) | `10000` |90| `--http_proxy` | Proxy URL (supports basic auth inline) | — |91| `--proxy_bearer_token` | Bearer token for `Proxy-Authorization` | — |92| `--obey-robots` | Respect `robots.txt` | off |93| `--wait-until` | Lifecycle event (`networkidle`) — fetch only | — |94| `--dump` | `html` or `markdown` — fetch only | — |9596**Always** set `LIGHTPANDA_DISABLE_TELEMETRY=true` when starting Lightpanda.97Pass it as an environment variable in every invocation — `serve`, `mcp`,98and `fetch`.99100## Core workflow: observe → decide → act101102Every browser interaction follows three steps:1031041. **Observe** — read the page with `markdown`, `semantic_tree`, or105 `interactiveElements`.1062. **Decide** — reason over the compact observation; plan the next action.1073. **Act** — call `goto`, `click(node ID)`, `fill(node ID, value)`.108109After every `goto`, **re-observe**. Do not carry stale observations forward —110the page changed.111112## MCP tools113114| Tool | Purpose |115| --------------------- | ---------------------------------------------------------------- |116| `goto` | Navigate to a URL. Specify a wait strategy. |117| `markdown` | Page content as CommonMark. Optional target node ID for scoping. |118| `semantic_tree` | Semantic outline with stable node IDs. |119| `interactiveElements` | Only clickable/fillable elements with node IDs. |120| `structuredData` | JSON-LD, OpenGraph, schema.org metadata. |121| `click` | Click by **node ID** (not CSS selector). |122| `fill` | Fill a form field by **node ID**. |123| `evaluate` | Run arbitrary JS. Escape hatch for unusual cases. |124| `waitForSelector` | Wait until a CSS selector appears. |125126## Choosing the right output127128| Goal | Tool | Typical tokens |129| ------------------------------------------ | ---------------------- | -------------- |130| Read an article or long text | `markdown` | 200–800 |131| Understand page structure to plan actions | `semantic_tree` | 400–1200 |132| Pick an element to click or fill | `interactiveElements` | 100–300 |133| Extract product/article metadata | `structuredData` | 100–400 |134| Custom extraction not covered above | `evaluate` | varies |135136### Output selection rules137138- **Start with `markdown` for read-only research.** Do not pull the semantic139 tree unless planning to act on the page.140- **Call `interactiveElements` before any `click` or `fill`.** It is cheaper141 than `semantic_tree` and returns exactly the node IDs needed for action.142- **Use `semantic_tree` for structural reasoning** — "find the section titled143 X and extract the table under it."144- **Use `structuredData` first on product/article pages** — JSON-LD and145 OpenGraph are richer and more reliable than DOM extraction.146- **Scope `markdown` to a node ID** (from `semantic_tree`) for long pages.147 A full `markdown` on a dense page can consume thousands of tokens.148149## Interaction rules1501511. **Ground actions in node IDs.** MCP `click` and `fill` take node IDs152 returned by `semantic_tree` or `interactiveElements` — never hallucinate153 CSS selectors.1542. **Wait before acting.** After `goto`, verify expected content is present155 (call `markdown` or `waitForSelector`) before clicking. Networks are real,156 JS is async.1573. **Re-observe after navigation.** Every `goto` or `click` that triggers158 navigation invalidates prior observations.1594. **Abort on repeated failures.** If three consecutive actions fail on the160 same URL, stop and trigger fallback guidance. If five consecutive tool calls161 fail overall, stop and report the problem. Do not loop on broken selectors162 or crashed pages.1635. **Budget context.** Prefer `interactiveElements` (small) over164 `semantic_tree` (medium) over full `markdown` (large). Scope to a subtree165 via node ID when possible.1666. **Treat `evaluate` as a last resort.** Prefer the typed MCP tools; raw JS167 is harder to debug and easier to get wrong.168169## Anti-patterns170171- **Expecting screenshots or PDF export.** The rendering pipeline does not172 exist. Do not attempt `screenshot` — the tool is absent.173- **Using CSS selectors from LLM reasoning for `click`/`fill`.** Always use174 node IDs from `interactiveElements` or `semantic_tree`.175- **Carrying stale observations across navigations.** Re-observe after every176 page change.177- **Dumping full-page `markdown` on every turn.** Scope to a section or use178 `structuredData` when structured data is the goal.179- **Retrying endlessly on a failing page.** Lightpanda has a ~5% SPA180 incompatibility tail. After 3 consecutive failures on the same URL, report181 the issue and suggest a Chrome fallback.182183## Chrome fallback184185When a page fails (navigation timeout, JS crash, repeated empty output, or1863 consecutive failures on the same URL):1871881. Report the failure to the user with the URL and error.1892. Suggest retrying with Chrome/Chromium if available.1903. Do not silently retry indefinitely — the failure is likely a compatibility191 gap, not a transient error.192193## Example: multi-step form submission194195**Task:** Navigate to a site, find and fill a search form, read results.196197**Tool sequence:**1981991. `goto` → `https://example.com` (wait: `networkidle`)2002. `interactiveElements` → returns list including `{id: 42, label: "Search", type: "input"}` and `{id: 43, label: "Submit", type: "button"}`2013. `fill` → node ID 42, value `"headless browser"`2024. `click` → node ID 432035. `markdown` → returns rendered search results as CommonMark204205**Key points:** step 2 grounds the action in real node IDs before step 3–4206attempt interaction. Step 5 re-observes after the navigation triggered by207the form submission.208209## Reference material210211- **CDP escape hatch, cookies, headers, proxies**: [references/01-cdp-and-configuration.md](references/01-cdp-and-configuration.md)212- **Troubleshooting and limitations**: [references/02-troubleshooting-and-limitations.md](references/02-troubleshooting-and-limitations.md)