Agentic coding
There are two ways to put an AI coding agent to work on browserscale, and they are meant to be used together. The MCP server hands the agent a live cloud browser it can look at and poke at, one call at a time. The CLI hands it a project to write into — a Go module that already compiles, already runs workers in parallel and already knows how to rent a browser.
Used on its own, MCP makes the agent a pair of hands: it clicks for you, you pay tokens for every step, and when the conversation ends nothing is left. Used together with the CLI, the agent explores with MCP and then writes what it learned into code that runs without a model in the loop. That is the workflow this page describes.
- MCP endpoint:
https://mcp.browserscale.cloud/mcp(Streamable HTTP). Authenticate with anAuthorization: Bearer <your-api-key>header. https://mcp.browserscale.cloud/— without/mcp— is a human-readable list of the tools. It is not the endpoint; pointing a client at it will not work.- The API key belongs in your client config as a header. It is never a tool argument, so it never enters the model's context.
- CLI:
go install github.com/browserscale/browserscale-cli@latest, thenbrowserscale-cli initscaffolds a runnable Go module. - Open the generated folder in Cursor, Codex, Claude or any other coding agent: it ships an
AGENTS.mdand the whole SDK reference offline, so the agent works from real signatures instead of guessing. browserscale-cli devrebuilds, restarts and streams the logs from one command — that is the loop the agent iterates in.
The MCP server
browserscale runs a hosted MCP server, so there is nothing to install and nothing to keep running.
| Endpoint | https://mcp.browserscale.cloud/mcp |
| Transport | Streamable HTTP (a remote MCP server, not stdio) |
| Auth | Authorization: Bearer <your-api-key> |
| Tool overview | mcp.browserscale.cloud — for humans, not for clients |
Connecting a client
Nearly every MCP client wants the same three things: a name, the URL and a map of headers. The file it lives in and the exact field names differ per client, so check yours — but the shape is this:
{
"mcpServers": {
"browserscale": {
"url": "https://mcp.browserscale.cloud/mcp",
"headers": {
"Authorization": "Bearer sk_your_api_key"
}
}
}
}
Two things worth being precise about:
- Use the
/mcppath. The bare host serves a page that lists every tool with its full description. That page exists so you (or an agent reading the web) can see what the server offers without connecting. A client pointed athttps://mcp.browserscale.cloud/will not speak MCP. - The key travels as a header, never as an argument. No tool takes an
apiKeyparameter; the server reads it off the request. That is what keeps your key out of the model's context window and out of the conversation log.
How sessions work
The server is stateless — it holds no session for you between calls, which is why the session id travels with every call:
rentstarts a browser and returns asessionIdand agrpcUrl.- Every action tool takes that
sessionId(and thegrpcUrlwhen your client is not pointed at a default one). stopreleases the browser. It needs only thesessionId.
Sessions also expire on their own after rentSeconds, so a forgotten one
does not bill forever — but calling stop when the flow is done frees the
browser and its proxy immediately.
Nothing says the session has to have been rented over MCP. Any session on
your account can be driven this way, which is what makes live debugging
work: log sessionId and grpcUrl from your code, and the agent can attach
to a session your program is running right now — or to the half-finished
state a failed run left behind — and inspect or drive the real page instead
of reconstructing what happened from a stack trace.
What it is good at
Use MCP for the parts where looking beats guessing:
- Walking an unfamiliar flow once, by hand, before writing any code.
- Proving a selector resolves to exactly one element —
evaluatethe expression you are about to bake intoclick, and check the count. - Attaching to a live run to see what the code is actually looking at, and
taking a
screenshotwhen the logs do not explain it. - Reading the page with
observe, which is the same viewGetObservationreturns in the SDK.
Every tool maps one-to-one onto an SDK method, so a call that worked over MCP is already a line of the script you are about to write. What MCP is not good for is running the work: that is what the CLI part is for.
The CLI
browserscale-cli is a scaffolder and a dev loop. It writes the boilerplate
every browser bot needs before the interesting part starts, so your agent
begins from a project that runs rather than an empty folder.
Install
go install github.com/browserscale/browserscale-cli@latest
That needs a Go toolchain (1.25 or newer) and puts a browserscale-cli
binary in your GOPATH/bin. If you would rather type less, alias it:
alias browserscale='browserscale-cli'
Scaffold a module with init
browserscale-cli init
Run without flags, it asks a few questions and every answer has a default, so you can hold enter:
| Question | Default | What it decides |
|---|---|---|
| Module name | my-module | The name, and the directory it is written to. |
| Go module path | the name | What goes into go.mod. |
| Lifecycle | one-shot | The shape of the run loop (see below). |
| Parallelism | configurable | Whether worker count is fixed, single, or a runtime flag. |
| Workers | 4 | Only asked when parallelism is fixed. |
The lifecycle is the one answer worth thinking about, because it decides
what run.go looks like:
| Lifecycle | The generated loop |
|---|---|
one-shot | Runs once, then exits. |
queue | Drains an input list across workers — the usual choice for a list of accounts or URLs. |
repeat | Runs until N successes, retrying failures. |
continuous | Loops forever until you stop it. |
scheduled | Runs on a fixed interval. |
custom | A minimal Run() you write yourself. |
Parallelism is single (one worker), fixed (a number baked in now) or
configurable (a -threads flag at runtime, which is the default).
To skip the wizard entirely — handy when an agent runs the command — pass
the answers as flags and add -yes:
browserscale-cli init -name orders-bot -kind queue -threading fixed -fixed-threads 8 -yes
The target directory has to be empty or not exist yet, so init can never
overwrite work you already have.
What you get
orders-bot/
main.go hands off to the kit's harness
module.go config schema + worker count
flow.go thin dispatcher: picks which flow to run
register.go a complete, working browser flow
run.go the lifecycle loop you chose
rent.go proxy pool + rent-with-backoff
browserscale.yaml your init answers, in writing
AGENTS.md the agent's guide to this project
README.md
docs/ the whole SDK reference, offline
data/proxies.txt one proxy per line
A few of these matter more than the others:
register.go(ortask.go, depending on lifecycle) holds a complete worked flow against the browserscale playground. It runs before you change anything, which means your agent has a working example to pattern-match against rather than a blank function.AGENTS.mdis a written playbook: the file layout, the SDK and kit APIs with copy-paste snippets, proven patterns, and an explicit list of things not to do (noel.click()throughevaluate, no inventing methods that do not exist).docs/is this documentation, bundled as Markdown. The agent greps it instead of guessing an API — which is the single biggest difference between a flow that works and one that hallucinates half a method name.rent.goalready retries the errors worth retrying, so a busy pool or a brief credit dip does not kill the run.
The generated module pulls in
browserscale-go for the
browser and browserscale-kit for everything around it: the worker
harness, a config form, work queues, proxy pools, a SQLite store for
de-duplication and resume, an IMAP fetcher for one-time codes, and a
logger. Neither version is pinned — go mod tidy resolves both to their
latest release.
Config
You don't have to get the project running before handing it over. The
config schema is declared in module.go, and the module ships its own
configurator that saves the values to data/config.json:
cd orders-bot
go mod tidy
go run .
Your agent can drive that, and extend the schema when the flow needs
another field. Two values can only come from you, because nobody else has
them: your API key, and at least one entry in data/proxies.txt —
every session is proxied, and the exit IP, the geolocation and the timezone
all follow from it.
Open it in your agent
cursor orders-bot
codex orders-bot
claude
Describe the job in plain language — "log in with every account in
data/accounts.csv and export each order to a file" — and let it work. The
agent has a running example, the offline reference and AGENTS.md, which
covers the file layout, the SDK and kit APIs, and how to iterate. With an
MCP client connected it can also look at the real page while it writes, and
attach to its own runs when something doesn't hold.
Iterate with dev
browserscale-cli dev
One command builds the module, kills the previous run, starts the new binary and streams stdout and stderr until you press Ctrl+C. Run it again and it rebuilds and restarts. This is what you want an agent using: it can start its own run and read what actually happened, instead of hand-rolling a build-and-kill loop mid-session.
| Flag | What it does |
|---|---|
-dir <path> | The module directory. Defaults to the current one. |
-run <name> | Write to a fixed runs/<name>/ folder instead of a timestamped one. |
dev starts the module non-interactively and headless, so it runs against
whatever is saved in data/config.json rather than opening the
configurator.
Gotchas
https://mcp.browserscale.cloud/is not the endpoint. The endpoint ishttps://mcp.browserscale.cloud/mcp. The bare host is a human-readable tool overview.rentneeds a proxy. The exit IP comes from the upstream proxy you pass, and country and timezone are derived from it server-side — so leavecountryCodeandtimezoneempty unless you deliberately want to override what the proxy implies.stopwhat yourent. Sessions expire afterrentSeconds, but an abandoned one holds a browser and a proxy until then.initrefuses a non-empty directory. That is deliberate; point-dirsomewhere new.devruns against the saved config. It starts the module non-interactively, sodata/config.jsonhas to exist — the configurator (go run .) is what writes it.- The CLI does not configure MCP for you. The MCP server is set up in
your coding agent's own config; the API key your module uses lives in
data/config.json. They are two separate places, and CLI-side auth and MCP wiring are still on the roadmap. - Attaching needs the
grpcUrl, not just the id. Log both from your code if you want the agent to reach a running session; the id alone is only enough forstop.
- Reading the page —
GetObservation, the SDK method behind theobservetool. - Targeting elements — how to pick a target that still works tomorrow, which is what you want the agent baking into code.
- Cookies, storage & sessions — carrying a logged-in identity between runs.
- mcp.browserscale.cloud — the full tool list with descriptions.
- browserscale-cli on GitHub — source and issues.