npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@yawlabs/vew-mcp

v0.4.2

Published

Browser MCP server for AI agents: drive a real Chromium via Playwright, with a compact token-efficient ref-addressable page snapshot at the center

Readme

@yawlabs/vew-mcp

A browser MCP server that lets AI agents drive a real Chromium optimally. Bring-your-own client: runs as a stdio MCP server so any MCP-compatible client (Claude Code, Claude Desktop, Cursor, mcph, …) can browse the web through a single persistent page.

Add to Yaw MCP

One click adds this to your local Yaw MCP config so it's available in every Yaw Terminal session. Or install manually below.

Why a snapshot instead of the DOM

An agent driving a browser does not need the raw DOM — tens of thousands of tokens of markup, inline styles, and script. It needs to know which elements it can act on, what each one is, and a handle to act on it. That is what browser_snapshot returns:

# Login
URL: https://example.com/login

## Interactive elements
[e1] textbox "Email"
[e2] textbox "Password"
[e3] button "Sign in"
[e4] link "Forgot password?"

## Page text
Welcome back. Please sign in to continue...

Then you act by ref: browser_type({ ref: "e1", text: "[email protected]" }), browser_click({ ref: "e3" }). Every action returns a fresh snapshot, so you always act off the latest view.

Refs are stable within a snapshot and renumber on the next one. Always use a ref from the most recent snapshot; a stale ref returns an error rather than clicking the wrong element after the page changed. This is the same model vew's own agent layer uses.

What it gives the model

| Tool | What it does | |------|--------------| | browser_navigate | Go to a URL, return a fresh snapshot of the loaded page | | browser_snapshot | Core — compact ref-tagged element list + readable page text | | browser_click | Click the element addressed by ref, return a fresh snapshot | | browser_type | Type into the field addressed by ref (optionally submit), return a snapshot | | browser_scroll | Scroll up / down / top / bottom, return a snapshot | | browser_back / browser_forward | History navigation, each returns a snapshot | | browser_get_text | Readable page text only (no element list) | | browser_screenshot | PNG of the viewport or full page, as an image | | browser_wait_for | Wait for text to appear / disappear (or a fixed time), then snapshot | | browser_press_key | Press a key (Enter, Escape, Arrow*, Control+A, …), optionally on a ref | | browser_hover | Hover a ref to reveal hover-only menus / tooltips | | browser_file_upload | Set local file(s) on an <input type=file> ref | | browser_evaluate | Run a JS expression in the page, return the JSON-serializable result | | browser_handle_dialog | Set accept/dismiss for the next native dialog; report the last one |

One persistent Chromium + one page per server process. The browser launches lazily on the first navigation and tears down cleanly when the client disconnects.

Install & run

# One-off (auto-updates each spawn via @latest)
npx -y @yawlabs/vew-mcp@latest

# Or globally
npm i -g @yawlabs/vew-mcp
vew-mcp

Requires Node ≥22. The first run needs the Chromium browser that Playwright drives:

npx playwright install chromium

(One-time, ~150 MB. The server itself does not download it for you — keeping the npm package light.)

Set VEW_HEADLESS=0 to watch the browser instead of running it headless.

Driving an existing browser (Vew)

The default mode launches a self-owned headless Chromium. To drive an existing browser instead -- most commonly the Vew desktop browser when the user has enabled the MCP bridge in Vew's Agent panel -- set VEW_CDP_URL to the host's Chrome DevTools Protocol endpoint:

VEW_CDP_URL=ws://127.0.0.1:9222 npx -y @yawlabs/vew-mcp@latest

In attach mode the host owns the process lifetime: VEW_HEADLESS is ignored, and vew-mcp does not close the host's browser on exit -- it just disconnects. The active page is selected from the host's first browser context (the most-recently focused page). Native dialogs on the host page default to dismiss in attach mode (vs auto-accept when vew drives its own browser), so the agent does not silently confirm the human's prompts; use browser_handle_dialog to accept a specific one. The threat model is wider than the default mode: a prompt-injected instruction that says "navigate to file:///etc/passwd" navigates the user's actual browser, with their cookies and authenticated sessions, not an isolated Playwright Chromium. Enable the bridge in Vew only when you trust the agent.

Configure in Claude Code / Claude Desktop

Add to your client's MCP config (usually claude_desktop_config.json or ~/.claude.json):

{
  "mcpServers": {
    "vew": {
      "command": "npx",
      "args": ["-y", "@yawlabs/vew-mcp@latest"]
    }
  }
}

Or via mcph:

mcph add vew

Tool reference

browser_navigate

{
  url: string;              // absolute http/https URL
  wait_until?: "load" | "domcontentloaded" | "networkidle" | "commit";  // default "load"
  timeout_ms?: number;      // default 30000
}

Navigates the persistent page and returns a fresh snapshot. Usually the first call.

browser_snapshot

{
  text_budget?: number;     // max chars of page text (default 8000)
}

Returns the compact view of the current page: a ref-tagged element list followed by readable text. Each element line is [ref] role "name", with optional trailing (value: "...", checked, disabled). Prefer this over any raw-DOM approach.

browser_click

{
  ref: string;              // e.g. "e3", from the most recent snapshot
  timeout_ms?: number;      // default 10000
}

