Browserless MCP Server
MCP (Model Context Protocol) server for Browserless.io — expose the Browserless smart scraper API to LLM clients like Claude Desktop, Cursor, VS Code, and Windsurf.
Quick Start
Get an API token from browserless.io (free tier available), then point your MCP client at the hosted server:
{
"mcpServers": {
"browserless": {
"url": "https://mcp.browserless.io/mcp?token=your-token-here"
}
}
}
No local install — see Configuration for per-client snippets.
Tools
Native file staging (disabled by default)
The full HTTP surface advertises browserless_stage_file with the native
openai/fileParams file descriptor. It is absent from stdio and the compliant
surface. Native downloads are currently disabled: the production hostname
allowlist is empty, so every staging request fails before DNS or network access.
Tool inputs and environment variables cannot enable it.
The implementation accepts file.download_url and file.file_id, with optional
file.mime_type and file.file_name. Once trusted hosts are verified and enabled
in code, successful staging returns only { handle, filename, mimeType, size }.
Pass the handle to browserless_agent's uploadFile; handles belong to the
authenticated token, remain reusable for 15 minutes, and are not tied to an MCP
transport. Stored MIME is inherited unless the upload supplies an override.
Controlled tests do not establish native ChatGPT attachment acceptance. Live
attachment acceptance and handle resolution across deployed replicas remain
unverified; the file store is process-local. Existing multipart /upload and
inline-content uploads are unchanged.
Skills
The server ships with a built-in library of Skills — on-demand recipes the agent can load to handle tricky page mechanics. Skills auto-inject into browserless_agent responses when their triggers fire (e.g. the agent hits a cookie banner), and can also be loaded manually via the browserless_skill tool.
Load a skill explicitly:
{
"method": "tools/call",
"params": {
"name": "browserless_skill",
"arguments": { "id": "cookie-consent" },
},
}
Built-in proxy (browserless_agent)
Pass a top-level proxy object on browserless_agent to route the session through datacenter or residential IPs. Datacenter is cheaper per MB; residential is less likely to be blocked.
{
"method": "tools/call",
"params": {
"name": "browserless_agent",
"arguments": {
"method": "goto",
"params": { "url": "https://example.com" },
"proxy": {
"proxy": "residential",
"proxyCountry": "us",
"proxySticky": true,
},
},
},
}
Note: Geo, sticky, and locale options require either a built-in
proxytier orexternalProxyServer;proxyPresetrequiresproxy: "residential". The MCP rejects unsupported combinations instead of letting the API silently ignore them.
The proxy object is read once at session creation. To change it, call close and start a new session — the agent client keys sessions on the proxy fingerprint, so passing a different config will land on a fresh WebSocket.
OS persona (browserless_agent)
Agent sessions can opt into a coherent OS persona with top-level creation options:
Set persona options on the first call before navigation and reuse the returned
sessionId afterward. Persona is fixed for the life of that browser session;
close it before selecting a different persona.
Configuration
The server is hosted at https://mcp.browserless.io/mcp. Authenticate via headers (preferred) or a ?token= query parameter.
Installing via an AI agent? See install.md for agent-readable setup instructions.
Using headers (recommended for clients that support them):
{
"mcpServers": {
"browserless": {
"url": "https://mcp.browserless.io/mcp",
"headers": {
"Authorization": "Bearer your-token-here"
}
}
}
}
Using URL query parameters (for clients like Claude.ai custom connectors that only accept a URL):
https://mcp.browserless.io/mcp?token=your-token-here
To connect to a specific Browserless regional endpoint, add the x-browserless-api-url header or the browserlessUrl query parameter:
{
"mcpServers": {
"browserless": {
"url": "https://mcp.browserless.io/mcp",
"headers": {
"Authorization": "Bearer your-token-here",
"x-browserless-api-url": "https://production-lon.browserless.io"
}
}
}
}
https://mcp.browserless.io/mcp?token=your-token-here&browserlessUrl=https://production-lon.browserless.io
When both headers and query parameters are present, headers take precedence.
API URL overrides are limited to browserless.io, its subdomains, the configured BROWSERLESS_API_URL origin (same scheme, hostname, and port), and hosts listed in MCP_ALLOWED_API_URL_HOSTS. Paths are allowed, but credentials, query strings, and fragments (including bare ? or #) are not. Without an override, the operator-configured URL is used unchanged.
Claude Desktop
Add to your claude_desktop_config.json:
{
"mcpServers": {
"browserless": {
"url": "https://mcp.browserless.io/mcp?token=your-token-here"
}
}
}
Cursor
Add to your Cursor MCP settings:
{
"mcpServers": {
"browserless": {
"url": "https://mcp.browserless.io/mcp?token=your-token-here"
}
}
}
VS Code
Add to your VS Code settings (settings.json):
{
"mcp": {
"servers": {
"browserless": {
"url": "https://mcp.browserless.io/mcp",
"headers": {
"Authorization": "Bearer your-token-here"
}
}
}
}
}
Windsurf
Add to your Windsurf MCP configuration:
{
"mcpServers": {
"browserless": {
"url": "https://mcp.browserless.io/mcp?token=your-token-here"
}
}
}
Self-Hosting
The server can also be run locally — useful for air-gapped deployments or pointing at a self-hosted Browserless instance. Clone this repo and build the Docker image:
docker build -f docker/Dockerfile -t browserless-mcp .
docker run \
-e BROWSERLESS_TOKEN=your-token \
-e BROWSERLESS_API_URL=https://your-browserless-instance.example.com \
-p 8080:8080 \
browserless-mcp
Then point your MCP client at http://localhost:8080/mcp using the same header/query-parameter auth as above.
Self-hosted environment variables
Hosted OpenTelemetry
On httpStream, set OTEL_ENABLED=true and OTEL_EXPORTER_OTLP_ENDPOINT
to a trusted collector's base URL. Metrics and stderr logs are exported over
OTLP HTTP/protobuf; tracing is disabled. stdio never starts this SDK.
OTEL_METRIC_EXPORT_INTERVAL controls the export interval in milliseconds
(default 60000, minimum 1000).
NodeSDK manages the providers and standard Node runtime instrumentation emits
nodejs.* and v8js.* metrics. Application session, tool, account and Redis
metrics remain under browserless.mcp.*. All duration metrics use seconds
(s), memory uses bytes (By), and request counters use {request};
application callers still supply
milliseconds, converted once by the metric recorders.
browserless.mcp.auth.duration and .auth.in_flight cover POST authentication,
not whole request execution. browserless.mcp.session.cleanup.age covers tracked
MCP idle reaps and explicit/idle/cap agent cleanup, not every session close.
Sweep-discovered MCP age starts at discovery; agent age resets on reconnect.
browserless.mcp.accounts.active counts distinct token hashes per collection
window. Top-account counts are an INFO log with body event
browserless.mcp.account.window, active_accounts, and top_accounts (at most
20 {hash, count} entries), not persistent per-account metric series.
An idle window emits no account log. Stderr text is INFO, console.warn is WARN,
and arguments containing an Error are ERROR; stderr records carry
log.iostream=stderr. OTEL_SDK_DISABLED=true also disables hosted telemetry.
For dashboards based on the initial telemetry PR: replace custom event-loop
metrics with nodejs.eventloop.*, GC pauses with v8js.gc.duration, and active
resources with v8js.resource.active. Drop _ms from tool/upstream/Redis
duration names, use the auth and cleanup names above, and divide millisecond
thresholds by 1000. Replace account.requests queries with the window logs.
Duration histogram boundaries are also in seconds, covering sub-second Redis
operations through two-hour sessions. Amplitude event fields and timeout
configuration remain in milliseconds.
Skill retrieval diagnostics
Skill Retrieval Completed emits once per actual remote skill fetch through
the existing analytics queue (when ANALYTICS_ENABLED, SQS_QUEUE_URL, and
SQS_REGION are configured). It is not duplicated through the SDK's
AMPLITUDE_API_KEY transport. Cache hits and concurrent callers sharing a fetch
do not emit another completion. Failed retrievals remain retryable on the next
call; this instrumentation adds no retries.
Fields are result=hit|miss|error, normalized domain, UUID request_id,
source, attempt, integer duration_ms, stage=fetch|decode|validate, and
available http_status. skill_count appears only on valid responses: positive
for hits, zero for misses. Errors carry error_category=timeout|network_error|http_error|invalid_json|invalid_shape.
Sources are cli_agent, script_builder, autologin, agent_run, mcp_client,
or unknown. Each fetch currently has attempt=1; no run identifier is
available at these call sites, so run_id is omitted.
Domains outside the bounded hostname format become invalid without dropping
the completion from the denominator.
Set OTEL_EXPORTER_OTLP_LOGS_ENDPOINT to a trusted collector's full /v1/logs
URL to export matching skill.retrieval.failed WARN records as OTLP/HTTP JSON.
The default is disabled. Exports have a one-second deadline, at most 16 in-flight
requests, and no retry. Caught queue/skill analytics errors produce
skill.telemetry.delivery_failed with originating_event and the fixed
diagnostic category delivery_error, at most once per minute per process.
Exporter failures are swallowed without recursively reporting themselves.
No new log contains tokens, prompts, full URLs, response bodies, or recipe text.
The queue retains its existing authentication field, separately from log fields.
Example failure attributes:
{
"event.name": "skill.retrieval.failed",
"result": "error",
"domain": "shop.example",
"request_id": "416e0409-25e2-4399-a3fa-6939f43a75e0",
"source": "mcp_client",
"attempt": 1,
"stage": "fetch",
"error_category": "http_error",
"http_status": 429,
"duration_ms": 17
}
Retrieval error rate is error completions / all completions. Hit rate is hit
completions / valid completions. Do not add the separate server Skill Lookup
events to either denominator. Tests use local/mock sinks; a configured exporter
or console message is not proof of remote receipt.
Failure diagnostics
MCP Tool Request retains analytics_version=2, the existing coarse
error_category, status_code, timing and tool-specific properties. These
additive diagnostic fields are failure-only; a successful retry has none of them.
For browserless_function, browserless_export and browserless_smartscraper,
a failed result's recorded status_code sets the reason: 400 from the function
tool → script_error (source script), 401 → unauthorized, 403 → forbidden,
404 → not_found, 408 → timeout, 429 → rate_limited, 5xx → server_error,
anything else → unknown. Function and export statuses are the Browserless API's
(error_status_origin=api); smart scraper statuses are the target website's
(error_status_origin=target_website, error_source=target_website). Export 400
remains unknown, with its status retained.
status_code keeps its original tool-specific meaning; the new status fields
do not replace it or turn successful target-page HTTP responses into failures.
HTTP failures retain API response status even when thrown. Codes are retained
when already available in structured errors or the JSON body read by the existing
4xx error handler; diagnostics do not read additional bodies on 5xx failures.
An unsuccessful search without structured evidence reports error_reason=unknown
and error_message="Unclassified search failure.". Its legacy user_error
category remains for chart compatibility, not as evidence of caller fault.
Example breakdowns: filter success=false and group by tool → error_reason;
for agent calls, group by failed_method → error_reason; for HTTP failures,
group by error_status_origin → error_status_code. Missing fields in older
events mean unavailable instrumentation, not an unknown failure. There is no
historical backfill. Verify representative received events after deployment
before treating these properties as available in production.
Live URL instructions
browserless_live_url creates viewer links over REST for an existing browser.
Set action: "create", browserId, and interactable: true for a human handoff:
{
"action": "create",
"browserId": "running-browser-id",
"interactable": true,
"instructions": {
"layout": "panel",
"format": "markdown",
"title": "Complete sign in",
"summary": "Enter your **verification code**.",
"steps": ["Open your inbox", "Enter the code"],
"currentStep": 2
},
"doneLabel": "Continue",
"failLabel": "Need help",
"requesterName": "Support"
}
Plain string instructions still work. With a string, use top-level
instructionsTitle, steps, currentStep, and instructionsFormat.
With an object, put these values inside instructions as title, steps,
currentStep, and format. Button labels and requester name stay top-level.
Titles allow 80 characters; up to 10 non-empty steps allow 300 characters each;
button labels allow 40 characters; requester names allow 60 characters.
currentStep is one-based and must refer to a supplied step. Summary text is
trimmed and capped at 2048 characters by the server. Steps-only cards are
supported. Markdown links must use HTTP or HTTPS. The server validates
cross-field constraints; deployments must support rich REST instructions.
MCP Resources
MCP Prompts
Development
npm install
npm run build
npm test
npm run coverage
Tests
The test suite uses Mocha with Chai and Sinon. Specs live alongside the code in test/ (test/lib/, test/tools/, test/prompts/, test/resources/, test/integration/) and run against the compiled output in build/.
npm test— compiles TypeScript and runs every*.spec.jsunderbuild/test/. No external services orBROWSERLESS_TOKENare required; the API client is stubbed.npm run coverage— runs the suite under c8 with the thresholds configured inpackage.json(lines ≥ 80%, branches ≥ 70%, functions ≥ 80%).
Tests run automatically on every pull request via the Test workflow on Node 24. PRs must keep the suite green before they can merge.
API Token
Get your API token at browserless.io. The token authenticates all requests to the Browserless API.
License
SSPL-1.0