Token-efficient browser automation MCP for AI agents

Browser MCP

by drisplabs · MIT license · ★ 19 on GitHub · updated Oct 20

Browser MCP cover

Token-efficient browser automation MCP for AI agents, with semantic page snapshots and stable element IDs.

# Drisp Browser

Drisp Browser is an MCP server that gives AI agents a compact, semantic interface to the browser.

**[Website](https://drisp.dev)** · **[npm](https://www.npmjs.com/package/@drisp/browser-mcp)** · **[vs. Playwright MCP](https://drisp.dev/vs-playwright-mcp)**

Instead of exposing the full DOM or accessibility tree, it returns structured page snapshots: visible regions, readable content, interactive elements, stable element IDs, form context, screenshots, canvas inspection, and network activity. Agents can then navigate and act on pages using semantic IDs instead of brittle selectors or massive context dumps.

It is built for coding agents, browser agents, QA agents, research agents, and automation workflows that need reliable web interaction without wasting tokens on low-signal browser internals.

---

## Why this exists

Browser automation is easy for scripts and hard for LLM agents.

Traditional browser tools expose either raw DOM, full accessibility trees, screenshots, or low-level selectors. That works for deterministic code, but it is inefficient for language models. The model has to spend context and reasoning budget separating useful UI intent from implementation noise.

Overview

Drisp Browser is an MCP server that gives AI agents a compact, semantic interface to the browser.

Connects with Agent Alley MCP · agentalley.io/mcp

Files included

  • README.md30.3 KB
Read README.md here
drisplabs/main1 file
README.md
Show the full text970 lines

Drisp Browser

Drisp Browser is an MCP server that gives AI agents a compact, semantic interface to the browser.

Website · npm · vs. Playwright MCP

Instead of exposing the full DOM or accessibility tree, it returns structured page snapshots: visible regions, readable content, interactive elements, stable element IDs, form context, screenshots, canvas inspection, and network activity. Agents can then navigate and act on pages using semantic IDs instead of brittle selectors or massive context dumps.

It is built for coding agents, browser agents, QA agents, research agents, and automation workflows that need reliable web interaction without wasting tokens on low-signal browser internals.


Why this exists

Browser automation is easy for scripts and hard for LLM agents.

Traditional browser tools expose either raw DOM, full accessibility trees, screenshots, or low-level selectors. That works for deterministic code, but it is inefficient for language models. The model has to spend context and reasoning budget separating useful UI intent from implementation noise.

Drisp Browser changes the interface boundary.

The browser still runs through Puppeteer and Chrome DevTools Protocol, but the agent sees a smaller, more semantic representation of the page:

  • What regions exist on the page
  • What the user can read
  • What the user can interact with
  • Which elements are visible, enabled, selected, expanded, or required
  • Which stable eid should be used for the next action
  • What changed after the previous action

The goal is not to mirror the browser. The goal is to expose the page in the shape an agent can reason about.


The core abstraction

Drisp Browser turns a browser page into an agent-readable snapshot.

A snapshot contains compact semantic information such as:

  • Page regions: header, navigation, main content, footer
  • Interactive elements: buttons, links, textboxes, checkboxes, radios, comboboxes
  • Readable content: headings, paragraphs, alerts, labels
  • Element state: visible, enabled, checked, selected, expanded, focused
  • Layout hints: bounding boxes and screen zones
  • Stable element IDs: eid values that can be reused by action tools

Agents act on these IDs:

{
  "eid": "btn-sign-in"
}

rather than reasoning from fragile CSS selectors or repeatedly scanning a large DOM tree.

This makes browser use more predictable for agents because observation and action are connected through a stable semantic contract.


What it is

Drisp Browser is:

  • An MCP server for browser automation
  • A semantic observation layer over Puppeteer and CDP
  • A compact page representation for LLM agents
  • A stable eid-based action interface
  • A toolset for navigation, interaction, forms, screenshots, canvas, readability, and network inspection

Drisp Browser is not:

  • A replacement for Puppeteer
  • A general-purpose browser
  • A visual testing framework
  • A scraping framework
  • A CAPTCHA or anti-bot bypass tool

Puppeteer and CDP remain the execution layer. Drisp Browser changes what the agent sees and how it decides what to do next.


How it works

At a high level:

  1. The agent calls a browser tool through MCP.
  2. Drisp Browser controls Chrome through Puppeteer and CDP.
  3. The current page is reduced into semantic regions, readable content, and actionable elements.
  4. The agent receives a compact snapshot instead of a raw browser dump.
  5. The agent acts using stable element IDs.
  6. Drisp Browser waits for the page to stabilize and returns the updated state.

This keeps browser lifecycle, page representation, and action execution separated.

AI Agent
   ↓ MCP
Drisp Browser
   ↓ semantic snapshots + stable eids
Puppeteer / Chrome DevTools Protocol
   ↓
Chrome / Chromium

Example agent loop

A typical browser-agent loop looks like this:

  1. The agent calls navigate with a URL.
  2. Drisp Browser returns a compact page snapshot.
  3. The agent calls find to locate a semantic element, such as a “Sign in” button or an email field.
  4. The agent calls click, type, select, or press using the returned eid.
  5. Drisp Browser waits for the page to stabilize and returns the updated snapshot.
  6. The agent continues from the changed page state instead of re-reading the entire DOM.

This gives the model a browser interaction loop based on semantic state transitions rather than raw page internals.


Example user journey

The following example shows how an agent might use Drisp Browser inside a dashboard-style web app.

Task:

Find the failed payment from [email protected], open it, add an internal note saying “Customer contacted. Waiting for bank confirmation.”, and confirm the note was saved.

1. Agent navigates to the payments dashboard

Tool call:

{
  "tool": "navigate",
  "input": {
    "url": "https://dashboard.example.com/payments"
  }
}

Tool response:

<state step="1" title="Payments · Dashboard" url="https://dashboard.example.com/payments">
  <meta view="1440x900" scroll="0,0" layer="main" />
  <baseline reason="first" />

  <region name="nav">
    <link id="lnk-home" href="/dashboard">Home</link>
    <link id="lnk-payments" selected="true" href="/payments">Payments</link>
    <link id="lnk-customers" href="/customers">Customers</link>
    <link id="lnk-reports" href="/reports">Reports</link>
  </region>

  <region name="main">
    <h id="hd-payments">Payments</h>
    <btn id="btn-create-payment">Create payment</btn>
    <btn id="btn-export">Export</btn>
    <inp id="inp-search-payments" type="search">Search payments</inp>
    <btn id="btn-filter-status">Status</btn>
    <btn id="btn-filter-date">Date</btn>

    <elt id="payment-row-1" kind="row">₹12,500 succeeded [email protected] Jun 6</elt>
    <elt id="payment-row-2" kind="row">₹8,999 failed [email protected] Jun 6</elt>
    <elt id="payment-row-3" kind="row">₹2,400 refunded [email protected] Jun 5</elt>
  </region>
</state>

The agent does not need to inspect a DOM table. It sees the relevant row directly as semantic state.

2. Agent opens the failed payment

Tool call:

{
  "tool": "click",
  "input": {
    "eid": "payment-row-2"
  }
}

Tool response:

<state step="2" title="Payment ₹8,999 · Dashboard" url="https://dashboard.example.com/payments/pay_8x91">
  <meta view="1440x900" scroll="0,0" layer="main" />
  <baseline reason="navigation" />

  <region name="main">
    <h id="hd-payment-detail">Payment ₹8,999</h>
    <alert id="payment-status" kind="status">Failed</alert>

    <elt id="payment-customer" kind="row">Customer [email protected]</elt>
    <elt id="payment-method" kind="row">Payment method UPI</elt>
    <elt id="payment-failure-reason" kind="row">Failure reason Bank declined transaction</elt>

    <h id="hd-timeline">Timeline</h>
    <elt id="timeline-row-1" kind="row">Payment created Jun 6, 10:42 AM</elt>
    <elt id="timeline-row-2" kind="row">Payment failed Jun 6, 10:43 AM</elt>

    <h id="hd-internal-notes">Internal notes</h>
    <inp id="inp-internal-note" type="textarea">Add an internal note</inp>
    <btn id="btn-save-note">Save note</btn>
  </region>

  <region name="aside">
    <btn id="btn-refund" enabled="false">Refund</btn>
    <btn id="btn-retry-payment">Retry payment</btn>
    <btn id="btn-copy-payment-id">Copy payment ID</btn>
  </region>
</state>

Because this is a navigation, the response is a new baseline. The previous row IDs are no longer assumed valid.

3. Agent types the internal note

Tool call:

{
  "tool": "type",
  "input": {
    "eid": "inp-internal-note",
    "text": "Customer contacted. Waiting for bank confirmation.",
    "clear": true
  }
}

Tool response:

<state step="3" title="Payment ₹8,999 · Dashboard" url="https://dashboard.example.com/payments/pay_8x91">
  <meta view="1440x900" scroll="0,0" layer="main" />
  <diff type="mutation" />

  <region name="main">
    <inp id="inp-internal-note" focused="true" val="Customer contacted. Waiting for bank confirmation." type="textarea">Add an internal note</inp>
  </region>
</state>

The agent receives only the changed field, not the whole page again.

4. Agent saves the note

Tool call:

{
  "tool": "click",
  "input": {
    "eid": "btn-save-note"
  }
}

Tool response:

<state step="4" title="Payment ₹8,999 · Dashboard" url="https://dashboard.example.com/payments/pay_8x91">
  <meta view="1440x900" scroll="0,0" layer="main" />
  <diff type="mutation" added="1">
    <status id="rd-note-status" role="status">Note saved.</status>
  </diff>

  <observations>
    <appeared when="action" eid="toast-note-saved" role="status" delay_ms="180" transient="true">Note saved.</appeared>
  </observations>

  <region name="main">
    <elt id="note-row-1" kind="row">Customer contacted. Waiting for bank confirmation.</elt>
    <btn id="btn-save-note" enabled="false">Save note</btn>
  </region>

  <region name="alert">
    <alert id="toast-note-saved" kind="status">Note saved.</alert>
  </region>
</state>

The agent can now conclude that the note was saved because both signals are present:

  1. A status appeared: Note saved.
  2. A new note row appeared with the submitted text.
5. Agent final answer to the user
Done. I opened the failed ₹8,999 payment for [email protected], added the internal note, and confirmed that the dashboard showed “Note saved.”

This journey demonstrates the core interaction model:

navigate → baseline state
click row → new page baseline
type note → small diff
click save → diff + observation + confirmation state

The agent does not receive raw DOM, brittle selectors, or a massive accessibility tree. It receives a compact state transition after every browser action.


Tool response format

Most browser interaction tools return a compact XML state response.

The response is designed for LLM agents, not for humans reading browser internals. It favors stable semantic IDs, short tags, page regions, and incremental diffs over full DOM dumps.

A typical response contains:

  • <state>: current page state, title, URL, and step number
  • <meta>: viewport, scroll position, and active interaction layer
  • <baseline> or <diff>: whether this is a full page state or an incremental update
  • <observations>: important transient UI changes such as dialogs, alerts, and toasts
  • <region>: grouped actionable elements by semantic page region
  • Short element tags such as <btn>, <inp>, <link>, <chk>, <sel>, and <alert>

Agents should use the id attributes, not CSS selectors, when performing follow-up actions.

Baseline response

This is the shape after navigate when the agent has no previous state.

<state step="1" title="Sign in | Acme" url="https://app.example.com/login">
  <meta view="1280x720" scroll="0,0" layer="main" />
  <baseline reason="first" />

  <region name="header">
    <link id="lnk-home" href="/">Acme</link>
    <link id="lnk-docs" href="/docs">Docs</link>
  </region>

  <region name="main">
    <h id="hd-sign-in">Sign in</h>
    <inp id="inp-email" type="email">Email</inp>
    <inp id="inp-password" type="password">Password</inp>
    <chk id="chk-remember">Remember me</chk>
    <btn id="btn-submit">Sign in</btn>
  </region>
</state>
Diff response

For same-page interactions, Drisp Browser returns only the meaningful change.

<state step="3" title="Sign in | Acme" url="https://app.example.com/login">
  <meta view="1280x720" scroll="0,0" layer="main" />
  <diff type="mutation" />

  <region name="main">
    <inp id="inp-email" focused="true" val="[email protected]" type="email">Email</inp>
  </region>
</state>
Validation error

Readable mutations are rendered inline inside <diff>.

<state step="4" title="Sign in | Acme" url="https://app.example.com/login">
  <meta view="1280x720" scroll="0,0" layer="main" />
  <diff type="mutation">
    <status id="rd-alert-login" role="alert">Invalid email or password.</status>
  </diff>

  <region name="main">
    <inp id="inp-password" focused="true" type="password">Password</inp>
    <btn id="btn-submit">Sign in</btn>
  </region>

  <region name="alert">
    <alert id="rd-alert-login">Invalid email or password.</alert>
  </region>
</state>
Modal response

Overlay layers such as modals, popovers, and drawers show the complete active overlay so the agent can reason about available actions.

<state step="6" title="Products | Acme" url="https://app.example.com/products">
  <meta view="1280x720" scroll="0,0" layer="modal" />
  <diff type="mutation" added="3" />

  <observations>
    <appeared when="action" eid="dlg-delete-product" role="dialog" delay_ms="120">
      <heading eid="dlg-title">Delete product?</heading>
      <text>This action cannot be undone.</text>
      <button eid="btn-confirm-delete">Delete</button>
    </appeared>
  </observations>

  <region name="dialog">
    <h id="dlg-title">Delete product?</h>
    <btn id="btn-cancel">Cancel</btn>
    <btn id="btn-confirm-delete">Delete</btn>
  </region>
</state>
Trimmed large region

When a page has many repeated elements, large regions may be trimmed. The agent can call find with the region to retrieve more.

<state step="9" title="Search results | Acme" url="https://app.example.com/search?q=invoice">
  <meta view="1280x720" scroll="0,420" layer="main" />
  <baseline reason="navigation" />

  <region name="main">
    <link id="result-1" href="/invoice/1001">Invoice 1001</link>
    <link id="result-2" href="/invoice/1002">Invoice 1002</link>
    <link id="result-3" href="/invoice/1003">Invoice 1003</link>
    <link id="result-4" href="/invoice/1004">Invoice 1004</link>
    <link id="result-5" href="/invoice/1005">Invoice 1005</link>
    <!-- trimmed 42 items. Use find with region=main to see all -->
    <link id="result-48" href="/invoice/1048">Invoice 1048</link>
    <link id="result-49" href="/invoice/1049">Invoice 1049</link>
    <link id="result-50" href="/invoice/1050">Invoice 1050</link>
  </region>
</state>

Real-world response examples

These examples are illustrative, but shaped according to the actual response contract: <state>, <meta>, <baseline> or <diff>, optional <observations>, and region-grouped short element tags.

GitHub-style issue page
<state step="1" title="Issue #42 · acme/platform" url="https://github.com/acme/platform/issues/42">
  <meta view="1440x900" scroll="0,0" layer="main" />
  <baseline reason="first" />

  <region name="header">
    <link id="lnk-github-logo" href="/">GitHub</link>
    <inp id="inp-global-search" type="search">Search or jump to...</inp>
    <link id="lnk-pulls" href="/pulls">Pull requests</link>
    <link id="lnk-issues" href="/issues">Issues</link>
    <btn id="btn-create-new">Create new...</btn>
  </region>

  <region name="nav">
    <link id="repo-code" href="/acme/platform">Code</link>
    <link id="repo-issues" selected="true" href="/acme/platform/issues">Issues</link>
    <link id="repo-pulls" href="/acme/platform/pulls">Pull requests</link>
    <link id="repo-actions" href="/acme/platform/actions">Actions</link>
  </region>

  <region name="main">
    <h id="issue-title">Checkout flow fails on Safari</h>
    <btn id="btn-issue-state">Open</btn>
    <link id="lnk-author" href="/nadeem">nadeem</link>
    <btn id="btn-edit-title">Edit</btn>
    <btn id="btn-copy-link">Copy link</btn>

    <h id="comment-1-author">nadeem commented</h>
    <elt id="comment-1-body">Safari users cannot complete checkout after selecting Apple Pay.</elt>
    <btn id="btn-add-reaction">Add reaction</btn>
    <btn id="btn-comment-menu">Comment options</btn>

    <inp id="comment-box" type="textarea">Leave a comment</inp>
    <btn id="btn-comment">Comment</btn>
    <btn id="btn-close-issue">Close issue</btn>
  </region>

  <region name="aside">
    <btn id="btn-assignees">Assignees</btn>
    <btn id="btn-labels">Labels</btn>
    <btn id="btn-projects">Projects</btn>
    <btn id="btn-milestone">Milestone</btn>
  </region>
</state>
Linear-style issue list
<state step="1" title="Drisp · Linear" url="https://linear.app/drisp/team/CORE/active">
  <meta view="1440x900" scroll="0,0" layer="main" />
  <baseline reason="first" />

  <region name="nav">
    <link id="lnk-inbox" href="/inbox">Inbox</link>
    <link id="lnk-my-issues" href="/my-issues">My issues</link>
    <link id="lnk-views" href="/views">Views</link>
    <link id="lnk-roadmaps" href="/roadmaps">Roadmaps</link>
    <link id="lnk-projects" href="/projects">Projects</link>
  </region>

  <region name="main">
    <h id="hd-active-issues">Active issues</h>
    <btn id="btn-new-issue">New issue</btn>
    <btn id="btn-display-options">Display options</btn>
    <btn id="btn-filter">Filter</btn>

    <h id="grp-in-progress">In Progress</h>
    <elt id="issue-core-61" kind="row">CORE-61 Operations console</elt>
    <elt id="issue-core-62" kind="row">CORE-62 GitHub Actions CI pipeline</elt>

    <h id="grp-todo">Todo</h>
    <elt id="issue-core-47" kind="row">CORE-47 KYB Step 2</elt>
    <elt id="issue-core-63" kind="row">CORE-63 Fix Biome config drift</elt>
  </region>
</state>
Payments dashboard
<state step="1" title="Payments · Dashboard" url="https://dashboard.example.com/payments">
  <meta view="1440x900" scroll="0,0" layer="main" />
  <baseline reason="first" />

  <region name="nav">
    <link id="lnk-home" href="/dashboard">Home</link>
    <link id="lnk-payments" selected="true" href="/payments">Payments</link>
    <link id="lnk-customers" href="/customers">Customers</link>
    <link id="lnk-products" href="/products">Products</link>
    <link id="lnk-reports" href="/reports">Reports</link>
  </region>

  <region name="main">
    <h id="hd-payments">Payments</h>
    <btn id="btn-create-payment">Create payment</btn>
    <btn id="btn-export">Export</btn>
    <inp id="inp-search-payments" type="search">Search payments</inp>
    <btn id="btn-filter-status">Status</btn>
    <btn id="btn-filter-date">Date</btn>

    <elt id="payment-row-1" kind="row">₹12,500 succeeded [email protected] Jun 6</elt>
    <elt id="payment-row-2" kind="row">₹8,999 failed [email protected] Jun 6</elt>
    <elt id="payment-row-3" kind="row">₹2,400 refunded [email protected] Jun 5</elt>
  </region>
</state>

Tool surface

Drisp Browser exposes MCP tools across the main phases of browser use.

Session
  • list_pages
  • close_page
Navigation
  • navigate
  • go_back
  • go_forward
  • reload
Observation
  • snapshot
  • find
  • get_element
  • screenshot
Interaction
  • click
  • type
  • press
  • select
  • hover
  • scroll_to
  • scroll
  • drag
  • wheel
Forms
  • get_form
  • get_field
File uploads

There is no upload tool. Clicking an upload control (file input, "Choose file" button, <label for>, or dropzone) returns a synthetic file-picker surface, which you drive with the same click and type tools:

click({ eid: '<upload-control-eid>' }); // → <non_dom kind="file-picker">
type({ eid: 'nd-picker-path', text: '/absolute/path/to/file.pdf', clear: true });
click({ eid: 'nd-picker-choose' });

Paths must be absolute and must resolve on the browser host (the machine or container running Chrome). UPLOAD_ALLOWED_ROOTS bounds which directories may be uploaded from. See Upload Files With Click and Type.

Canvas
  • inspect_canvas
Content
  • read_page
Network
  • list_network_calls
  • search_network_calls

Quickstart

Run the interactive installer — it auto-detects which AI tools you have installed and registers the MCP server and agent skill in one step:

npx @drisp/browser-mcp install

Then ask your AI to use the browser:

Open https://example.com and summarize the main actions available to a user.
Target a specific harness
# Claude Code (also installs the agent skill)
npx @drisp/browser-mcp install --harness claude-code

# Cursor
npx @drisp/browser-mcp install --harness cursor

# VS Code
npx @drisp/browser-mcp install --harness vscode

# Claude Desktop (MCP only — no skill placement)
npx @drisp/browser-mcp install --harness claude-desktop

# Multiple at once
npx @drisp/browser-mcp install --harness cursor,vscode

# All detected harnesses
npx @drisp/browser-mcp install --harness all
Install flags
Flag Description
--harness <id|all|csv> Target harness(es): claude-code, cursor, vscode, claude-desktop, all, or comma-separated
--scope project|user Where to write the config (default: project)
--global Alias for --scope global
--project Alias for --scope project
--browser-mode <mode> Browser mode: auto (default), user, persistent, isolated
--headless Launch Chrome in headless mode
--cdp-url <url> Connect to an existing Chrome DevTools Protocol endpoint
--pin <version> Register an exact version instead of @latest
--dry-run Preview changes without writing files
--yes Skip interactive prompts (non-TTY mode)
Check installation status
npx @drisp/browser-mcp doctor

Prints a per-harness status table showing whether the MCP server is registered and whether the agent skill is installed.

Skill-only installation (advanced)

The drisp-browser skill can also be installed independently — without the MCP server — using npx skills:

npx skills add drisplabs/browser-mcp

This copies only the skill into your agent's skills/ directory. You still need to register the MCP server separately (the install command above does both). npx skills is intentionally kept as a supported alternative for skill-only workflows — see ADR-0003.


Manual setup (Claude Desktop / Cursor / VS Code)

If you prefer to edit config files manually, add the server under the appropriate key:

{
  "mcpServers": {
    "drisp-browser": {
      "command": "npx",
      "args": ["@drisp/browser-mcp@latest"]
    }
  }
}

VS Code uses servers instead of mcpServers and requires "type": "stdio":

{
  "servers": {
    "drisp-browser": {
      "type": "stdio",
      "command": "npx",
      "args": ["@drisp/browser-mcp@latest"]
    }
  }
}

To force connection to your existing Chrome session:

{
  "mcpServers": {
    "drisp-browser": {
      "command": "npx",
      "args": ["@drisp/browser-mcp@latest"],
      "env": {
        "DRISP_BROWSER_MODE": "user"
      }
    }
  }
}

Browser modes

Browser initialization happens automatically on the first browser tool call.

Set DRISP_BROWSER_MODE to control how Chrome is started.

Mode Behavior Profile
unset Auto: try user, then persistent, then isolated Depends on fallback
user Connect to your running Chrome Chrome's default profile
persistent Launch Chrome with a dedicated persistent profile ~/.cache/drisp-browser/chrome-profile
isolated Launch Chrome with a temporary clean profile Deleted on close

Examples:

# Auto mode
npx @drisp/browser-mcp

# Always connect to existing Chrome
DRISP_BROWSER_MODE=user npx @drisp/browser-mcp

# Always launch with persistent profile
DRISP_BROWSER_MODE=persistent npx @drisp/browser-mcp

# Headless isolated browser
DRISP_BROWSER_MODE=isolated DRISP_BROWSER_HEADLESS=true npx @drisp/browser-mcp
Stealth (DRISP_BROWSER_STEALTH)

Launched browsers (persistent/isolated) carry Puppeteer's automation tells — navigator.webdriver, the "controlled by automated test software" infobar, an empty plugins list — which some sites use to false-positive a legitimate session as a bot. DRISP_BROWSER_STEALTH (default true) applies fingerprint-only patches so a launched Chrome looks like your everyday Chrome. It changes nothing about behavior (clicks and typing are untouched) and is a no-op in user mode, where the connected Chrome already has a genuine fingerprint.

# Disable stealth (raw Puppeteer fingerprint)
DRISP_BROWSER_STEALTH=false DRISP_BROWSER_MODE=isolated npx @drisp/browser-mcp

This is not a CAPTCHA or anti-bot bypass tool — it only avoids false-positive blocking of ordinary, authorized use.


Using your existing Chrome

To connect Drisp Browser to your regular Chrome profile:

  1. Open Chrome.
  2. Navigate to chrome://inspect/#remote-debugging.
  3. Enable remote debugging and allow the connection.
  4. Start Drisp Browser with DRISP_BROWSER_MODE=user.
DRISP_BROWSER_MODE=user npx @drisp/browser-mcp

This is useful when an agent needs access to an already authenticated browser session.


CLI arguments

The server accepts transport-level arguments only. Browser configuration is controlled through environment variables.

Argument Description Default
--transport Transport mode: stdio or http stdio
--port Port for HTTP transport 3000

Examples:

# stdio transport
npx @drisp/browser-mcp

# HTTP transport
npx @drisp/browser-mcp --transport http --port 8080

Environment variables

Variable Description Default
DRISP_BROWSER_MODE Browser mode: user, persistent, or isolated unset; auto fallback
DRISP_BROWSER_HEADLESS Run browser headless: true or false false
DRISP_BROWSER_CDP_URL Explicit CDP endpoint; overrides browser mode unset
DRISP_BROWSER_TRIM_REGIONS Set to false to disable region trimming globally true
TRANSPORT Transport mode override, for example http unset
HTTP_HOST Host for HTTP transport 127.0.0.1
HTTP_PORT Port for HTTP transport 3000
LOG_LEVEL Logging level info
CEF_BRIDGE_HOST CDP host for CEF bridge connection 127.0.0.1
CEF_BRIDGE_PORT CDP port for CEF bridge connection 9223
BRING_TO_FRONT Set to true to focus the Chrome tab before each action false
CHROME_PATH Path to Chrome executable unset
UPLOAD_ALLOWED_ROOTS Colon-separated absolute dirs uploads must stay within unset; no restriction
DRISP_BROWSER_DOWNLOAD_DIR Absolute path for browser downloads unset; browser default

Directional benchmark results

Early internal comparisons against Playwright MCP show lower token usage and faster completion on representative browser-agent tasks.

Current directional results:

  • Lower token usage on multi-step navigation tasks
  • Faster task completion in common browsing workflows
  • Same or better success rates in tested scenarios

These results are not yet a formal benchmark suite. They are task-dependent and should be treated as directional until the benchmark harness, task definitions, model versions, and raw traces are published.


Architecture overview

Drisp Browser separates browser automation into three layers.

Browser lifecycle

Responsible for:

  • Launching or connecting to Chrome
  • Managing browser contexts
  • Managing pages and tabs
  • Preserving or isolating browser state depending on mode
Semantic snapshot generation

Responsible for:

  • Extracting DOM structure
  • Reading accessibility metadata
  • Resolving labels and roles
  • Detecting page regions
  • Tracking element state
  • Producing compact agent-readable output
Action resolution

Responsible for:

  • Mapping stable eid values to browser nodes
  • Executing clicks, typing, scrolling, keypresses, and selection
  • Waiting for page stabilization
  • Returning the next page state

This separation lets the browser implementation evolve while keeping the agent-visible interface stable.


Development

Clone the repository:

git clone https://github.com/drisplabs/browser-mcp
cd browser-mcp
npm install

Build:

npm run build

Run locally:

npm start

Run in watch mode:

npm run dev

Run checks:

npm run check

Run tests:

npm test

Inspect with the MCP inspector:

npm run mcp:inspect

Requirements

  • Node.js 22.12 or newer
  • Chrome or Chromium
  • An MCP-compatible client such as Claude Code, Claude Desktop, Cursor, or VS Code

Status

Drisp Browser is under active development.

The public tool surface is intended to stay simple, but internal APIs and snapshot formats may evolve as real-world agent usage informs the design.

Feedback from practitioners building browser agents, coding agents, QA agents, and automation systems is especially welcome.


License

MIT

1# Drisp Browser
2 
3Drisp Browser is an MCP server that gives AI agents a compact, semantic interface to the browser.
4 
5**[Website](https://drisp.dev)** · **[npm](https://www.npmjs.com/package/@drisp/browser-mcp)** · **[vs. Playwright MCP](https://drisp.dev/vs-playwright-mcp)**
6 
7Instead of exposing the full DOM or accessibility tree, it returns structured page snapshots: visible regions, readable content, interactive elements, stable element IDs, form context, screenshots, canvas inspection, and network activity. Agents can then navigate and act on pages using semantic IDs instead of brittle selectors or massive context dumps.
8 
9It is built for coding agents, browser agents, QA agents, research agents, and automation workflows that need reliable web interaction without wasting tokens on low-signal browser internals.
10 
11---
12 
13## Why this exists
14 
15Browser automation is easy for scripts and hard for LLM agents.
16 
17Traditional browser tools expose either raw DOM, full accessibility trees, screenshots, or low-level selectors. That works for deterministic code, but it is inefficient for language models. The model has to spend context and reasoning budget separating useful UI intent from implementation noise.
18 
19Drisp Browser changes the interface boundary.
20 
21The browser still runs through Puppeteer and Chrome DevTools Protocol, but the agent sees a smaller, more semantic representation of the page:
22 
23- What regions exist on the page
24- What the user can read
25- What the user can interact with
26- Which elements are visible, enabled, selected, expanded, or required
27- Which stable `eid` should be used for the next action
28- What changed after the previous action
29 
30The goal is not to mirror the browser. The goal is to expose the page in the shape an agent can reason about.
31 
32---
33 
34## The core abstraction
35 
36Drisp Browser turns a browser page into an agent-readable snapshot.
37 
38A snapshot contains compact semantic information such as:
39 
40- Page regions: header, navigation, main content, footer
41- Interactive elements: buttons, links, textboxes, checkboxes, radios, comboboxes
42- Readable content: headings, paragraphs, alerts, labels
43- Element state: visible, enabled, checked, selected, expanded, focused
44- Layout hints: bounding boxes and screen zones
45- Stable element IDs: `eid` values that can be reused by action tools
46 
47Agents act on these IDs:
48 
49```json
50{
51 "eid": "btn-sign-in"
52}
53```
54 
55rather than reasoning from fragile CSS selectors or repeatedly scanning a large DOM tree.
56 
57This makes browser use more predictable for agents because observation and action are connected through a stable semantic contract.
58 
59---
60 
61## What it is
62 
63Drisp Browser is:
64 
65- An MCP server for browser automation
66- A semantic observation layer over Puppeteer and CDP
67- A compact page representation for LLM agents
68- A stable `eid`-based action interface
69- A toolset for navigation, interaction, forms, screenshots, canvas, readability, and network inspection
70 
71Drisp Browser is not:
72 
73- A replacement for Puppeteer
74- A general-purpose browser
75- A visual testing framework
76- A scraping framework
77- A CAPTCHA or anti-bot bypass tool
78 
79Puppeteer and CDP remain the execution layer. Drisp Browser changes what the agent sees and how it decides what to do next.
80 
81---
82 
83## How it works
84 
85At a high level:
86 
871. The agent calls a browser tool through MCP.
882. Drisp Browser controls Chrome through Puppeteer and CDP.
893. The current page is reduced into semantic regions, readable content, and actionable elements.
904. The agent receives a compact snapshot instead of a raw browser dump.
915. The agent acts using stable element IDs.
926. Drisp Browser waits for the page to stabilize and returns the updated state.
93 
94This keeps browser lifecycle, page representation, and action execution separated.
95 
96```text
97AI Agent
98 ↓ MCP
99Drisp Browser
100 ↓ semantic snapshots + stable eids
101Puppeteer / Chrome DevTools Protocol
102 ↓
103Chrome / Chromium
104```
105 
106---
107 
108## Example agent loop
109 
110A typical browser-agent loop looks like this:
111 
1121. The agent calls `navigate` with a URL.
1132. Drisp Browser returns a compact page snapshot.
1143. The agent calls `find` to locate a semantic element, such as a “Sign in” button or an email field.
1154. The agent calls `click`, `type`, `select`, or `press` using the returned `eid`.
1165. Drisp Browser waits for the page to stabilize and returns the updated snapshot.
1176. The agent continues from the changed page state instead of re-reading the entire DOM.
118 
119This gives the model a browser interaction loop based on semantic state transitions rather than raw page internals.
120 
121---
122 
123## Example user journey
124 
125The following example shows how an agent might use Drisp Browser inside a dashboard-style web app.
126 
127Task:
128 
129> Find the failed payment from `[email protected]`, open it, add an internal note saying “Customer contacted. Waiting for bank confirmation.”, and confirm the note was saved.
130 
131### 1. Agent navigates to the payments dashboard
132 
133Tool call:
134 
135```json
136{
137 "tool": "navigate",
138 "input": {
139 "url": "https://dashboard.example.com/payments"
140 }
141}
142```
143 
144Tool response:
145 
146```xml
147<state step="1" title="Payments · Dashboard" url="https://dashboard.example.com/payments">
148 <meta view="1440x900" scroll="0,0" layer="main" />
149 <baseline reason="first" />
150 
151 <region name="nav">
152 <link id="lnk-home" href="/dashboard">Home</link>
153 <link id="lnk-payments" selected="true" href="/payments">Payments</link>
154 <link id="lnk-customers" href="/customers">Customers</link>
155 <link id="lnk-reports" href="/reports">Reports</link>
156 </region>
157 
158 <region name="main">
159 <h id="hd-payments">Payments</h>
160 <btn id="btn-create-payment">Create payment</btn>
161 <btn id="btn-export">Export</btn>
162 <inp id="inp-search-payments" type="search">Search payments</inp>
163 <btn id="btn-filter-status">Status</btn>
164 <btn id="btn-filter-date">Date</btn>
165 
166 <elt id="payment-row-1" kind="row">₹12,500 succeeded [email protected] Jun 6</elt>
167 <elt id="payment-row-2" kind="row">₹8,999 failed [email protected] Jun 6</elt>
168 <elt id="payment-row-3" kind="row">₹2,400 refunded [email protected] Jun 5</elt>
169 </region>
170</state>
171```
172 
173The agent does not need to inspect a DOM table. It sees the relevant row directly as semantic state.
174 
175### 2. Agent opens the failed payment
176 
177Tool call:
178 
179```json
180{
181 "tool": "click",
182 "input": {
183 "eid": "payment-row-2"
184 }
185}
186```
187 
188Tool response:
189 
190```xml
191<state step="2" title="Payment ₹8,999 · Dashboard" url="https://dashboard.example.com/payments/pay_8x91">
192 <meta view="1440x900" scroll="0,0" layer="main" />
193 <baseline reason="navigation" />
194 
195 <region name="main">
196 <h id="hd-payment-detail">Payment ₹8,999</h>
197 <alert id="payment-status" kind="status">Failed</alert>
198 
199 <elt id="payment-customer" kind="row">Customer [email protected]</elt>
200 <elt id="payment-method" kind="row">Payment method UPI</elt>
201 <elt id="payment-failure-reason" kind="row">Failure reason Bank declined transaction</elt>
202 
203 <h id="hd-timeline">Timeline</h>
204 <elt id="timeline-row-1" kind="row">Payment created Jun 6, 10:42 AM</elt>
205 <elt id="timeline-row-2" kind="row">Payment failed Jun 6, 10:43 AM</elt>
206 
207 <h id="hd-internal-notes">Internal notes</h>
208 <inp id="inp-internal-note" type="textarea">Add an internal note</inp>
209 <btn id="btn-save-note">Save note</btn>
210 </region>
211 
212 <region name="aside">
213 <btn id="btn-refund" enabled="false">Refund</btn>
214 <btn id="btn-retry-payment">Retry payment</btn>
215 <btn id="btn-copy-payment-id">Copy payment ID</btn>
216 </region>
217</state>
218```
219 
220Because this is a navigation, the response is a new baseline. The previous row IDs are no longer assumed valid.
221 
222### 3. Agent types the internal note
223 
224Tool call:
225 
226```json
227{
228 "tool": "type",
229 "input": {
230 "eid": "inp-internal-note",
231 "text": "Customer contacted. Waiting for bank confirmation.",
232 "clear": true
233 }
234}
235```
236 
237Tool response:
238 
239```xml
240<state step="3" title="Payment ₹8,999 · Dashboard" url="https://dashboard.example.com/payments/pay_8x91">
241 <meta view="1440x900" scroll="0,0" layer="main" />
242 <diff type="mutation" />
243 
244 <region name="main">
245 <inp id="inp-internal-note" focused="true" val="Customer contacted. Waiting for bank confirmation." type="textarea">Add an internal note</inp>
246 </region>
247</state>
248```
249 
250The agent receives only the changed field, not the whole page again.
251 
252### 4. Agent saves the note
253 
254Tool call:
255 
256```json
257{
258 "tool": "click",
259 "input": {
260 "eid": "btn-save-note"
261 }
262}
263```
264 
265Tool response:
266 
267```xml
268<state step="4" title="Payment ₹8,999 · Dashboard" url="https://dashboard.example.com/payments/pay_8x91">
269 <meta view="1440x900" scroll="0,0" layer="main" />
270 <diff type="mutation" added="1">
271 <status id="rd-note-status" role="status">Note saved.</status>
272 </diff>
273 
274 <observations>
275 <appeared when="action" eid="toast-note-saved" role="status" delay_ms="180" transient="true">Note saved.</appeared>
276 </observations>
277 
278 <region name="main">
279 <elt id="note-row-1" kind="row">Customer contacted. Waiting for bank confirmation.</elt>
280 <btn id="btn-save-note" enabled="false">Save note</btn>
281 </region>
282 
283 <region name="alert">
284 <alert id="toast-note-saved" kind="status">Note saved.</alert>
285 </region>
286</state>
287```
288 
289The agent can now conclude that the note was saved because both signals are present:
290 
2911. A status appeared: `Note saved.`
2922. A new note row appeared with the submitted text.
293 
294### 5. Agent final answer to the user
295 
296```text
297Done. I opened the failed ₹8,999 payment for [email protected], added the internal note, and confirmed that the dashboard showed “Note saved.”
298```
299 
300This journey demonstrates the core interaction model:
301 
302```text
303navigate → baseline state
304click row → new page baseline
305type note → small diff
306click save → diff + observation + confirmation state
307```
308 
309The agent does not receive raw DOM, brittle selectors, or a massive accessibility tree. It receives a compact state transition after every browser action.
310 
311---
312 
313## Tool response format
314 
315Most browser interaction tools return a compact XML state response.
316 
317The response is designed for LLM agents, not for humans reading browser internals. It favors stable semantic IDs, short tags, page regions, and incremental diffs over full DOM dumps.
318 
319A typical response contains:
320 
321- `<state>`: current page state, title, URL, and step number
322- `<meta>`: viewport, scroll position, and active interaction layer
323- `<baseline>` or `<diff>`: whether this is a full page state or an incremental update
324- `<observations>`: important transient UI changes such as dialogs, alerts, and toasts
325- `<region>`: grouped actionable elements by semantic page region
326- Short element tags such as `<btn>`, `<inp>`, `<link>`, `<chk>`, `<sel>`, and `<alert>`
327 
328Agents should use the `id` attributes, not CSS selectors, when performing follow-up actions.
329 
330### Baseline response
331 
332This is the shape after `navigate` when the agent has no previous state.
333 
334```xml
335<state step="1" title="Sign in | Acme" url="https://app.example.com/login">
336 <meta view="1280x720" scroll="0,0" layer="main" />
337 <baseline reason="first" />
338 
339 <region name="header">
340 <link id="lnk-home" href="/">Acme</link>
341 <link id="lnk-docs" href="/docs">Docs</link>
342 </region>
343 
344 <region name="main">
345 <h id="hd-sign-in">Sign in</h>
346 <inp id="inp-email" type="email">Email</inp>
347 <inp id="inp-password" type="password">Password</inp>
348 <chk id="chk-remember">Remember me</chk>
349 <btn id="btn-submit">Sign in</btn>
350 </region>
351</state>
352```
353 
354### Diff response
355 
356For same-page interactions, Drisp Browser returns only the meaningful change.
357 
358```xml
359<state step="3" title="Sign in | Acme" url="https://app.example.com/login">
360 <meta view="1280x720" scroll="0,0" layer="main" />
361 <diff type="mutation" />
362 
363 <region name="main">
364 <inp id="inp-email" focused="true" val="[email protected]" type="email">Email</inp>
365 </region>
366</state>
367```
368 
369### Validation error
370 
371Readable mutations are rendered inline inside `<diff>`.
372 
373```xml
374<state step="4" title="Sign in | Acme" url="https://app.example.com/login">
375 <meta view="1280x720" scroll="0,0" layer="main" />
376 <diff type="mutation">
377 <status id="rd-alert-login" role="alert">Invalid email or password.</status>
378 </diff>
379 
380 <region name="main">
381 <inp id="inp-password" focused="true" type="password">Password</inp>
382 <btn id="btn-submit">Sign in</btn>
383 </region>
384 
385 <region name="alert">
386 <alert id="rd-alert-login">Invalid email or password.</alert>
387 </region>
388</state>
389```
390 
391### Modal response
392 
393Overlay layers such as modals, popovers, and drawers show the complete active overlay so the agent can reason about available actions.
394 
395```xml
396<state step="6" title="Products | Acme" url="https://app.example.com/products">
397 <meta view="1280x720" scroll="0,0" layer="modal" />
398 <diff type="mutation" added="3" />
399 
400 <observations>
401 <appeared when="action" eid="dlg-delete-product" role="dialog" delay_ms="120">
402 <heading eid="dlg-title">Delete product?</heading>
403 <text>This action cannot be undone.</text>
404 <button eid="btn-confirm-delete">Delete</button>
405 </appeared>
406 </observations>
407 
408 <region name="dialog">
409 <h id="dlg-title">Delete product?</h>
410 <btn id="btn-cancel">Cancel</btn>
411 <btn id="btn-confirm-delete">Delete</btn>
412 </region>
413</state>
414```
415 
416### Trimmed large region
417 
418When a page has many repeated elements, large regions may be trimmed. The agent can call `find` with the region to retrieve more.
419 
420```xml
421<state step="9" title="Search results | Acme" url="https://app.example.com/search?q=invoice">
422 <meta view="1280x720" scroll="0,420" layer="main" />
423 <baseline reason="navigation" />
424 
425 <region name="main">
426 <link id="result-1" href="/invoice/1001">Invoice 1001</link>
427 <link id="result-2" href="/invoice/1002">Invoice 1002</link>
428 <link id="result-3" href="/invoice/1003">Invoice 1003</link>
429 <link id="result-4" href="/invoice/1004">Invoice 1004</link>
430 <link id="result-5" href="/invoice/1005">Invoice 1005</link>
431 <!-- trimmed 42 items. Use find with region=main to see all -->
432 <link id="result-48" href="/invoice/1048">Invoice 1048</link>
433 <link id="result-49" href="/invoice/1049">Invoice 1049</link>
434 <link id="result-50" href="/invoice/1050">Invoice 1050</link>
435 </region>
436</state>
437```
438 
439---
440 
441## Real-world response examples
442 
443These examples are illustrative, but shaped according to the actual response contract: `<state>`, `<meta>`, `<baseline>` or `<diff>`, optional `<observations>`, and region-grouped short element tags.
444 
445### GitHub-style issue page
446 
447```xml
448<state step="1" title="Issue #42 · acme/platform" url="https://github.com/acme/platform/issues/42">
449 <meta view="1440x900" scroll="0,0" layer="main" />
450 <baseline reason="first" />
451 
452 <region name="header">
453 <link id="lnk-github-logo" href="/">GitHub</link>
454 <inp id="inp-global-search" type="search">Search or jump to...</inp>
455 <link id="lnk-pulls" href="/pulls">Pull requests</link>
456 <link id="lnk-issues" href="/issues">Issues</link>
457 <btn id="btn-create-new">Create new...</btn>
458 </region>
459 
460 <region name="nav">
461 <link id="repo-code" href="/acme/platform">Code</link>
462 <link id="repo-issues" selected="true" href="/acme/platform/issues">Issues</link>
463 <link id="repo-pulls" href="/acme/platform/pulls">Pull requests</link>
464 <link id="repo-actions" href="/acme/platform/actions">Actions</link>
465 </region>
466 
467 <region name="main">
468 <h id="issue-title">Checkout flow fails on Safari</h>
469 <btn id="btn-issue-state">Open</btn>
470 <link id="lnk-author" href="/nadeem">nadeem</link>
471 <btn id="btn-edit-title">Edit</btn>
472 <btn id="btn-copy-link">Copy link</btn>
473 
474 <h id="comment-1-author">nadeem commented</h>
475 <elt id="comment-1-body">Safari users cannot complete checkout after selecting Apple Pay.</elt>
476 <btn id="btn-add-reaction">Add reaction</btn>
477 <btn id="btn-comment-menu">Comment options</btn>
478 
479 <inp id="comment-box" type="textarea">Leave a comment</inp>
480 <btn id="btn-comment">Comment</btn>
481 <btn id="btn-close-issue">Close issue</btn>
482 </region>
483 
484 <region name="aside">
485 <btn id="btn-assignees">Assignees</btn>
486 <btn id="btn-labels">Labels</btn>
487 <btn id="btn-projects">Projects</btn>
488 <btn id="btn-milestone">Milestone</btn>
489 </region>
490</state>
491```
492 
493### Linear-style issue list
494 
495```xml
496<state step="1" title="Drisp · Linear" url="https://linear.app/drisp/team/CORE/active">
497 <meta view="1440x900" scroll="0,0" layer="main" />
498 <baseline reason="first" />
499 
500 <region name="nav">
501 <link id="lnk-inbox" href="/inbox">Inbox</link>
502 <link id="lnk-my-issues" href="/my-issues">My issues</link>
503 <link id="lnk-views" href="/views">Views</link>
504 <link id="lnk-roadmaps" href="/roadmaps">Roadmaps</link>
505 <link id="lnk-projects" href="/projects">Projects</link>
506 </region>
507 
508 <region name="main">
509 <h id="hd-active-issues">Active issues</h>
510 <btn id="btn-new-issue">New issue</btn>
511 <btn id="btn-display-options">Display options</btn>
512 <btn id="btn-filter">Filter</btn>
513 
514 <h id="grp-in-progress">In Progress</h>
515 <elt id="issue-core-61" kind="row">CORE-61 Operations console</elt>
516 <elt id="issue-core-62" kind="row">CORE-62 GitHub Actions CI pipeline</elt>
517 
518 <h id="grp-todo">Todo</h>
519 <elt id="issue-core-47" kind="row">CORE-47 KYB Step 2</elt>
520 <elt id="issue-core-63" kind="row">CORE-63 Fix Biome config drift</elt>
521 </region>
522</state>
523```
524 
525### Payments dashboard
526 
527```xml
528<state step="1" title="Payments · Dashboard" url="https://dashboard.example.com/payments">
529 <meta view="1440x900" scroll="0,0" layer="main" />
530 <baseline reason="first" />
531 
532 <region name="nav">
533 <link id="lnk-home" href="/dashboard">Home</link>
534 <link id="lnk-payments" selected="true" href="/payments">Payments</link>
535 <link id="lnk-customers" href="/customers">Customers</link>
536 <link id="lnk-products" href="/products">Products</link>
537 <link id="lnk-reports" href="/reports">Reports</link>
538 </region>
539 
540 <region name="main">
541 <h id="hd-payments">Payments</h>
542 <btn id="btn-create-payment">Create payment</btn>
543 <btn id="btn-export">Export</btn>
544 <inp id="inp-search-payments" type="search">Search payments</inp>
545 <btn id="btn-filter-status">Status</btn>
546 <btn id="btn-filter-date">Date</btn>
547 
548 <elt id="payment-row-1" kind="row">₹12,500 succeeded [email protected] Jun 6</elt>
549 <elt id="payment-row-2" kind="row">₹8,999 failed [email protected] Jun 6</elt>
550 <elt id="payment-row-3" kind="row">₹2,400 refunded [email protected] Jun 5</elt>
551 </region>
552</state>
553```
554 
555---
556 
557## Tool surface
558 
559Drisp Browser exposes MCP tools across the main phases of browser use.
560 
561### Session
562 
563- `list_pages`
564- `close_page`
565 
566### Navigation
567 
568- `navigate`
569- `go_back`
570- `go_forward`
571- `reload`
572 
573### Observation
574 
575- `snapshot`
576- `find`
577- `get_element`
578- `screenshot`
579 
580### Interaction
581 
582- `click`
583- `type`
584- `press`
585- `select`
586- `hover`
587- `scroll_to`
588- `scroll`
589- `drag`
590- `wheel`
591 
592### Forms
593 
594- `get_form`
595- `get_field`
596 
597### File uploads
598 
599There is no `upload` tool. Clicking an upload control (file input, "Choose file" button, `<label for>`, or dropzone) returns a synthetic **file-picker surface**, which you drive with the same `click` and `type` tools:
600 
601```js
602click({ eid: '<upload-control-eid>' }); // → <non_dom kind="file-picker">
603type({ eid: 'nd-picker-path', text: '/absolute/path/to/file.pdf', clear: true });
604click({ eid: 'nd-picker-choose' });
605```
606 
607Paths must be absolute and must resolve on the browser host (the machine or container running Chrome). `UPLOAD_ALLOWED_ROOTS` bounds which directories may be uploaded from. See [Upload Files With Click and Type](docs/upload-files-with-click-type.md).
608 
609### Canvas
610 
611- `inspect_canvas`
612 
613### Content
614 
615- `read_page`
616 
617### Network
618 
619- `list_network_calls`
620- `search_network_calls`
621 
622---
623 
624## Quickstart
625 
626Run the interactive installer — it auto-detects which AI tools you have installed and registers the MCP server and agent skill in one step:
627 
628```bash
629npx @drisp/browser-mcp install
630```
631 
632Then ask your AI to use the browser:
633 
634```text
635Open https://example.com and summarize the main actions available to a user.
636```
637 
638### Target a specific harness
639 
640```bash
641# Claude Code (also installs the agent skill)
642npx @drisp/browser-mcp install --harness claude-code
643 
644# Cursor
645npx @drisp/browser-mcp install --harness cursor
646 
647# VS Code
648npx @drisp/browser-mcp install --harness vscode
649 
650# Claude Desktop (MCP only — no skill placement)
651npx @drisp/browser-mcp install --harness claude-desktop
652 
653# Multiple at once
654npx @drisp/browser-mcp install --harness cursor,vscode
655 
656# All detected harnesses
657npx @drisp/browser-mcp install --harness all
658```
659 
660### Install flags
661 
662| Flag | Description |
663| -------------------------- | -------------------------------------------------------------------------------------------------- |
664| `--harness <id\|all\|csv>` | Target harness(es): `claude-code`, `cursor`, `vscode`, `claude-desktop`, `all`, or comma-separated |
665| `--scope project\|user` | Where to write the config (default: `project`) |
666| `--global` | Alias for `--scope global` |
667| `--project` | Alias for `--scope project` |
668| `--browser-mode <mode>` | Browser mode: `auto` (default), `user`, `persistent`, `isolated` |
669| `--headless` | Launch Chrome in headless mode |
670| `--cdp-url <url>` | Connect to an existing Chrome DevTools Protocol endpoint |
671| `--pin <version>` | Register an exact version instead of `@latest` |
672| `--dry-run` | Preview changes without writing files |
673| `--yes` | Skip interactive prompts (non-TTY mode) |
674 
675### Check installation status
676 
677```bash
678npx @drisp/browser-mcp doctor
679```
680 
681Prints a per-harness status table showing whether the MCP server is registered and whether the agent skill is installed.
682 
683### Skill-only installation (advanced)
684 
685The [`drisp-browser`](skills/drisp-browser/SKILL.md) skill can also be installed independently — without the MCP server — using [`npx skills`](https://github.com/vercel-labs/skills):
686 
687```bash
688npx skills add drisplabs/browser-mcp
689```
690 
691This copies only the skill into your agent's `skills/` directory. You still need to register the MCP server separately (the `install` command above does both). `npx skills` is intentionally kept as a supported alternative for skill-only workflows — see [ADR-0003](docs/adr/0003-keep-npx-skills-alongside-installer.md).
692 
693---
694 
695## Manual setup (Claude Desktop / Cursor / VS Code)
696 
697If you prefer to edit config files manually, add the server under the appropriate key:
698 
699```json
700{
701 "mcpServers": {
702 "drisp-browser": {
703 "command": "npx",
704 "args": ["@drisp/browser-mcp@latest"]
705 }
706 }
707}
708```
709 
710VS Code uses `servers` instead of `mcpServers` and requires `"type": "stdio"`:
711 
712```json
713{
714 "servers": {
715 "drisp-browser": {
716 "type": "stdio",
717 "command": "npx",
718 "args": ["@drisp/browser-mcp@latest"]
719 }
720 }
721}
722```
723 
724To force connection to your existing Chrome session:
725 
726```json
727{
728 "mcpServers": {
729 "drisp-browser": {
730 "command": "npx",
731 "args": ["@drisp/browser-mcp@latest"],
732 "env": {
733 "DRISP_BROWSER_MODE": "user"
734 }
735 }
736 }
737}
738```
739 
740---
741 
742## Browser modes
743 
744Browser initialization happens automatically on the first browser tool call.
745 
746Set `DRISP_BROWSER_MODE` to control how Chrome is started.
747 
748| Mode | Behavior | Profile |
749| ------------ | ---------------------------------------------------- | --------------------------------------- |
750| unset | Auto: try `user`, then `persistent`, then `isolated` | Depends on fallback |
751| `user` | Connect to your running Chrome | Chrome's default profile |
752| `persistent` | Launch Chrome with a dedicated persistent profile | `~/.cache/drisp-browser/chrome-profile` |
753| `isolated` | Launch Chrome with a temporary clean profile | Deleted on close |
754 
755Examples:
756 
757```bash
758# Auto mode
759npx @drisp/browser-mcp
760 
761# Always connect to existing Chrome
762DRISP_BROWSER_MODE=user npx @drisp/browser-mcp
763 
764# Always launch with persistent profile
765DRISP_BROWSER_MODE=persistent npx @drisp/browser-mcp
766 
767# Headless isolated browser
768DRISP_BROWSER_MODE=isolated DRISP_BROWSER_HEADLESS=true npx @drisp/browser-mcp
769```
770 
771### Stealth (`DRISP_BROWSER_STEALTH`)
772 
773Launched browsers (`persistent`/`isolated`) carry Puppeteer's automation tells —
774`navigator.webdriver`, the "controlled by automated test software" infobar, an empty
775plugins list — which some sites use to false-positive a legitimate session as a bot.
776`DRISP_BROWSER_STEALTH` (default `true`) applies **fingerprint-only** patches so a launched Chrome
777looks like your everyday Chrome. It changes nothing about behavior (clicks and typing are
778untouched) and is a **no-op in `user` mode**, where the connected Chrome already has a
779genuine fingerprint.
780 
781```bash
782# Disable stealth (raw Puppeteer fingerprint)
783DRISP_BROWSER_STEALTH=false DRISP_BROWSER_MODE=isolated npx @drisp/browser-mcp
784```
785 
786This is not a CAPTCHA or anti-bot bypass tool — it only avoids false-positive blocking of
787ordinary, authorized use.
788 
789---
790 
791## Using your existing Chrome
792 
793To connect Drisp Browser to your regular Chrome profile:
794 
7951. Open Chrome.
7962. Navigate to `chrome://inspect/#remote-debugging`.
7973. Enable remote debugging and allow the connection.
7984. Start Drisp Browser with `DRISP_BROWSER_MODE=user`.
799 
800```bash
801DRISP_BROWSER_MODE=user npx @drisp/browser-mcp
802```
803 
804This is useful when an agent needs access to an already authenticated browser session.
805 
806---
807 
808## CLI arguments
809 
810The server accepts transport-level arguments only. Browser configuration is controlled through environment variables.
811 
812| Argument | Description | Default |
813| ------------- | --------------------------------- | ------- |
814| `--transport` | Transport mode: `stdio` or `http` | `stdio` |
815| `--port` | Port for HTTP transport | `3000` |
816 
817Examples:
818 
819```bash
820# stdio transport
821npx @drisp/browser-mcp
822 
823# HTTP transport
824npx @drisp/browser-mcp --transport http --port 8080
825```
826 
827---
828 
829## Environment variables
830 
831| Variable | Description | Default |
832| ---------------------------- | -------------------------------------------------------- | ---------------------- |
833| `DRISP_BROWSER_MODE` | Browser mode: `user`, `persistent`, or `isolated` | unset; auto fallback |
834| `DRISP_BROWSER_HEADLESS` | Run browser headless: `true` or `false` | `false` |
835| `DRISP_BROWSER_CDP_URL` | Explicit CDP endpoint; overrides browser mode | unset |
836| `DRISP_BROWSER_TRIM_REGIONS` | Set to `false` to disable region trimming globally | `true` |
837| `TRANSPORT` | Transport mode override, for example `http` | unset |
838| `HTTP_HOST` | Host for HTTP transport | `127.0.0.1` |
839| `HTTP_PORT` | Port for HTTP transport | `3000` |
840| `LOG_LEVEL` | Logging level | `info` |
841| `CEF_BRIDGE_HOST` | CDP host for CEF bridge connection | `127.0.0.1` |
842| `CEF_BRIDGE_PORT` | CDP port for CEF bridge connection | `9223` |
843| `BRING_TO_FRONT` | Set to `true` to focus the Chrome tab before each action | `false` |
844| `CHROME_PATH` | Path to Chrome executable | unset |
845| `UPLOAD_ALLOWED_ROOTS` | Colon-separated absolute dirs uploads must stay within | unset; no restriction |
846| `DRISP_BROWSER_DOWNLOAD_DIR` | Absolute path for browser downloads | unset; browser default |
847 
848---
849 
850## Directional benchmark results
851 
852Early internal comparisons against Playwright MCP show lower token usage and faster completion on representative browser-agent tasks.
853 
854Current directional results:
855 
856- Lower token usage on multi-step navigation tasks
857- Faster task completion in common browsing workflows
858- Same or better success rates in tested scenarios
859 
860These results are not yet a formal benchmark suite. They are task-dependent and should be treated as directional until the benchmark harness, task definitions, model versions, and raw traces are published.
861 
862---
863 
864## Architecture overview
865 
866Drisp Browser separates browser automation into three layers.
867 
868### Browser lifecycle
869 
870Responsible for:
871 
872- Launching or connecting to Chrome
873- Managing browser contexts
874- Managing pages and tabs
875- Preserving or isolating browser state depending on mode
876 
877### Semantic snapshot generation
878 
879Responsible for:
880 
881- Extracting DOM structure
882- Reading accessibility metadata
883- Resolving labels and roles
884- Detecting page regions
885- Tracking element state
886- Producing compact agent-readable output
887 
888### Action resolution
889 
890Responsible for:
891 
892- Mapping stable `eid` values to browser nodes
893- Executing clicks, typing, scrolling, keypresses, and selection
894- Waiting for page stabilization
895- Returning the next page state
896 
897This separation lets the browser implementation evolve while keeping the agent-visible interface stable.
898 
899---
900 
901## Development
902 
903Clone the repository:
904 
905```bash
906git clone https://github.com/drisplabs/browser-mcp
907cd browser-mcp
908npm install
909```
910 
911Build:
912 
913```bash
914npm run build
915```
916 
917Run locally:
918 
919```bash
920npm start
921```
922 
923Run in watch mode:
924 
925```bash
926npm run dev
927```
928 
929Run checks:
930 
931```bash
932npm run check
933```
934 
935Run tests:
936 
937```bash
938npm test
939```
940 
941Inspect with the MCP inspector:
942 
943```bash
944npm run mcp:inspect
945```
946 
947---
948 
949## Requirements
950 
951- Node.js 22.12 or newer
952- Chrome or Chromium
953- An MCP-compatible client such as Claude Code, Claude Desktop, Cursor, or VS Code
954 
955---
956 
957## Status
958 
959Drisp Browser is under active development.
960 
961The public tool surface is intended to stay simple, but internal APIs and snapshot formats may evolve as real-world agent usage informs the design.
962 
963Feedback from practitioners building browser agents, coding agents, QA agents, and automation systems is especially welcome.
964 
965---
966 
967## License
968 
969MIT
970 
View all files on GitHub →

Details

Updated
Oct 20, 2026
License
MIT
Stars on the repo
★ 19
Checked
Source
drisplabs/browser-mcp ↗
Creator
drisplabs · 1 listing
View creator

Similar MCP servers

See all →
SkillTests in real browsers via Chrome DevTools MCPBrowser testing with devtoolsaddyosmani/agent-skills · by addyosmaniTests in real browsers via Chrome DevTools MCP. Use when building or debugging anything that runs in a browser. Use when you need to inspect the DOM, capture console errors, analyze network requests, profile performance, or verify visual output with real runtime data. Requires the chrome-devtools MCP server to be configured.★ 98.7k GitHub starsAgentThis MCP server attempts to exercise all the featuresEverything MCP Servermodelcontextprotocol/servers · by modelcontextprotocolThis MCP server attempts to exercise all the features of the MCP protocol. It is not intended to be a useful server, but rather a test…★ 90.5k GitHub starsAgentA Model Context Protocol server that provides web contentFetch MCP Servermodelcontextprotocol/servers · by modelcontextprotocolA Model Context Protocol server that provides web content fetching capabilities. This server enables LLMs to retrieve and process content…★ 90.5k GitHub starsAgentNode.js server implementing Model Context ProtocolFilesystem MCP Servermodelcontextprotocol/servers · by modelcontextprotocolNode.js server implementing Model Context Protocol (MCP) for filesystem operations.★ 90.5k GitHub starsSkillAbout libraries, frameworks, API referencesContext7 MCPupstash/context7 · by upstashThis skill should be used when the user asks about libraries, frameworks, API references, or needs code examples. Activates for setup questions, code generation involving libraries, or mentions of specific frameworks like React, Vue, Next.js, Prisma, Supabase, etc.★ 62.3k GitHub starsSkillUses Chrome DevTools via MCP for efficient debuggingChromeDevTools/chrome-devtools-mcp · by ChromeDevToolsUses Chrome DevTools via MCP for efficient debugging, troubleshooting and browser automation. Use when debugging web pages, automating browser interactions, analyzing performance, or inspecting network requests. This skill does not apply to `--slim` mode (MCP configuration).★ 52.4k GitHub stars