Clicks the element and returns a fresh snapshot. Use for buttons, links, checkboxes, tabs, menu items.

browser_type

{
  ref: string;              // a textbox/searchbox/combobox ref
  text: string;             // value to type (field is cleared first)
  submit?: boolean;         // press Enter after typing (default false)
  timeout_ms?: number;      // default 10000
}

Fills the field and optionally submits (e.g. to run a search). Returns a fresh snapshot.

browser_scroll

{
  direction: "up" | "down" | "top" | "bottom";
}

Scrolls one viewport (up/down) or jumps to the page extreme (top/bottom), then returns a fresh snapshot — useful to surface lazily-loaded content.

browser_back / browser_forward

No parameters. Navigate one entry in browser history and return a fresh snapshot. Errors if there is no entry in that direction.

browser_get_text

{
  text_budget?: number;     // max chars (default 8000)
}

Returns the page's readable text (innerText), normalized and clamped — without the interactive-element list.

browser_screenshot

{
  full_page?: boolean;      // capture the whole scrollable page (default false = viewport)
}

Returns a PNG as MCP image content. Prefer browser_snapshot for acting on the page; use a screenshot when you need to see pixels (layout, charts, visual verification). An over-cap image (very tall full_page) is refused with an error rather than swamping the client context — retake with full_page: false.

browser_wait_for

{
  text?: string;        // wait until this text becomes visible
  text_gone?: string;   // wait until this text is no longer visible
  time_ms?: number;     // OR just wait this many ms (max 30000)
  timeout_ms?: number;  // max wait for a text condition (default 10000)
}

Provide at least one of text / text_gone / time_ms (when several are given they apply in that order), then get a fresh snapshot. Use it to settle async / SPA rendering before snapshotting instead of racing a half-rendered DOM.

browser_press_key

{
  key: string;          // "Enter", "Escape", "ArrowDown", "Control+A", …
  ref?: string;         // focus this element first; omit to target current focus
  timeout_ms?: number;  // default 10000
}

Press a single key, optionally on a ref. With no ref the key goes to the focused element (e.g. Escape to close a modal). Returns a fresh snapshot.

browser_hover

{ ref: string; timeout_ms?: number }

Hover the element to reveal hover-only menus, tooltips, or action icons, then return a fresh snapshot.

browser_file_upload

{
  ref: string;          // an <input type=file> ref (shows as a textbox in the snapshot)
  paths: string[];      // absolute path(s) on the machine running the browser
  timeout_ms?: number;  // default 10000
}

browser_evaluate

{
  expression: string;   // a JS expression evaluated in the page; result must be JSON-serializable
  timeout_ms?: number;  // abort with an error if it hasn't resolved (default 30000)
}

The escape hatch for reading computed state or extracting structured data the snapshot does not surface. Wrap an object literal in parens: ({ a: 1 }). Runs in the page sandbox (not the server process); arbitrary JS can mutate the page, so it is not a read-only call. The call is bounded by timeout_ms so a slow/stalled expression returns control with an error instead of hanging the shared page (a synchronous infinite loop still pins the page's JS thread, so prefer terminating expressions).

browser_handle_dialog

{
  accept?: boolean;     // accept (default) or dismiss the next dialog
  prompt_text?: string; // answer for a prompt(); ignored when accept is false
}

In its own launched browser vew auto-accepts native dialogs (alert / confirm / prompt / beforeunload) by default, so a confirm-gated action proceeds and alert() text is captured rather than silently dismissed; when attached to an existing browser it dismisses by default so it does not hijack the human's dialogs. Pass accept to override that for the next dialog (and prompt_text to answer a prompt()); the override stays armed until a dialog actually fires, then reverts to the mode default. Calling with no arguments does not change the disposition — it just reports the last dialog and leaves the mode default in place (so a no-arg read can't accidentally arm an accept on the human's browser in attach mode).

How a typical session looks

browser_navigate { url: "https://news.ycombinator.com" }
  -> snapshot with [e1] link "Hacker News", [e12] link "Show HN: ...", ...
browser_click { ref: "e12" }
  -> fresh snapshot of the linked story
browser_back {}
  -> back to the front page (refs renumbered)
browser_type { ref: "e3", text: "playwright", submit: true }   // search box
  -> fresh snapshot of results

Development

npm install
npx playwright install chromium   # one-time, for live-browser usage
npm run build
npm test         # vitest (pure snapshot logic + --version; no browser needed)
npm run lint     # biome
npm run typecheck

The vitest suite exercises the token-shaping logic (ref numbering, element-line rendering, text normalization + clamping, full snapshot layout) without launching Chromium — the heavy browser binary is never required to verify the part of the code where the real complexity lives.

Release

This repo follows the YawLabs "CI trim" convention: no .github/workflows/release.yml. Releases ship locally:

npm login --auth-type=web    # once, in your terminal (WebAuthn)
bash release.sh 0.2.0        # lint, test, build, bump, tag, publish, GitHub + MCP Registry release

release.sh is idempotent — re-running with the same version skips completed steps.

License

MIT © Yaw Labs

Links

  • npm: https://www.npmjs.com/package/@yawlabs/vew-mcp
  • issues: https://github.com/YawLabs/vew-mcp/issues
  • Yaw Labs: https://vew.sh