Plasmate

Plasmate

The browser engine for agents.
HTML in. Semantic Object Model out.

Website · Docs · Benchmarks · Crates.io · npm · PyPI

CI crates.io npm Documented integrations License

--- Dependency and CI trust policy, local audit commands, and scorecard promotion rules are documented in [Supply-chain policy](docs/SUPPLY-CHAIN.md). Plasmate compiles HTML into a **Semantic Object Model (SOM)**, a structured representation that LLMs can reason about directly. It runs JavaScript via V8 and implements a documented CDP subset for supported Puppeteer workflows. SOM output removes presentation and runtime markup; output size and tokenization are page-, configuration-, serialization-, and tokenizer-dependent. | | Plasmate | Lightpanda | Chrome | |---|---|---|---| | **Primary output** | **Structured SOM JSON** | Raw HTML / DOM | Raw HTML / DOM | | **JavaScript** | **Embedded V8 pipeline** | Browser runtime | Browser runtime | | **Agent protocol** | **Native MCP and AWP** | CDP | CDP | | **Benchmark policy** | **Retained, denominator-complete reports** | Project-specific | Project-specific | | **License** | **Apache-2.0** | AGPL-3.0 | Chromium | ## Install ```bash curl -fsSL https://plasmate.app/install.sh | sh ``` Or install the native engine with Cargo: ```bash cargo install plasmate ``` The npm and PyPI packages named `plasmate` are client SDKs; they do not bundle or install the native `plasmate` executable. Install the native engine first, then add `npm install plasmate` or `pip install plasmate` to applications that need the corresponding SDK. ## Quick Start ### Fetch a page and get structured output ```bash plasmate fetch https://news.ycombinator.com ``` Returns SOM JSON: structured regions, interactive elements with stable IDs, and content. Measure output size on the pages and configuration used by your application. ### Start a CDP server (Puppeteer compatible) ```bash plasmate serve --protocol cdp --host 127.0.0.1 --port 9222 ``` Then connect with Puppeteer: ```javascript import puppeteer from 'puppeteer-core'; const browser = await puppeteer.connect({ browserWSEndpoint: 'ws://127.0.0.1:9222', protocolTimeout: 10000, }); const page = await browser.newPage(); await page.goto('https://example.com'); const title = await page.evaluate(() => document.title); console.log(title); await browser.close(); ``` ### Start an AWP server (native protocol) ```bash plasmate serve --protocol awp --host 127.0.0.1 --port 9222 ``` AWP's foundational v0.1 core has seven wire methods: `awp.hello`, `session.create`, `session.close`, `page.navigate`, `page.observe`, `page.act`, and `page.extract`. The current native handler also exposes session-listing, network-interception, plugin, cookie, and proxy-pool extensions. Click, type, select, scroll, toggle, and clear are `page.act` actions, not separate wire methods. ### Run a supervised stateful workflow ```bash plasmate agent-run --plan examples/agent-workflow.json \ --report agent-workflow-report.json --dry-run ``` Plans are versioned and bounded; mutating steps require separate `--confirm-step ` approvals, while JavaScript evaluation and cookie writes also require category opt-ins. See [the workflow contract](docs/agent-workflows.md) for schemas, secret references, containment, and execution examples. ### Run as an MCP tool server (Model Context Protocol) ```bash plasmate mcp ``` This exposes Plasmate over stdio as MCP tools: - `fetch_page` - get structured SOM from any URL - `extract_text` - get clean readable text - `extract_links` - get deduplicated links from a page - `ard_discover` - inspect bounded static ARD v0.9 draft catalog signals without invoking them - `crawl_policy` - evaluate RFC 9309 robots.txt policy without changing fetch behavior - `inspect_page` - return bounded SOM first, with deterministic optional visual fallback - `cache_status` - inspect MCP SOM cache reuse and restorable page-state entries - `session_status` - inspect active sessions, loaded URLs, HTML/SOM/node inventory, and disabled/readonly interactive counts - `trace_status` - inspect bounded action-trace retention for one session - `trace_export` - export privacy-safe `plasmate.trace.v1` events - `trace_clear` - discard retained events without resetting their sequence - `replay_validate` - validate a retained action without executing it - `screenshot_page` - capture a page screenshot, with SOM fallback - `open_page` - start an interactive session (returns session_id, SOM, cache_restored) - `navigate_to` - navigate an existing session - `evaluate` - run JavaScript in the page context - `click` - click elements by SOM element ID - `type_text` - type into an input or textarea - `select_option` - select a dropdown option - `scroll` - scroll the page or an element - `toggle` - toggle checkbox, radio, or details state - `clear` - clear an input or textarea - `close_page` - end a session - `get_cookies` - read session cookies - `set_cookies` - add or update session cookies - `clear_cookies` - remove session cookies For an authenticated local Streamable HTTP endpoint, set a capability token and select the HTTP transport explicitly: ```bash PLASMATE_MCP_HTTP_TOKEN="$(openssl rand -hex 32)" \ plasmate mcp --transport http --host 127.0.0.1 --port 9272 ``` The endpoint is `http://127.0.0.1:9272/mcp`. Every request must include `Authorization: Bearer `. The server implements JSON response mode for the stateful MCP `2025-11-25` transport and the stateless `2026-07-28` release candidate. It deliberately returns HTTP 405 for GET rather than claiming an SSE notification stream it does not implement. See [`docs/mcp-streamable-http.md`](docs/mcp-streamable-http.md) for lifecycle, headers, and browser-Origin policy. Example Claude Desktop config: ```json { "mcpServers": { "plasmate": { "command": "plasmate", "args": ["mcp"] } } } ``` ## For AI Agents Plasmate is purpose-built for AI agent pipelines. Several ways to wire it in: ### MCP (Claude Desktop, Cursor, VS Code Copilot, Windsurf) Add to your MCP config and every tool call automatically uses Plasmate: ```json { "mcpServers": { "plasmate": { "command": "plasmate", "args": ["mcp"] } } } ``` Config file locations: - **Claude Desktop** — `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) - **Cursor** — `~/.cursor/mcp.json` - **VS Code Copilot** — `.vscode/mcp.json` (workspace) or user settings - **Windsurf** — `~/.codeium/windsurf/mcp_config.json` Once connected, the native server advertises its current tool surface through MCP `tools/list`. It includes stateless fetch/extraction/discovery tools, cache/session/trace inspection, screenshot and stateful page interaction, cookie operations, and validation-only replay. Query `tools/list` instead of depending on a hard-coded count; the authoritative registration is [`src/mcp/server.rs`](./src/mcp/server.rs). **Tip:** use `selector="main"` (or `main_content`/`main_contents`/`main-content`/`main-contents`/`main content`/`main contents`/`main_region`/`main_regions`/`main-region`/`main-regions`/`main region`/`main regions`) to strip nav/footer, `selector="interactive"` to return only actionable elements, `selector="h1"` through `selector="h6"` (or `heading1` through `heading6`) to isolate a heading level, or an action selector such as `selector="click"` (the equivalent `selector="action:click"` also works), `selector="type"`, `selector="clear"`, `selector="select"`, `selector="toggle"`, `selector="submit"`, or `selector="reset"` (equivalently `selector="action:reset"`, including `action : reset` and `action = reset`) to build a compact action menu before the LLM sees the content. The MCP tool-name aliases `selector="type_text"` and `selector="select_option"` (including their `action:type_text` and `action:select_option` forms) are also accepted for typing and selection targets. Use `selector="label:"` or `selector="label="` for case-insensitive accessible-label/text matching on regions and elements; element matching also falls back to `aria-label`, `title`, `placeholder`, and `alt` attributes when a compiled label is unavailable; whitespace is normalized so labels split across lines still match. Element selectors also accept common HTML tag aliases: `a`/`link`, `p`/`paragraph`, and `ul`, `ol`, `dl`, or `menu`/`list`/`lists` (plus the explicit `unordered_list`, `ordered_list`, and `definition_list` forms). Structural aliases include `fieldset`/`fieldsets`/`group`/`groups`, ARIA group roles `figure`/`figures`/`radiogroup`/`radiogroups`/`progressbar`/`progressbars`/ `meter`/`meters`/`output`/`outputs`, `hr`/`separator`, `details`/`summary`/`summaries`, and `iframe`; `img`/`image` and ARIA widget names such as `menuitem`/`menu items`, `option`, `tab`, `treeitem`/`tree items`, `switch`, `menuitemcheckbox`, and `menuitemradio` are also accepted. Region selectors accept ARIA landmark aliases such as `navigation`, `nav region`/`nav-region`/`nav regions`, `navigation region`/`navigation-region`/`navigation regions`, `search`/`search navigation`/`search navigations`, `banner`, `complementary`, and `contentinfo`; `forms` and `dialogs` are plural aliases for `form` and `dialog`; `content_region`/`content_regions` (and their hyphenated or space-separated forms) select content regions. Table selectors include `table`/`tables` and `data_table`/`data_tables` (with `data-table`/`data-tables` and `data table`/`data tables` accepted too). Use `selector="media"` to keep images and media-capable grouped elements such as audio, video, and canvas while removing unrelated page content. HTML text-input type names are also accepted as selectors, including `text`, `search_input`/`search-input`, `search_control`/`search-control`, `search_field`/`search-field`, `search_box`/`search-box`, `query`, `query_input`/`query-input`, `query_field`/`query-field`, `query_control`/`query-control`, `querybox`/`query-box`, `email`, `number`, `password`, `tel`, `url`, `date`, `datetime`/`datetime-local`, `month`, `time`, `week`, `color`, `range`, `hidden`, and `file`; each targets the corresponding compiled text-input element. Use `cache_status` after repeated fetches to inspect local MCP SOM cache hits, misses, selector entries, effective-HTML entries, and avoided HTML work. Use `session_status` before long interactive runs to inspect active browser session count, capacity, loaded URLs, raw/effective HTML sizes, SOM sizes, node-map counts, structured data presence, disabled/readonly interactive counts, age, and idle time. Stateful `open_page` and `navigate_to` return `cache_restored=true` when they reuse a content-hash-validated cache entry with both SOM and effective HTML. Set `trace=true` on `open_page` to opt into bounded, memory-only action tracing, then use `trace_status`, `trace_export`, and side-effect-free `replay_validate`. Typed values and page bodies are never exported. See [`docs/session-tracing.md`](docs/session-tracing.md) for the privacy contract, bounds, drift classes, and validation-only limitation. Before a multi-page crawl, use `crawl_policy` or the CLI equivalent: ```bash plasmate crawl-policy https://example.com/private --product-token Plasmate ``` The versioned report distinguishes an unavailable robots file (4xx, access permitted by RFC 9309) from an unreachable one (network/5xx, access denied). It is advisory metadata, not authorization, and does not silently alter ordinary `fetch` or `fetch_page`. See [docs/CRAWL-POLICY.md](docs/CRAWL-POLICY.md). Use `inspect_page` when an agent may need pixels. Its default `auto` mode always returns a bounded compact SOM and only attaches a screenshot for named signals such as near-empty structure, canvas-heavy content, or image-map/image controls. `never` recommends without capture; `always` is an explicit capture request. Page JavaScript is off by default; `javascript=true` runs untrusted page code through the supervised JavaScript worker before the separately isolated screenshot process. Rendering uses a sandboxed/CSP-constrained document with JavaScript disabled, no unsafe file-access or browser-sandbox switches, a dead network proxy, and process-tree cleanup. Plasmate does not interpret the image with a vision model. See [docs/STRUCTURED-VISUAL-FALLBACK.md](docs/STRUCTURED-VISUAL-FALLBACK.md). ### Static ARD discovery (draft) Inspect the three static Agentic Resource Discovery signals supported by Plasmate—the well-known catalog, an HTML `rel="ai-catalog"` link, and a robots.txt `Agentmap` directive: ```bash plasmate ard-discover https://example.com/ --output ard-report.json ``` The versioned `plasmate.ard.discovery.v1` report validates bounded catalog envelopes and labels all publisher metadata, inline data, trust manifests, attestations, and signatures as untrusted and unverified. It does not search a registry, invoke entries, fetch entry endpoints, follow nested catalogs, or verify identity. HTTPS and same-origin catalog discovery references are mandatory, and the ARD path cannot use Plasmate's private-network development override. Standalone reports and fully wrapped MCP tool results each have an independently measured 512 KiB serialized bound. See [docs/ARD.md](docs/ARD.md) for limits and protocol scope. ### Vercel AI SDK Use Plasmate via the AI SDK's built-in MCP client (AI SDK v4+): ```bash npm install ai @ai-sdk/openai ``` ```ts import { experimental_createMCPClient as createMCPClient, generateText } from 'ai' import { Experimental_StdioMCPTransport as StdioMCPTransport } from 'ai/mcp-stdio' import { openai } from '@ai-sdk/openai' const mcp = await createMCPClient({ transport: new StdioMCPTransport({ command: 'plasmate', args: ['mcp'], }), }) const { text } = await generateText({ model: openai('gpt-4o'), tools: await mcp.tools(), maxSteps: 5, prompt: 'Summarize the top 3 stories on news.ycombinator.com', }) await mcp.close() ``` This wires the full Plasmate MCP tool set directly into any Vercel AI SDK agent. See [Vercel AI SDK MCP docs](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling#mcp-tools) for details. ### LLM context - Machine-readable summary: [`https://plasmate.app/llms.txt`](https://plasmate.app/llms.txt) - Codebase guide for AI coding agents: [`AGENTS.md`](./AGENTS.md) - The checked-in [MCP Registry](https://registry.modelcontextprotocol.io) metadata declares `ghcr.io/plasmate-labs/plasmate:v0.6.0` as the runnable, version-pinned OCI package for the next release candidate. This image is not available until the immutable v0.6.0 release workflow publishes it. Only after it is anonymously pullable and the metadata is published can Registry clients launch it over stdio with the `mcp` subcommand. The eventual direct invocation is: ```bash docker run --rm -i ghcr.io/plasmate-labs/plasmate:v0.6.0 mcp ``` The npm and PyPI packages named `plasmate` are client SDKs, not server executables. For a host-native installation, install the Rust binary above and configure the command as `plasmate mcp`. ## What is SOM? The DOM was built for rendering. SOM was built for reasoning. SOM strips layout, styling, scripts, SVGs, and boilerplate. It keeps structure, content, and interactive elements with stable IDs that agents can reference in actions. ## Output-size evidence The retained v0.5.1 public-web snapshots in [`website/docs/coverage.json`](./website/docs/coverage.json) and [`website/docs/coverage-js.json`](./website/docs/coverage-js.json) recorded median serialized-byte ratios of 9.98x over 83 successful non-JavaScript inputs and 9.32x over 82 successful JavaScript inputs, respectively, from 98 attempted URLs in each run. These are historical, observational byte ratios—not universal token savings, cost savings, latency, or task-success claims. Blocked and failed inputs remain in the denominator. See [the benchmarking policy](docs/BENCHMARKING.md) and [public claim registry](claims/evidence.v1.json) before comparing or citing results. ## JavaScript Support Plasmate embeds V8 and executes page JavaScript, including: - Inline and external `