Integrationsbrowsers
Firecrawl browsers and web tools
Use Firecrawl from agents with @mayurajs/browser-firecrawl: Interact sessions as browsers, and scrape, map, search, crawl and extract as tools.
- Package
- @mayurajs/browser-firecrawl
- Install
- npm install mayura @mayurajs/browser-firecrawl
- Vendor
- Firecrawl ↗
Firecrawl turns web pages into data for models, and hosts browsers you can drive.
@mayurajs/browser-firecrawl brings both to Mayura:
firecrawlBrowsers: browsers formayura/browser, as Firecrawl Interact sessions driven over CDP, with the samecreateBrowserslimits, origins and tools as the Chrome installed on your machine.firecrawlTools: Firecrawl's web data as permission-gated tools, kept to the origins you allow: scrape a page, map a site and, when enabled, search the web, crawl a site or extract structured data. See Web data without a browser.
Both talk to Firecrawl's v2 API with fetch: no Firecrawl SDK, no other dependencies, and they run on every runtime
Mayura runs on.
Install
npm install mayura @mayurajs/browser-firecrawlCredentials
Create an API key (it starts with fc-) in the Firecrawl dashboard under
API Keys. The package sends it as a bearer token. It reads nothing from the environment: pass the key in yourself,
for example from FIRECRAWL_API_KEY.
Use the tools
import { createRuntime, defineAgent, z } from 'mayura';
import { firecrawlTools } from '@mayurajs/browser-firecrawl';
const web = firecrawlTools({
apiKey: process.env.FIRECRAWL_API_KEY ?? '',
origins: ['https://docs.example.com'],
crawl: true,
maxCrawlPages: 20,
costMicros: { scrape: 1_000, map: 1_000, crawl: 20_000 },
});
const researcher = defineAgent({
id: 'docs.researcher',
version: '1',
instructions: 'Answer from the documentation at https://docs.example.com. Use web.map to find pages and web.scrape to read them.',
input: z.object({ question: z.string() }),
output: z.object({ answer: z.string() }),
tools: web,
model,
});
const runtime = createRuntime({
profile: 'ephemeral',
permissions: {
allow: ['model:openai.responses', 'tool:web.scrape', 'tool:web.map', 'tool:web.crawl', 'effect:read', 'web:web:read', 'web:web:crawl'],
},
limits: { maxCostMicros: 200_000 },
});
const outcome = await runtime.submit(researcher, { input: { question: 'How do I rotate keys?' } }).result();Every tool reads (effects: 'read'), so a call needs tool:<name>.<tool>, effect:read and the tool's permission:
| Tool | Permission | Option |
|---|---|---|
<name>.scrape: a page as markdown (the default), html, its links or a summary |
web:<name>:read |
always |
<name>.map: a site's pages (up to 500, 100 by default), optionally matching a search |
web:<name>:read |
always |
<name>.search: web search results (titles, URLs, descriptions; up to 10, 5 by default) |
web:<name>:search |
search: true |
<name>.crawl: up to maxCrawlPages pages of a site, as markdown, optionally only paths matching a regular expression |
web:<name>:crawl |
crawl: true |
<name>.extract: structured data from one page, by prompt and an optional JSON Schema |
web:<name>:extract |
extract: true |
Tool options
| Option | Default | Meaning |
|---|---|---|
apiKey |
required | Your Firecrawl API key. |
origins |
required | The sites the tools may read, as createBrowsers takes them, or 'all'. |
name |
web |
Names the tools and their permissions: lowercase letters, digits, _ and -, starting with a letter. |
search |
false |
Add <name>.search. |
crawl |
false |
Add <name>.crawl. |
extract |
false |
Add <name>.extract. |
maxPageBytes |
64 KiB | The most bytes of a page's content (or extracted data) returned to the model: 1 KiB to 4 MiB. |
maxCrawlPages |
10 |
The most pages one crawl reads: 1 to 100. |
crawlTimeoutMs |
5 minutes | The longest one crawl runs before it is cancelled: 10 s to 1 hour. |
costMicros |
0 each |
What each call costs at most, by tool (scrape, map, search, crawl, extract), charged to the run's budget. |
baseUrl |
https://api.firecrawl.dev |
Firecrawl's API. Must be https (or localhost/127.0.0.1). |
fetch |
globalThis.fetch |
For tests and proxies. |
What the tools do
- Origins. Every URL a tool reads must be within
origins; one outside them comes back as the tool's result,{ error: 'PERMISSION_DENIED', message }, so the model can choose another. Search results are not bound byorigins, but reading them is. A crawl stays on its site (no external links, no subdomains), and pages a redirect took elsewhere are left out. - Off-machine. Firecrawl fetches the pages from its own servers, with TLS checked (Firecrawl's own default skips it), main content only. Page content is the tools' result for the model to read: treat it as untrusted text.
- Limits. Content is cut at
maxPageBytesand markedtruncated: true. Extracted data larger than that comes back as aLIMIT_EXCEEDEDresult. - Crawls. A crawl polls Firecrawl every 2 seconds until it completes. One cancelled with the run, or running past
crawlTimeoutMs, is cancelled at Firecrawl too, so it stops spending. - Extraction uses Firecrawl's JSON format on one page, with Firecrawl's model.
- Costs.
costMicroscharges each call to the run's budget; Firecrawl bills credits on its side.
Use the browsers
import { browserPerRun, browserTools, createBrowsers } from 'mayura/browser';
import { firecrawlBrowsers } from '@mayurajs/browser-firecrawl';
const browsers = createBrowsers(firecrawlBrowsers({ apiKey: process.env.FIRECRAWL_API_KEY ?? '' }), {
maxBrowsers: 2,
maxLifetimeMs: 10 * 60_000,
origins: ['https://example.com', 'https://*.example.com'],
});
const browser = await browsers.open({ lifetimeMs: 5 * 60_000 });
try {
console.log(browser.liveViewUrl); // view only: treat it as a secret
await browser.goto('https://example.com/');
console.log((await browser.snapshot()).text);
} finally {
await browser.release();
}
// Or give each run a browser as tools; see the browser guide for the grants they need.
const perRun = browserPerRun(browsers);
const tools = browserTools(perRun.source, { name: 'site', act: true });Browser options
| Option | Default | Meaning |
|---|---|---|
apiKey |
required | Your Firecrawl API key. |
liveView |
'view' |
'view' gives a view-only liveViewUrl, 'interact' Firecrawl's interactive view, whose holder can use the browser, false gives none. |
activityTimeoutSeconds |
300 |
End a browser after this long without activity: 10 to 3,600. |
maxLifetimeMs |
1 hour | The longest lifetime: 30 s to 1 hour. |
baseUrl |
https://api.firecrawl.dev |
Firecrawl's API. Must be https (or localhost/127.0.0.1). |
fetch |
globalThis.fetch |
For tests and proxies. |
Sessions
- Each browser is one Interact session, created when you open it and deleted when you release it. Firecrawl ends it at
its lifetime (given as its
ttlin seconds, at least 30) or afteractivityTimeoutSecondswithout activity. release()deletes the session. One deleted already answers 404 (gone), whichcreateBrowserscounts as released.- The session is not given your viewport or labels;
createBrowserssets the viewport over CDP. - The CDP endpoint must be
wss://(plainws://only on this machine). Treat it as a secret, as Firecrawl's docs differ on what it carries: Mayura never logs it. Each session is yours alone, sobrowser.cdpis there for another client, such as Stagehand, to drive it alongside. - Origins are enforced inside the browser: Mayura intercepts every request the session makes over CDP and blocks those to origins you did not allow, as the guide describes. Should a connection not allow that interception, opening the browser fails rather than running unchecked.
Errors
Browsers fail with a BrowserError whose reason is mapped from Firecrawl's HTTP status, without anything Firecrawl
wrote: authentication (401, 403), quota (402), rate_limited (429), gone (404, 410), timeout (408, 504),
unavailable (5xx), rejected (other refusals), and invalid_response when a reply lacks a session id or a secure
CDP URL.
Tool calls fail the call, again without Firecrawl's text, except for mistakes the model can put right:
| Code | When | Comes back as |
|---|---|---|
PERMISSION_DENIED |
A URL is outside origins. |
the tool's result |
INVALID_INPUT |
A URL is not http(s). | the tool's result |
LIMIT_EXCEEDED |
Extracted data is larger than maxPageBytes. |
the tool's result |
BUDGET_EXCEEDED |
Firecrawl answered 402: your credits ran out. | a failed call |
TOOL_FAILED |
Firecrawl refused the request, did not succeed, or a crawl failed or was cancelled. | a failed call |
TIMEOUT |
A crawl ran past crawlTimeoutMs; it is cancelled. |
a failed call |
CANCELLED |
The run was cancelled during a crawl; it is cancelled. | a failed call |
Configuration mistakes, such as missing origins, a tool name with spaces or maxCrawlPages over 100, fail with
INVALID_CONFIG when you call firecrawlTools() or firecrawlBrowsers(), before anything is sent.
How it is tested
- Unit tests run both against a fake Firecrawl API that checks every request: the tools' permissions and costs, TLS
checked, truncation, origins refused as results, map and search bodies, crawls polled, kept to their site and
cancelled when cancelled, timed out or failed, extraction bounded, a call blocked without its permission, and the
Interact session's
ttl, live views and delete. - The browser conformance suite runs against sessions served by the Chrome or Edge installed on the test machine behind that fake API.
scripts/live/browser-firecrawl.mjsruns the suite's public-page checks on a real Interact session (live view, opening and snapshotting a page, origins enforced, following a link and a screenshot): all passed on October 6, 2026. The same script also calls scrape, map and extract against Firecrawl.
Related
- Browsers: limits, origins, per-run browsers and tools.
- Cloudflare quick actions: Browser Run's markdown, links, scrape, screenshot, JSON and crawl as tools.
- Permissions: how
tool:,effect:and capability grants combine.