Data, safety and extensions
Browsers
Real browsers for agents to read and use web pages in, on the Chrome or Edge installed here or a hosted browser service, kept to the origins you allow, with permission-gated tools.
mayura/browser lets an agent use a real browser: open pages, read them, click, type and choose, take screenshots.
It speaks the Chrome DevTools Protocol itself, with no dependency. The Chrome or Edge installed on your machine is built
in (mayura/browser/local), any browser with a CDP endpoint can be reached (cdpBrowsers), and hosted browser
services come as @mayurajs/browser-* packages. Every browser has a lifetime, and its pages load only from the origins
you allow.
import { createBrowsers } from 'mayura/browser';
import { localBrowsers } from 'mayura/browser/local';
const browsers = createBrowsers(localBrowsers(), {
maxBrowsers: 2,
maxLifetimeMs: 30 * 60_000,
origins: ['https://example.com', 'https://*.example.com'],
});
const browser = await browsers.open();
try {
await browser.goto('https://example.com/');
const page = await browser.snapshot();
console.log(page.text);
// - heading "Example Domain"
// - link "Learn more" [ref=e1]
await browser.click('e1');
} finally {
await browser.release();
}Limits
createBrowsers(provider, options) holds every browser from a provider to the same limits.
| Option | Notes |
|---|---|
maxBrowsers |
Required. The most browsers open at once, counting those opening; more fail with LIMIT_EXCEEDED. |
maxLifetimeMs |
Required. The longest a browser may live; at most the provider's. |
origins |
Required. The origins pages may load from, or 'all'. See Origins. |
maxTabs |
The most tabs a browser may have open; 8 by default. |
callTimeoutMs |
How long one call other than a navigation may take; 30 s by default. |
navigationTimeoutMs |
How long a navigation waits for its page to load; 30 s by default. A page still loading is returned with loaded: false. |
maxTextBytes |
The most bytes of a snapshot, of page text or of an evaluated value; 256 KiB by default. |
maxScreenshotBytes |
The largest screenshot; 8 MiB by default. |
viewport |
The window size pages are laid out for; 1280 × 800 by default. |
labels |
Labels given to every browser, to find sessions in the provider's console. |
webSocket |
Opens the CDP WebSocket, for runtimes whose WebSocket cannot send headers. |
browsers.close() releases every browser still open and opens no more.
Origins
Nothing is allowed by default: origins lists what pages may load from, as https://example.com (scheme, host and
port), https://*.example.com (its subdomains, not example.com itself) or http://127.0.0.1:8080. 'all' allows
every http(s) URL. originPolicy(origins) checks a URL against such a list, for tools of your own that fetch pages.
The list is enforced in the browser, not only at goto. Every request is checked before it leaves — navigations,
redirects, frames, scripts, images, fetch, and requests from dedicated, shared and service workers — and one to
another origin fails as blocked. Each new page, frame or worker is held until its requests are checked; one that
cannot be checked never runs. goto to another origin, or a navigation redirected to one, fails with
PERMISSION_DENIED. When the browser is this one's alone, requests are checked for the whole browser, so a page
another client opens (such as Stagehand, over browser.cdp) is covered from its first request. In a browser shared
with others (cdpBrowsers), each page is checked as it is attached, as others' pages must be left alone: a page
another CDP client opens there with a URL may make that first request unchecked.
Downloads are refused, file pickers stay closed, and dialogs (alert, confirm, prompt) are dismissed and reported
in the next snapshot.
A browser
browsers.open(options) takes lifetimeMs, labels and signal. The browser ends when its lifetime is over, if
release() did not end it first; after that every call fails with a BrowserError whose reason is gone. Calls on
one browser run one at a time, in order.
| Method | Notes |
|---|---|
goto(url) |
Opens a page in the current tab. Returns its url, title, status and whether it loaded. |
back(), forward(), reload() |
Move through the tab's history. |
snapshot() |
The page as an outline: one line per meaningful element, indented by depth. Elements to act on carry [ref=eN]; refs from earlier snapshots stop working. Also reports dialogs the page opened. |
text({ ref }) |
The visible text of the page, or of one element. |
click(ref, { double }), hover(ref) |
Point at an element by its ref. |
fill(ref, text) |
Replaces the text of an input or editable element, as typing would. |
press(keys) |
Presses a key on what has focus: Enter, Tab, ArrowDown, a, Control+a, Shift+Tab. |
select(ref, option) |
Chooses an option of a list by its value or label. |
scroll(dy, { ref }) |
Scrolls the page, or an element, by dy pixels. |
screenshot({ fullPage }) |
A PNG of the visible page, or all of it. |
evaluate(expression) |
Runs JavaScript in the page; returns { value } as JSON, or { error } with what it threw. |
tabs(), newTab(url), selectTab(tab), closeTab(tab) |
Tabs, including windows a page opens. |
release() |
Ends the browser. |
liveViewUrl is where a person can watch, on providers with a live view. It may carry a token: treat it as a secret.
For a live view of any browser other clients can join, local ones included, @mayurajs/browser-viewer streams the
active tab to a page: serveBrowserViewer() then share(browser) gives a link that expires, view only unless
interact: true lets a person use the page (within the same origins). browserViewer() is the same as request
handling to mount in your own server.
cdp is the browser's CDP endpoint (url, headers, and whether it is isolated), for another client to drive it
alongside, such as Stagehand. It may carry a credential. Origins stay enforced for every page while the browser is open.
It is absent where another connection would not reach this browser: a provider whose every connection starts a
browser of its own (such as Browserless) marks its endpoint joinable: false.
Tools
browserTools(browser, options) gives an agent the browser as tools named after options.name. Navigating and
reading are on; the rest are each off until enabled, and each needs its permission.
| Tools | Permission | Option |
|---|---|---|
<name>.goto, <name>.back, <name>.snapshot, <name>.text |
browser:<name>:read |
always |
<name>.click, .fill, .press, .select, .hover, .scroll, .tab |
browser:<name>:act |
act: true |
<name>.screenshot |
browser:<name>:read |
screenshot: true |
<name>.evaluate |
browser:<name>:evaluate |
evaluate: true |
Navigations and actions return a fresh snapshot (snapshotAfter, true by default), so the model sees what changed.
A mistake the model can put right — an origin that is not allowed, a ref from an older snapshot, an option that is not
there — comes back as the tool's result, { error, message }, so it can try again; a failure of the browser itself
fails the call. callCostMicros charges each navigation and action to the run's budget.
import { browserPerRun, browserTools } from 'mayura/browser';
const perRun = browserPerRun(browsers);
const tools = browserTools(perRun.source, { name: 'web', act: true });
// Release each run's browser when it ends:
// defineHook({ id: 'browser.release', version: '1', stage: 'onFinally', handler: (_event, context) => perRun.release(context.runId) })A browser you already run
cdpBrowsers({ endpoint, headers }) reaches any browser with a CDP endpoint: a ws(s):// URL, or http://host:port
(asked for its WebSocket URL), such as a Chrome started with --remote-debugging-port. Each browser opened there is a
new browser context, closed on release; the browser's other tabs are left alone.
Providers
| Package | Browsers | Live view |
|---|---|---|
mayura/browser/local |
The Chrome, Chromium or Edge installed here, one process with a new profile per browser; Node | no |
cdpBrowsers (in mayura/browser) |
Any browser with a CDP endpoint, in a context of its own | no |
@mayurajs/browser-browserbase |
Browserbase sessions; captchas, recording and logs off | debugger view |
@mayurajs/browser-steel |
Steel sessions, which outlive their connection: release them | session viewer, view-only by default |
@mayurajs/browser-hyperbrowser |
Hyperbrowser sessions; proxies, stealth, captchas and recording off | live view, view-only by default |
@mayurajs/browser-browserless |
Browserless, hosted or your own: a fresh browser per connection | no |
@mayurajs/browser-kernel |
Kernel browsers, deleted soon after nothing is connected; stealth (and its captcha solver) off | live view, view-only by default |
@mayurajs/browser-browser-use |
Browser Use Cloud browsers, which outlive their connection; no proxy, no captcha solving | only with liveView: true |
@mayurajs/browser-anchor |
Anchor Browser sessions; recording and ad blocking off | live view, view-only by default |
@mayurajs/browser-browserview |
BrowserView sessions, not kept alive; stealth off; its CDP connection needs a header (give webSocket on Workers or Deno) |
watch link by default |
@mayurajs/browser-cloudflare |
Cloudflare Browser Run from outside a Worker: a session per browser, closed on release | yes (read only by default) |
@mayurajs/browser-firecrawl |
Firecrawl Interact sessions; the package also has firecrawlTools, Firecrawl's scrape, map, search, crawl and extract as permission-gated tools kept to origins |
live view, view-only by default |
localBrowsers({ channel, executablePath, headless, args }) finds Chrome (channel: 'chrome', the default) or Edge
('edge') in the usual install places, or runs executablePath. Nothing is downloaded. Each browser runs headless with
its own profile in a temporary directory, removed on release. args adds switches; ones that would open the browser
to others (remote debugging, profiles, extensions, proxies, the sandbox) are refused.
Stagehand and agent-browser
stagehandTools(browser, { model }) in @mayurajs/browser-stagehand adds Stagehand 3's natural-language tools —
extract, observe and, when enabled, act — on a Mayura browser, driving it alongside over browser.cdp. The
browser's origins, lifetime and limits still hold, including for pages Stagehand opens.
agentBrowserTools(browser) in @mayurajs/browser-agent-browser does the same with Vercel Labs' agent-browser command
line: its snapshots with @e refs and its actions, from a short list of commands, each tool behind its permission.
Web data without a browser
When a page only needs reading, not driving, two packages fetch it in the provider's own browser and give back
markdown, links, structured data or whole crawls, as permission-gated tools kept to origins like browsers are:
firecrawlToolsin@mayurajs/browser-firecrawl: Firecrawl's scrape, map, search, crawl and extract.quickActionToolsin@mayurajs/cloudflare-quick-action: Cloudflare Browser Run's markdown, links, scrape, screenshot, JSON and crawl, the rendered page also kept toorigins.
Writing a provider
A provider is { id, features: { liveView }, maxLifetimeMs, create(spec, { signal }) }. create starts a browser and
returns { id, cdp: { url, headers }, liveViewUrl, isolate, release }: cdp.url is the browser's CDP WebSocket,
release ends it (a BrowserError with reason gone counts as released), and isolate asks for a browser context
of its own in a browser shared with others. browserHttpFailure(status) and browserResponseFailure(response) turn
HTTP failures into a BrowserError without the provider's text.
browserConformance from mayura/browser/testing holds the cases every provider must pass, against the pages in
browserFixturePages served on two origins — serveBrowserFixtures() from mayura/browser/local serves them on this
machine.