Give an AI agent a browser and it can read the web. But it reads the web from exactly one address — whichever machine it happens to be running on.
That's fine until the thing you're asking about is country-specific. Public prices, currency, regional availability, localized search results — all of it comes back through a vantage point the agent never chose and can't move. The agent doesn't error. It just confidently reports the wrong country's answer.
I wanted an agent that could say "check this from Germany, now from the UK, now from the US" — and then prove it actually did. So I built an MCP server for it, and this post is the walkthrough: we'll build a small country-by-country price index, driven entirely by tool calls.
The gap this fills
If you've used Microsoft's Playwright MCP, you know it's a genuinely good tool — it drives a browser properly. But its proxy is a launch flag: you set it when the server starts, and every call in that session goes out the same door. It wasn't built for per-task geography, and that's a reasonable design choice.
Hosted "scraping" MCPs solve geography by routing through their own cloud and handing you JSON. The geography is real, but it's opaque — you don't pick the exit, and you can't verify where a request actually landed.
What I wanted sat in between: a local browser, a residential exit, and the country as an argument on the tool call — plus a way for the agent to check the exit before trusting the page.
Setup (2 minutes)
It's an npm package. Add it to your MCP client config — claude_desktop_config.json for Claude Desktop, ~/.cursor/mcp.json for Cursor:
{
"mcpServers": {
"aethyn-browser": {
"command": "npx",
"args": ["-y", "aethyn-browser-mcp"],
"env": {
"AETHYN_USERNAME": "aethyn-XXXXX",
"AETHYN_PASSWORD": "your-proxy-password",
"AETHYN_DEFAULT_TIER": "premium"
}
}
}
}
Chromium auto-installs on first run (~170 MB, once). The browser runs on your machine — nothing is hosted for you, and your credentials never leave your box.
Restart the client and the agent has 10 tools, all prefixed aethyn_:
| Tool | What it does |
|---|---|
aethyn_launch_browser |
Open a proxied session in a chosen country |
aethyn_navigate |
Go to a URL |
aethyn_get_content |
Page content as text / html / markdown |
aethyn_snapshot |
Accessibility tree with [ref=eNN] handles |
aethyn_click / aethyn_type
|
Interact by ref or selector |
aethyn_check_exit_ip |
Verify the exit through the session's proxy |
aethyn_new_identity |
Rotate to a fresh exit IP, clear cookies |
aethyn_close |
Close the session |
aethyn_list_countries |
Discover exit options at runtime |
The build: a regional price index
The task: read a public product/pricing page from three countries and put the numbers side by side. Every step below is a real tool call the agent makes on its own.
Step 1 — let the agent discover its options
Rather than hard-coding a country list, the agent asks:
{ "tool": "aethyn_list_countries", "args": { "with_cities": false } }
This matters more than it looks: the agent can now plan a loop over countries it knows are actually available, instead of guessing and failing.
Step 2 — one sticky session per country
For each country, launch a session. The session argument is the important one — it's your task id, and reusing it pins the same exit IP for the whole task:
{
"tool": "aethyn_launch_browser",
"args": {
"country": "de",
"session": "price_idx_de",
"lifetime_min": 10,
"tier": "premium"
}
}
Note the token is lifetime_min (1–1440 minutes, default 10), not "ttl". It's how long that exit stays pinned to your session. Launch returns a session_id, which every other tool takes.
Step 3 — verify before you trust (the step people skip)
This is the one I'd argue is the whole point:
{ "tool": "aethyn_check_exit_ip", "args": { "session_id": "..." } }
{ "ip": "…", "country": "DE", "city": "…", "org": "…", "is_residential": true }
It runs the check through the session's own proxy, so it tells you where the page you're about to load will actually come from. If the agent asked for de and the exit says something else, it can rotate and re-check instead of quietly reporting a German price it never saw.
(Fair warning: is_residential is a best-effort heuristic derived from the ASN/org — treat it as a signal, not a guarantee.)
Step 4 — read the page
{ "tool": "aethyn_navigate", "args": { "session_id": "...", "url": "https://example-store.com/pricing" } }
{ "tool": "aethyn_get_content", "args": { "session_id": "...", "format": "markdown" } }
markdown is usually the right format for feeding an LLM — it's the page, cleaned, without the script/style noise eating your context window.
Step 5 — close, repeat, aggregate
aethyn_close the session, then run the same sequence for uk and us with their own session ids. Because each country gets its own session, the identities never cross-contaminate.
What you actually type
The nice part is you don't orchestrate any of this by hand. You say:
"Using the aethyn-browser tools: list the available countries, then for de, uk, and us, open a sticky session, verify the exit IP is in the right country, load
https://example-store.com/pricing, and pull the plan prices. Close each session when you're done. Put the results in a table with the currency, and flag any country where the exit IP didn't match what you asked for."
…and the agent runs the loop, checks its own work, and hands you the table. From there it's one more step to a chart or a nightly pipeline.
Two gotchas worth your time
1. The 407 that looks like a proxy failure but isn't.
AETHYN_DEFAULT_TIER defaults to premium, which uses HTTP port 2099. Elite uses 5499. If your account is Elite and you leave the default alone, the browser launches perfectly fine and then the proxy rejects auth with a 407. It looks like broken credentials; it's a port mismatch. Fix: set AETHYN_DEFAULT_TIER=elite, or pass tier: "elite" on the call.
Also note city and state targeting are Elite-only (port 5499). Country works on both tiers.
2. HTTP, not SOCKS5.
The server is HTTP-only by design — Chromium can't authenticate SOCKS5 proxies. If you've been burned by that before, this is why.
Bring your own proxy
It defaults to Aethyn, but it isn't locked to it. Point it at any HTTP proxy with PROXY_HOST, PROXY_PORT, PROXY_USERNAME, PROXY_PASSWORD, and a PROXY_USERNAME_TEMPLATE describing how your provider encodes targeting in the username:
PROXY_USERNAME_TEMPLATE="{username}-country-{country}[-city-{city}]-session-{session}-lifetime-{lifetime}"
[optional] segments drop out when empty. For a fixed proxy with no geo targeting at all, the template is just {username}.
Where this stays honest
Worth being direct about the boundaries, because the "agents + browsers" space is full of overclaiming:
-
Public data only. Respect
robots.txt, rate limits, and each site's terms. "The agent can reach it" isn't "the agent should." - There is no CAPTCHA-solving tool, and none is planned. If a challenge fires, that's a signal to back off — not to fight it.
- No login/credential-wall automation.
-
Nothing here auto-detects a block. If a page comes back looking wrong, the agent reads the content, judges it, and calls
aethyn_new_identity. That's a deliberate choice — I'd rather the model make that call visibly than have the tool silently paper over it. - A residential exit fixes your network vantage point. It doesn't fix reckless pacing or a client that looks robotic in every other way.
Try it
npx -y aethyn-browser-mcp
Drop the config block in your MCP client, and your agent can pick a country on its very next tool call.
- Full walkthrough + runnable examples: docs
- The deeper writeup: Residential proxy browser MCP for AI agents
- Source (MIT): github.com/aethynio/aethyn-browser-mcp · npm · Glama
- Free account, no card: aethyn.io/signup
If you build something with it — or it breaks in an interesting way — I'd like to hear about it.
Top comments (0)