{"slug": "otto-give-your-ai-agent-a-real-browser-without-running-a-browser-farm", "title": "OTTO: Give your AI agent a real browser without running a browser farm", "summary": "A developer built Otto, an open-source tool that lets AI agents control real Chrome tabs from a CLI or LLM over a secure WebSocket relay, eliminating the need for headless browser farms or cloud-browser rentals. Otto splits responsibilities between deterministic code for browser mechanics and the model for high-level decisions, reducing token costs and latency. The tool includes an MCP server for agent integration and supports site-scoped command bundles.", "body_md": "Otto controls real Chrome tabs from your CLI or your LLM over a secure relay — no headless farm, no cloud-browser rental. Here's how it works and why interaction should be code, not tokens:\n\nEvery time I needed an agent (or a test, or a monitor) to touch a real website, I hit the same wall: the browser. Not the automation logic — the *infrastructure* under it. You end up choosing between two bad options, and I got tired of both, so I built a third. It's open source and it's called **Otto**.\n\n**Option one: run a headless farm.** Spin up Docker, manage a pool of Puppeteer/Playwright instances, keep them patched, scale them, and pray your IPs don't get flagged. It works, but it's a standing operational cost, and headless Chrome is *not* the same as the browser you actually use. Different fingerprint, different behavior, no logged-in session. Plenty of sites quietly serve headless traffic a worse — or broken — experience.\n\n**Option two: rent cloud browsers.** Pay a per-session or per-minute fee to a hosted-browser service. Convenient until the bill scales with your usage, and you're still not running in *your* real session with *your* cookies.\n\nWhat I actually wanted was dumb and simple: drive the real Chrome tab already sitting on my machine — logged in, warmed up, indistinguishable from me — from a script or an agent running somewhere else. No farm. No rental. Just the tab.\n\nOtto is three pieces connected over authenticated WebSocket:\n\n```\nController (otto CLI / script)\n        |  WebSocket (authenticated)\n        v\n  Relay daemon  (:8787)\n        |  WebSocket (authenticated, node)\n        v\n  Extension node (Chrome)\n        |  chrome.tabs / chrome.scripting\n        v\n  Browser tab (managed, site-scoped)\n```\n\nA lightweight Chrome **extension** turns a live tab into a *node*. A **relay daemon** authenticates connections, authorizes commands by scope, and routes them to the right node. A **controller** — the `otto`\n\nCLI, a script, or an agent — sends command envelopes to the relay.\n\nThe nice consequence: the controller and the browser don't have to live on the same machine. Your agent can run on a server and drive a Chrome tab open on your laptop, because the relay handles routing and auth in between. Execution is serial per tab and parallel across tabs, with replay protection on every command.\n\nThis is the design decision I care about most, so let me be blunt about it.\n\nIf you wire an LLM directly to a browser and let it perform *every* interaction — \"find the button,\" \"click at these coordinates,\" \"type this character\" — you pay for all of that in tokens and latency. The model becomes a very expensive mouse.\n\nOtto splits the responsibilities. **Deterministic code** handles the mechanics: opening tabs, navigating, querying the DOM, extracting text/markdown/clean HTML, screenshots, clicking, typing, intercepting network traffic. The **model** only decides *what to do next*. It calls a command, gets a structured result, and reasons about strategy — not about pixel coordinates.\n\nConcretely, the surface looks like primitives plus site-scoped commands:\n\n```\n# universal primitives\notto extract-content https://news.ycombinator.com   # → markdown\n# tab open / close / navigate / query\n# extract_text / extract_markdown / extract_clean_html / screenshot\n\n# site-scoped command bundles, e.g.\notto commands list --site reddit.com\n# → getPosts, getUserInfo, sendChatMessage, commentOnPost, ...\n```\n\nThose site command bundles are versioned, shareable, and testable — you author a reusable command for a domain once instead of re-deriving the DOM dance on every run. And because there's an **MCP server**, an agent (Claude, an OpenAI tool-use loop, whatever speaks MCP) can call these directly as tools.\n\nRequirements: Node.js 20+, Chrome, npm.\n\n```\n# 1. install the CLI\nnpm install -g @telepat/otto\n\n# 2. guided setup (prints the extension path to load)\notto setup\n\n# 3. load the unpacked extension into Chrome at the printed path\n\n# 4. register and log in a controller identity\notto client register --name \"my-laptop\" --description \"Local controller\"\notto client login\n\n# 5. verify the stack\notto commands list\n```\n\nFor headless/CI use, `otto setup --non-interactive`\n\nemits deterministic JSON with no TTY prompts, most commands take `--json`\n\n, and `otto logs follow --source all`\n\nstreams structured events by `requestId`\n\nso you can correlate relay, controller, and node in real time while debugging an agent run.\n\nHanding automation a *real* logged-in browser is powerful, so the defaults are conservative:\n\nOtto is built for developers and automation teams who need *real* browser context — integration tests against logged-in flows, uptime/monitoring on pages that gate headless traffic, scraping that has to look human, and agent workflows that read and act on live sites. If your use case is genuinely fine with headless, you may not need this. If headless keeps lying to you, this is the alternative.\n\nIt's MIT-licensed and on npm as [ @telepat/otto](https://www.npmjs.com/package/@telepat/otto). Repo and docs:\n\nIt's early. I'd genuinely rather hear where the design is wrong than collect polite stars — so if you run it and something feels off (the relay model, the security assumptions, the command-bundle approach), tell me in the comments. What would you point it at first?", "url": "https://wpnews.pro/news/otto-give-your-ai-agent-a-real-browser-without-running-a-browser-farm", "canonical_source": "https://dev.to/sebivaduva/otto-give-your-ai-agent-a-real-browser-without-running-a-browser-farm-3dk5", "published_at": "2026-06-16 10:02:47+00:00", "updated_at": "2026-06-16 10:17:21.146824+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "large-language-models"], "entities": ["Otto", "Chrome", "WebSocket", "MCP", "Claude", "OpenAI", "Puppeteer", "Playwright"], "alternates": {"html": "https://wpnews.pro/news/otto-give-your-ai-agent-a-real-browser-without-running-a-browser-farm", "markdown": "https://wpnews.pro/news/otto-give-your-ai-agent-a-real-browser-without-running-a-browser-farm.md", "text": "https://wpnews.pro/news/otto-give-your-ai-agent-a-real-browser-without-running-a-browser-farm.txt", "jsonld": "https://wpnews.pro/news/otto-give-your-ai-agent-a-real-browser-without-running-a-browser-farm.jsonld"}}