🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@yawlabs/vew-mcp

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@yawlabs/vew-mcp

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

latest
Source
npmnpm
Version
0.4.3
Version published
Maintainers
1
Created
Source

@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: "a@b.com" }), 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

ToolWhat it does
browser_navigateGo to a URL, return a fresh snapshot of the loaded page
browser_snapshotCore — compact ref-tagged element list + readable page text
browser_clickClick the element addressed by ref, return a fresh snapshot
browser_typeType into the field addressed by ref (optionally submit), return a snapshot
browser_scrollScroll up / down / top / bottom, return a snapshot
browser_back / browser_forwardHistory navigation, each returns a snapshot
browser_get_textReadable page text only (no element list)
browser_screenshotPNG of the viewport or full page, as an image
browser_wait_forWait for text to appear / disappear (or a fixed time), then snapshot
browser_press_keyPress a key (Enter, Escape, Arrow*, Control+A, …), optionally on a ref
browser_hoverHover a ref to reveal hover-only menus / tooltips
browser_file_uploadSet local file(s) on an <input type=file> ref
browser_evaluateRun a JS expression in the page, return the JSON-serializable result
browser_handle_dialogSet 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

Keywords

mcp

FAQs

Package last updated on 21 Jul 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts