auto-browser

LvcidPsycheMIT活跃维护

给 AI Agent 配一个真实浏览器,关键步骤可人工接管,基于 MCP 协议、可自托管运行。

Give your AI agent a real browser — with a human in the loop. Open-source MCP-native browser agent.

v1.17.0发布于 2026-11-09 04:32 UTC926star +2 / 7 天154fork同步于 2026-11-10 11:52 UTC

怎么上手

由 README 提炼,执行前请确认
git clone https://github.com/LvcidPsyche/auto-browser.git
cd auto-browser
docker compose up --build
硬件要求按 README 说明

README 未说明具体显存或 GPU 要求;默认通过 Docker Compose 本地部署,也可用 GitHub Codespaces 托管运行,预构建镜像为 linux/amd64。

有 Docker 镜像
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

给 AI agent 一个带人工接管的真实 Playwright 浏览器,MCP 原生、本地优先

给 AI Agent 配一个真实浏览器,关键步骤可人工接管,基于 MCP 协议、可自托管运行。

核心能力

  • 以 MCP 服务器形式暴露浏览器控制面,支持 HTTP 与 stdio 接入
  • 用 noVNC 保留同一实时会话,人工可随时接管
  • 保存命名 auth profile,新会话免重复登录
  • 内置审批门、操作者身份、PII 清洗与 Ed25519 签名 Witness 回执
  • Docker Compose 一键本地部署,可选按会话隔离

容易踩的坑

首次启动会在 data/api-token 生成令牌,所有 API 和 MCP 请求都必须带 Authorization: Bearer;预构建控制器镜像不含 agent CLI,使用 *_AUTH_MODE=cli 需从源码构建并设 INSTALL_AGENT_CLIS=true;叠加隔离配置时 docker-compose.images.yml 必须放在最后,否则会用到本地旧镜像。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v1.17.0
  1. v1.17.0最新正式版2026-11-09 · 2026-11-09 04:32 UTC

    A security and correctness release over 1.16.0, from the review findings 1.16.0 left open and from reviews of this release's own changes. The controller image now installs its whole Python dependency tree from a lock pinned at releases at least 72 hours old. A restricted witness receipt withholds secrets at any depth; the MCP bridges, the Python client and the LangChain tools no longer show the bearer token or a secret of the controller URL in an error; a screenshot that is to be PII-redacted is no longer served before its redaction is done, and a download read with browser.read_download is PII-scrubbed; and browser.get_html with text_only and browser.find_elements now read the page in the controller's isolated world, so a page's own scripts cannot change what they see. Read the upgrade note first: a MESH_TIMESTAMP_WINDOW above 250 seconds, or at or below 0, now stops startup. The controller image and the client and LangChain packages change, and the MCP bridge package now requires this client; the browser-node image changes only in version.

    Upgrade notes

    • A MESH_TIMESTAMP_WINDOW above 250 seconds, or at or below 0, now stops startup. Set it inside those limits (the default is 30), on a controller with the mesh off too. With the mesh on, lowering it needs the wait that docs/mesh.md describes for any lowering: the startup check assumes the last process ran with no larger window, so start this release only once the old window has passed since the old controller last accepted an envelope (stop it, then wait that long).

    Security

    • The controller image now installs its whole Python dependency tree from controller/constraints.txt, pinned at releases at least 72 hours old, and CI's Python test installs take no younger release. requirements.txt and requirements-dev.txt pin only the packages the controller names, so every build took the newest release of each package below them, however new: the 1.15.0 image carried pydantic-core 2.50.4, uploaded under 72 hours before it was built. The file is uv's universal resolution of both files for Python 3.11 and later, with its command and cutoff in its header. The host tests and the devcontainer install under it too, and CI's host tests hold what it does not pin, the build backend of the editable install, to releases at least 72 hours old. The dependency audits read the file as it is, where they resolved the top-level pins afresh: CI's, on Linux and Python 3.11 as the image is, checks the versions the image installs, and scripts/release_audit.py's the versions the file gives the platform and Python it runs on. A requirements file moved to a release the file pins at another fails CI's install until the file is generated again. The client and LangChain tests still take the newest release of each package, as those libraries' users do, but only one at least 72 hours old, their build backends included. Dependabot holds version updates three days, as its configuration now says. (R5C-14)
    • A restricted witness receipt now withholds secrets at any depth, and in its metadata and approval reason too. A receipt in restricted evidence mode (a confidential session, or a sensitive, high-risk or auth-material action) blanked five top-level target keys (text, text_preview, file_path, password, totp_secret) and what a select_option chose, and kept its metadata and its approval's reason as given. It now withholds, at any depth of the target and the metadata, those keys, every key whose name holds a word the run trace redacts by (authorization, cookie, api-key, api_key, token, password, secret), and the free text an agent or a tool call wrote: a reason key, and the approval's reason, which for a governed tool call previews its arguments, and an operator's comment on an approval decision. Each URL there (a url key's) now has its query and fragment scrubbed as the run trace scrubs a URL's, by
  2. v1.16.02026-11-07 · 2026-11-07 01:57 UTC
    查看更新说明

    A security and correctness release over 1.15.0 (GHSA-gmvj-rj2h-7528), from the open findings of the review behind 1.15.0 and from reviews of this release's own changes. The model no longer receives an isolated session's noVNC password: the full takeover link now comes from a route that also demands a takeover token, which agents are not given. Codex behind the host bridge no longer gets the whole environment of the bridge's shell; Playwright's driver, Tesseract and the controller's other helper processes get an allowlisted environment too (a Docker credential helper, when one is configured, is a known limit); and on Linux the controller and the bridge make themselves non-dumpable, so a process of the same user cannot read their environment through /proc. The Python client, the LangChain tools, the MCP bridges and the repo's scripts no longer send their tokens through an environment proxy to a controller on the same machine. Read the upgrade notes first: a person opens an isolated session's takeover link from the dashboard or scripts/takeover_link.sh with the takeover token, an approval webhook on a private name or address needs APPROVAL_WEBHOOK_ALLOW_PRIVATE_TARGETS=true, a Codex setup behind the bridge that needs another variable passes it with --pass-env, and a controller rolled back to 1.15.0 cannot read the harness runs this version saved. The controller image and the client and LangChain packages change, and the MCP bridge package now requires this client; the browser-node image changes only in version.

    Upgrade notes

    • An isolated session's full takeover link now needs the new takeover token. An agent that handed a person the takeover link of an isolated (docker_ephemeral) session now hands over a link without the noVNC password. The person opens the full link from the dashboard ("Open noVNC") or prints it with scripts/takeover_link.sh <session_id>; a link opened without the password makes noVNC ask for it. Both need the new takeover token (TAKEOVER_TOKEN, else the token the controller generates in TAKEOVER_TOKEN_FILE, ./data/takeover-token with the shipped compose): the dashboard asks for it on the first "Open noVNC" of an isolated session in a tab, before it opens the window (Chrome and Edge show no prompt in a tab that is not in front), and again on the click after a refusal, and scripts/takeover_link.sh reads it from TAKEOVER_TOKEN (the shell or .env) or data/takeover-token. A script or integration that took the password from POST /sessions, GET /sessions/{id}, GET /remote-access or the takeover result now calls GET /sessions/{session_id}/takeover-link with the X-Takeover-Token header.
    • An approval webhook on a private name or address now needs APPROVAL_WEBHOOK_ALLOW_PRIVATE_TARGETS=true. An approval webhook whose host is a private name (a service on the compose network, a LAN host) was reachable by name before and is now refused, as is one at a shared address (100.64.0.0/10, such as a Tailscale node): every address that is not global is. Set the new APPROVAL_WEBHOOK_ALLOW_PRIVATE_TARGETS=true to allow it. The flag lifts the name and address checks, so loopback (localhost, 127.0.0.1) and every private range are allowed too; cloud metadata stays refused whatever it says: 169.254.0.0/16, 100.100.100.200, fe80::/10, fd00:ec2::254, fd20:ce::254 (GCP), fd00:c1::a9fe:a9fe (OCI) and the names metadata and metadata.google.internal, and an IPv6 address that spells a metadata address: mapped, translated, compatible, NAT64's well-known 64:ff9b::/96, the local-use 64:ff9b:1::/48 read at every RFC 6052 position (/32 to /96), 6to4, Teredo (the client's address) or an ISATAP interface id (0:5efe or 200:5efe, then the IPv4 address) after any prefix. Without the flag such an address is refused, too, when the IPv4 address it spells is not global. A network-specific NAT64 prefix (one an operator chose elsewh
  3. v1.15.02026-11-05 · 2026-11-05 17:40 UTC
    查看更新说明

    A security and correctness release over 1.14.1, from an internal review of the whole controller and of this release's own fixes. Read the upgrade notes first: provider CLIs now get a fixed environment and need a usable CLI_HOME, a misspelled SESSION_ISOLATION_MODE stops startup, an unreadable proxy persona file or cron store answers 503, and the repo's scripts read .env literally. On Linux with per-session networks, the fix that keeps the controller's published port in place when it joins a session network needs Docker Engine 28 or later (older engines get a warning). The controller and browser-node images change; the LangChain package's crewai extra now installs langchain-core; the client and the MCP bridge packages change only in version.

    Upgrade notes

    • A misspelled SESSION_ISOLATION_MODE now stops the controller from starting. It used to fall back to the shared browser without a word. Only shared_browser_node and docker_ephemeral are accepted; surrounding spaces and capital letters are forgiven, but docker-ephemeral or dockerephemeral are errors.
    • A provider CLI (claude, codex, gemini) now gets only a fixed set of environment variables. If your CLI needs one that is not in the set, name it in the new CLI_PROVIDER_ENV_PASSTHROUGH setting (comma-separated variable names, empty by default). The set is the operating-system basics (PATH, HOME, TEMP, LANG, LC_*, proxy and certificate variables) plus that provider's own key, endpoint and config-directory variables (for codex, that includes CODEX_API_KEY). With CLI_HOME unset, or set to a directory that cannot be created, a CLI call now fails (with an error that says to set CLI_HOME) where the controller's user has no home directory it can write, or where its private directory is refused (see Security); before this release the CLI started in the controller's working directory instead. Set CLI_HOME to a writable directory there; the image's default, /data/cli-home, is one.
    • A Witness bundle whose chain failed the exporter's own check now fails the offline verifier. That covers, for example, a chain whose anchor reads anchor_missing, anchor_unreadable, truncated or diverged, or whose hash walk fails, with or without --allow-unsigned; a chain_valid_at_export that is present and not exactly true fails too. The bundle now carries chain_reason_at_export (verify's reason, as a sentence) and chain_anchor_status_at_export (the anchor's status code), and the verifier prints the reason, so the failure says why. A bundle made by an older version has no reason, and one whose chain failed at export no longer passes --allow-unsigned.
    • Harness runs and staged skills written by earlier versions may still hold a URL token on disk, and return it. Under HARNESS_ROOT (default /data/harness), runs/<id>/run.json, runs/<id>/strategy.md and a staged skill's candidate.json, envelope.json, provenance.json and drift.json could carry the token from the final URL, and older trace.json files were not text-scrubbed. The files are not rewritten. An attempt in run.json now carries scrubbed: true once its verification is scrubbed: an attempt without it (written by an earlier version) is scrubbed when read, so harness.get_status, harness.list_runs and harness.graduate no longer return the token from its verification (a scrubber that fails on such a record leaves the run listed, with only the verdict kept and a warning in the log). What is read back from the other files is not scrubbed again, so for such a run the staged candidate that run.json holds and harness.get_trace (its trace.json), and for such a skill harness.list_candidates and harness.get_candidate (the candidate's envelope), can still return the token. A run.json written by this version has the new scrubbed field, and a 1.14.x controller cannot load such a file: its a
  4. v1.14.12026-11-05 · 2026-11-05 05:36 UTC
    查看更新说明

    A patch release over 1.14.0. A session whose start page fails to load now gets a 400 the agent can act on instead of a 500. Closing shadow browse no longer logs a false warning. The docs fix a compose ordering that ran isolated sessions on a stale local browser image. The controller image changes; browser-node and the client, LangChain and MCP bridge packages change only in version.

    Upgrade notes

    • If you run the published images with isolated sessions, check the order of your compose files. docker-compose.images.yml must come after docker-compose.isolation.yml. Named before it, it lost ISOLATED_BROWSER_IMAGE to the isolation overlay's default, so docker_ephemeral sessions ran a local auto-browser-browser-node:latest (a stale build, or none) instead of the published browser-node. To check, run docker compose ... config and confirm ISOLATED_BROWSER_IMAGE names the ghcr.io image. Then close any isolated sessions started before you fixed the order.
    • Creating a session whose start_url fails to load now returns 400 browser_action_failed (retryable), not 500. A client that handled the 500 should handle the 400 the way it handles a failed navigate.

    Fixed

    • A session whose start_url fails to load (a refused connection, an unresolved name, a timeout) is answered with 400 browser_action_failed, retryable, as navigate answers the same failure. It used to be a 500 "Internal error". The message names Chrome's net error (net::ERR_CONNECTION_REFUSED) or the timeout, or else says the browser could not open the page. It never repeats Playwright's text, which carries the URL, and the controller's log keeps that text's first line. The session is closed as before. Shadow browse's page gets the same answer.
    • Closing shadow browse no longer logs a WARNING that the pre-request gate could not detach from a target that was already closed. That case is logged at DEBUG; a detach that fails otherwise is still a WARNING.

    Changed

    • The README and docker-compose.images.yml say to name the images overlay last when isolated sessions are on. Named before docker-compose.isolation.yml, it lost ISOLATED_BROWSER_IMAGE to that overlay's default, so isolated sessions ran a local auto-browser-browser-node:latest (a stale build, or none) instead of the published image.
    • docs/deployment.md states that under PII_SCRUB_SCREENSHOT_STRICT=true an action whose before-screenshot cannot be scrubbed is refused before it runs (503). With OCR unavailable, that means every action.
  5. v1.14.02026-11-04 · 2026-11-04 16:47 UTC
    查看更新说明

    A hardening release over 1.13.1. The controller no longer runs as root, and Chromium runs with its own sandbox in browser-node and in every isolated container, so a renderer exploit no longer starts with every syscall the container allows. BROWSER_SANDBOX=on refuses any session whose browser is not verifiably sandboxed. Read the upgrade notes first: the controller changes user and hands its data over at the first start, and browser-node runs under a custom seccomp profile. The controller and browser-node images change; the client, LangChain and MCP bridge packages change only in version.

    Upgrade notes

    • A default compose install now runs browser-node under a custom seccomp profile that lets the container create user namespaces. Chromium's own sandbox needs them, and Docker's default profile refuses them to a container without CAP_SYS_ADMIN. The profile is moby's default plus chroot, clone and unshare (controller/app/browser/seccomp/); docker_ephemeral containers get it too. It exposes the kernel's user-namespace code to code in the container, and in exchange every renderer runs sandboxed. That is the better trade for a browser that renders arbitrary pages: a renderer exploit no longer starts with every syscall the container allows. To keep the old setup under compose, set both BROWSER_SANDBOX=off and BROWSER_SECCOMP_PROFILE=builtin (docs/deployment.md, "Chromium's sandbox"). On a host whose Docker daemon sets its own seccomp-profile, 1.14.0's profile replaces that one, and builtin means moby's built-in profile, not the daemon's: point BROWSER_SECCOMP_PROFILE and ISOLATED_BROWSER_SECCOMP_PROFILE at your profile plus the auto-browser entry to keep it.
    • The controller now runs as the owner of your data directory, or as uid 10002, not as root. A Linux checkout whose ./data belongs to you runs it as you; Docker Desktop and named volumes run it as uid 10002 (never browser-node's 10001). It holds no capabilities and runs with no_new_privs. At start, the root-owned files earlier releases left in the controller's own /data subtrees are handed to that user, and data/api-token stays readable by its owner. CONTROLLER_RUN_AS=root restores the root controller; it is not recommended, and readiness warns while it is set. With the isolation overlay on Docker Desktop, the controller joins group 0 to use the Docker socket.
    • BROWSER_SANDBOX=on now also gates shared mode: every session is refused, and readiness fails, while the shared browser-node's connect-time state is not a verified on, including a browser-node image older than 1.14.0. Under on, browser.cdp_attach is refused; a CDP_CONNECT_URL Chrome is the operator's own and is reported as attached, not checked.
    • Rolling back: after running an older release on the same data, delete data/.auto-browser-owner before starting 1.14 again, so the files the older release created as root are handed over. A container without DAC_OVERRIDE or DAC_READ_SEARCH cannot look inside its own unreadable directories: chown ./data by hand then.
    • A capability-less uid 0 still owns the image: under cap_drop: [SETUID] or [SETGID], or a cap_add set without SETUID or SETGID, a compromised controller could rewrite the image and regain the container's capabilities on a restart. The entrypoint names them; prefer cap_drop: [ALL], user: "10002:10002", or read_only: true with a tmpfs for /tmp, which screenshot OCR needs (docs/deployment.md).
    • A controller that cannot switch users (cap_drop: [ALL], cap_drop: [SETUID] or [SETGID], or cap_add without SETUID or SETGID) starts as a uid 0 with no capabilities at all, and refuses to start, in one line, when it cannot enter the API token's directory or read the token the controller would read. A directory it cannot see, not there yet or under one it cannot search, is left to the controller, which creates it or says why it cannot

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    Auto Browser

    MCP Toplist

    CI PyPI License: MIT MCP Server Local First Open in GitHub Codespaces

    Auto Browser demo

    Give your AI agent a real browser, with a human in the loop.

    Auto Browser is an MCP-native browser control plane for authorized workflows. It gives MCP clients, LLM agents, and operators a shared Playwright browser with human takeover, reusable auth profiles, approvals, audit trails, and local-first deployment.

    Works with:

    • Claude Code, Codex (CLI, IDE extension, and app), Google Antigravity, Cursor, and VS Code, connected directly over HTTP
    • Claude Desktop, through the bundled stdio bridge
    • any MCP client that can talk HTTP or stdio
    • direct REST callers when you want curl-first control

    Why Auto Browser

    • MCP-native from day one. The browser surface is already packaged as an MCP server instead of bolted on after the fact.
    • Human takeover when the web gets brittle. noVNC keeps the same live session available when a person needs to step in.
    • Login once, reuse later. Save named auth profiles and reopen fresh sessions that are already signed in.
    • Local-first by default. Run the full stack on your own box with Docker Compose, or use Codespaces for a quick hosted demo.
    • Safety rails built in. Approvals, operator identity, PII scrubbing, Witness receipts, and policy presets are all part of the product surface.
    • Evidence you can hand to someone else. Witness receipt chains are Ed25519-signed, and an exported bundle verifies with scripts/verify_witness_bundle.py — which imports nothing from this project, so a recipient need not run or trust this controller to check it. The signing key sits beside the receipts, so for evidence that survives a compromised box, pin a head hash you recorded earlier or ship the receipts off the box (details).
    • We audit ourselves in public. docs/audits/2026-08-execution-audit.md documents an adversarial audit of this repo that found safety controls which reported success while doing nothing, with reproductions, the fixes, and the gates that close the class.
    • Governed skill induction. Verified browser traces can become staged skill candidates with provenance that is signed when a mesh identity is configured — and checked on read, not just produced — plus verifier adapters and review-only graduation — agents that prove they can repeat themselves correctly, not just act once.

    Good Fits

    • internal dashboards and admin tools
    • operator-assisted QA and browser debugging
    • login-once, reuse-later account workflows
    • brittle sites where a human may need to recover the flow
    • MCP-powered agent workflows that need a real browser, not just HTML fetches

    Not the Goal

    • CAPTCHA solving
    • unauthorized scraping or account automation
    • deceptive identity shaping or bypass tooling

    What You Get

    Browser ControlOperator SafetyDeployment and Integration
    Playwright-backed sessions with screenshots, DOM summaries, OCR excerpts, tab controls, downloads, and network inspectionapproval gates, operator identity headers, audit events, PII scrubbing, Witness receipts, and protection profilesMCP over HTTP, bundled stdio bridge, REST API, Docker Compose, Codespaces, auth profiles, and optional per-session isolation

    Quickstart

    git clone https://github.com/LvcidPsyche/auto-browser.git
    cd auto-browser
    docker compose up --build
    

    That is enough for local development with the default settings. On first start the controller generates an API token and saves it to data/api-token in this folder (git ignores it), and every API and MCP request must send it as Authorization: Bearer <token>. The dashboard asks for it once, the examples below read it with $(cat data/api-token), and the MCP clients take it as shown under MCP Clients. API_ALLOW_TOKENLESS_LOOPBACK=true runs the controller without a token, as it ran before, but then any container on the compose network can drive the browser.

    Optional:

    cp .env.example .env
    make doctor
    

    Run make doctor from a normal terminal with local Docker access and permission to open localhost sockets.

    Open:

    • API docs: http://127.0.0.1:8000/docs (Authorize with the token to try requests). The docs need no token on a loopback deployment (an exposed one answers 401), and Swagger UI loads its assets from cdn.jsdelivr.net.
    • Operator dashboard: http://127.0.0.1:8000/dashboard, which includes the queue of actions waiting on your approval; paste the token when it asks, or open http://127.0.0.1:8000/dashboard#token=<token>
    • Visual takeover: http://127.0.0.1:6080/vnc.html?autoconnect=true&resize=scale

    All published ports bind to 127.0.0.1 by default.

    Prebuilt images

    Every release is also published to GHCR, so you can skip the build:

    docker compose -f docker-compose.yml -f docker-compose.images.yml up -d
    

    AUTO_BROWSER_IMAGE_TAG picks the version: latest (the default), a release such as 1.10.1, or edge for the current main. The images are linux/amd64, and each carries an SBOM and a build provenance attestation (gh attestation verify oci://ghcr.io/lvcidpsyche/auto-browser-controller:latest -R LvcidPsyche/auto-browser). The controller image leaves out the agent CLIs; for *_AUTH_MODE=cli, build from source with INSTALL_AGENT_CLIS=true.

    With isolated sessions, name docker-compose.images.yml last, so they start from the published browser image too:

    docker compose -f docker-compose.yml -f docker-compose.isolation.yml -f docker-compose.images.yml up -d
    

    In the other order, the isolation overlay's default wins, and isolated sessions run a local auto-browser-browser-node:latest: a stale build, or no image at all.

    Try It in Codespaces

    Open in GitHub Codespaces

    Codespaces provisions the stack automatically. The dashboard and noVNC tabs are usually ready in about 90 seconds.

    First Useful Demo

    The highest-signal flow in this repo is:

    1. create a session
    2. log in manually if the site needs a human
    3. save the session as a named auth profile
    4. open a new session from that auth profile
    5. continue work without reauthing

    Start here:

    Minimal session creation, from the auto-browser folder (AUTH carries the token; if you set API_BEARER_TOKEN, use $API_BEARER_TOKEN in place of $(cat data/api-token)):

    AUTH=(-H "Authorization: Bearer $(cat data/api-token)")
    curl -s "${AUTH[@]}" http://127.0.0.1:8000/sessions \
      -X POST \
      -H 'content-type: application/json' \
      -d '{"name":"demo","start_url":"https://example.com"}' | jq
    

    Minimal observation:

    curl -s "${AUTH[@]}" http://127.0.0.1:8000/sessions/<session-id>/observe | jq
    

    Recent Changes

    1.10.1

    • Rebuilt Docker images with updated dependencies (Pillow 13.0.0). Image tags now publish only from a main commit whose CI run succeeded.

    1.10.0

    • Operators can no longer reach each other's work. With named credentials (API_BEARER_TOKENS), saved auth state, exported archives, harness runs, skill candidates, memory profiles, uploads, MCP resource lists and controller-wide approvals belong to the operator who made them.
    • ALLOWED_HOSTS holds wherever a page ends up: after a redirect, a tab switch, a cookie written through browser.set_cookies, or a page that navigates itself or opens a popup (a popup's first page is held from 1.11.2). A page that gets off the list on its own is sent back to about:blank once it has navigated, not before its request goes out.
    • Witness verification rejects a chain with its signatures stripped, and the top-level valid now covers signatures. Read the upgrade notes in the changelog before upgrading.
    • Fixes for the Playwright export, stealth mode, REST error statuses, text-only agent steps, private-key scrubbing and the dashboard's error reporting, plus an opt-in FORCE_GOVERNED_APPROVALS.

    1.9.1

    • Prebuilt Docker images on GHCR (thanks @FRFlo): run a release without building it. See Prebuilt images.

    1.9.0

    • Operators no longer share sessions. With named credentials (API_BEARER_TOKENS), a session belongs to the operator who created it, as auth profiles already did.
    • Every setting in .env reaches the controller. Compose used to forward only the keys it listed, so settings like API_BEARER_TOKENS and PII_SCRUB_* were ignored. Check your .env before upgrading.
    • Governed approvals run, a page stuck in a script loop can no longer hang the controller, and the Claude and OpenAI providers work on current models.
    • Security fixes for approvals, navigation, TOTP autofill, PII scrubbing and witness bundle verification (GHSA-37hm-f6gf-q7vx).

    1.8.1

    • Sturdier sessions and stores. Actions that ran are no longer reported as failed when the page navigates right after, two requests carrying one approval no longer both run its action, MAX_SESSIONS holds under concurrent creates, and sessions, tabs, cron jobs and the JSON stores no longer leak or lose writes.
    • The stdio MCP bridge survives a controller restart and reports auth and rate-limit errors instead of hanging. The SDK, bridge and LangChain adapters can send an operator id for controllers with REQUIRE_OPERATOR_ID=true.
    • Faster actions and observations, fewer browser round trips per observation, and a controller image without the test tooling.
    • Security fixes for the noVNC socket, TOTP autofill, auth-profile paths, approvals and witness receipts. create_session with totp_secret now needs totp_hosts or a start_url.

    See CHANGELOG.md for the full release history.

    MCP Clients

    Auto Browser exposes:

    • an HTTP MCP endpoint at http://127.0.0.1:8000/mcp
    • convenience endpoints at http://127.0.0.1:8000/mcp/tools and http://127.0.0.1:8000/mcp/tools/call
    • a stdio bridge: uvx auto-browser-mcp from PyPI, or scripts/mcp_stdio_bridge.py in a repo checkout

    Clients that speak MCP over HTTP connect directly, sending the controller's token. With Claude Code or Codex, from the auto-browser folder (--scope user makes the server available in every project, not only this folder):

    claude mcp add --transport http --scope user auto-browser http://127.0.0.1:8000/mcp \
      --header "Authorization: Bearer $(cat data/api-token)"
    
    # Codex reads the token from the variable you name: export it wherever Codex runs.
    export AUTO_BROWSER_TOKEN="$(cat data/api-token)"
    codex mcp add auto-browser --url http://127.0.0.1:8000/mcp --bearer-token-env-var AUTO_BROWSER_TOKEN
    

    Google Antigravity takes the server in its raw MCP config (MCP Servers, then Manage MCP Servers, then View raw config), or in ~/.gemini/config/mcp_config.json. It reads serverUrl, not url, and the token goes in headers:

    {
      "mcpServers": {
        "auto-browser": {
          "serverUrl": "http://127.0.0.1:8000/mcp",
          "headers": { "Authorization": "Bearer <contents of data/api-token>" }
        }
      }
    }
    

    Cursor, VS Code, Claude Desktop through the stdio bridge (which reads the file named by AUTO_BROWSER_TOKEN_FILE / --token-file; the repo wrapper, make stdio-bridge or scripts/mcp_stdio_bridge.py, defaults that to its checkout's data/api-token for a loopback URL), and pairing Auto Browser with a web-search MCP server are covered in docs/mcp-clients.md.

    The default MCP tool profile is curated: the 20 tools a browsing agent needs (sessions, observe and screenshot, execute_action, page and download reading, tabs, auth profiles, human takeover). Every listed tool costs the model context on every request, so the diagnostics, audit, harness, and admin tools are in the full profile. To expose everything, set:

    MCP_TOOL_PROFILE=full
    

    MCP_TOOL_PROFILE=lean serves just the 7-tool browsing loop (observe, screenshot, execute_action, get_html, find_elements, human takeover, close), and MCP_TOOL_PACKS adds or removes named packs on top of any profile, e.g. MCP_TOOL_PACKS=tabs,downloads or -scripting. The pack table is in docs/mcp-clients.md.

    Raw tool-call example:

    curl -s "${AUTH[@]}" http://127.0.0.1:8000/mcp/tools/call \
      -X POST \
      -H 'content-type: application/json' \
      -d '{
        "name":"browser.create_session",
        "arguments":{
          "name":"demo",
          "start_url":"https://example.com"
        }
      }' | jq
    

    Client setup guides:

    For resource listing, resource reads, and subscription-style update examples, see docs/mcp-clients.md#resources-and-subscriptions.

    Convergence Harness

    Auto Browser ships a Stage 0 convergence harness for Agent Skill Induction. It runs a structured task contract, records tamper-checked traces, verifies completion, and writes a staged skill candidate carrying provenance. With a mesh identity configured that provenance is signed, and the registry verifies the signature before serving a candidate — a candidate that fails the check, or that was dropped into the staging directory unsigned, is refused. Candidates induced from a mock run are marked simulated so they cannot pass as converged. Generated skills are staged only — promotion stays explicit and reviewed.

    The harness tools — convergence runs, run status and traces, drift checks, candidate management, and graduation — are in the full MCP tool profile (MCP_TOOL_PROFILE=full), or can be invoked directly over REST.

    Start with docs/convergence-harness.md. A deterministic local smoke is:

    python -m controller.harness.run --contract evals/contracts/example_read.json --mock-final-url https://example.com --mock-final-text "Example Domain"
    

    For MCP clients, set MCP_TOOL_PROFILE=full (or MCP_TOOL_PACKS=harness on top of curated) to expose the harness.* tools.

    Security and Compliance

    For a real private deployment, set at least:

    APP_ENV=production
    API_BIND_SCOPE=exposed
    API_BEARER_TOKEN=<strong-random-secret>
    REQUIRE_OPERATOR_ID=true
    AUTH_STATE_ENCRYPTION_KEY=<44-char-fernet-key>
    REQUIRE_AUTH_STATE_ENCRYPTION=true
    SHARE_TOKEN_SECRET=<strong-random-secret>
    CONTROLLER_ALLOWED_HOSTS=<controller hostname>
    ALLOWED_HOSTS=<sites the browser may visit>
    REQUEST_RATE_LIMIT_ENABLED=true
    METRICS_ENABLED=true
    STEALTH_ENABLED=false
    

    With APP_ENV=production the controller refuses to start while any of these is missing, and the startup log names the missing ones.

    By default every session shares one Chromium process and one noVNC desktop (SESSION_ISOLATION_MODE=shared_browser_node). Cookies and storage stay separate per session, but a human taking over one session can see the others' windows. When sessions belong to different people, accounts or trust domains, start with make up-isolation to give each session its own browser container, takeover surface and Docker network (docker_ephemeral), so one session's browser cannot reach another's Playwright server. docs/session-isolation-audit.md has the details.

    ALLOWED_HOSTS is held before the request goes out (PRE_REQUEST_BLOCKING=true, the default):

    • Stopped before it is sent: a top-level navigation's request to a host off ALLOWED_HOSTS, including every redirect hop and a popup's first request.
    • Still caught after the fact by NAV-07 (the guard that sends a refused page back to about:blank once it commits):
      • speculation-rules prefetch in attach modes, and in a launched browser where it could not be turned off (launched browsers turn it off when the controller acquires them; /readiness warns when that failed);
      • a navigation a service worker serves (one a same-site refused iframe registered) for a popup's first navigation, and for any the popup makes before its service-worker bypass is set (a background task once the controller sees the popup), in every mode;
      • blob: navigations a refused iframe makes;
      • bfcache restores in attach modes;
      • requests held when the gate detaches, and any navigation a page starts itself before the replacement gate attaches. They go out. The controller attaches the replacement before it closes that browser's sessions, so the window is one attach, and it refuses their actions meanwhile.
    • Any tab, rarely: a navigation to a refused host fails closed in any tab of a gated browser, the operator's own included in attach modes, when the gate cannot classify its frame (its lookup failed).
    • Not covered: iframes and subresources of other hosts.

    In an attach mode (CDP_CONNECT_URL, browser.cdp_attach) the gate stops navigations of the controller's sessions only (apart from a frame it cannot classify, above), but every navigation in that Chrome, the operator's own tabs included, waits on its reply: about 13 ms each, the median measured on Chromium 151. Start that Chrome with --disable-features=BlockOriginHeaderModificationOnRedirect,AvoidCorsURLLoaderRestartOnRedirect and set CDP_BLOCK_ORIGIN_ON_REDIRECT_DISABLED=true. Without it, cross-origin 307/308 POST redirects can fail, and on Chrome 155 and later credentialed cross-origin requests that need a CORS preflight fail after a redirect, in the operator's own tabs too, while the controller is attached. Details: docs/deployment.md.

    COMPLIANCE_TEMPLATE can apply a preconfigured posture at startup:

    PresetAuth EncryptionOperator IDPII ScrubIsolationMax Session Age
    strictrequiredrequiredall layersdocker_ephemeral4h
    balanced-requirednetwork + textshared24h

    Both presets require upload approvals and enable Witness receipts. Startup writes the applied policy to /data/compliance-manifest.json. The legacy names (HIPAA, SOC2, GDPR, PCI-DSS) still work as deprecated aliases and emit a warning at startup.

    Example:

    COMPLIANCE_TEMPLATE=strict docker compose up
    

    For deployment details, hosted Witness notes, CLI auth modes, and reverse-SSH guidance, see:

    Architecture at a Glance

    flowchart LR
        User[Human operator] -->|watch / takeover| noVNC[noVNC]
        LLM[Any model: OpenAI / Claude / Gemini / OpenRouter / Grok / DeepSeek / MiniMax / local] -->|shared tools| Controller[Controller API]
        Controller -->|Playwright protocol| Browser[Browser node]
        noVNC --> Browser
        Browser --> Artifacts[(screenshots / traces / auth state)]
        Controller --> Artifacts
        Controller --> Policy[Allowlist + approval gates]
    

    Model providers

    First-class adapters for OpenAI, Claude, and Gemini (API or CLI). Beyond those, a single generic OpenAI-compatible adapter drives any model reachable over an OpenAI /chat/completions endpoint — set an API key to enable it:

    ProviderReaches
    openrouterone key → ~every frontier model (Claude, GPT, Gemini, Grok, DeepSeek, Llama, Mistral, Qwen, …)
    xaiGrok
    deepseekDeepSeek (text-only; driven from the DOM/accessibility outline)
    minimaxMiniMax
    openai_compatibleany custom base URL — self-hosted Ollama / vLLM / LM Studio, Azure OpenAI, Together, Groq, Fireworks, …

    Vision (screenshots) is used for every provider except text-only ones. See .env.example for the *_API_KEY / *_BASE_URL / *_MODEL settings.

    Core components:

    • browser-node/ runs Chromium, Xvfb, x11vnc, and noVNC
    • controller/ exposes the FastAPI controller, MCP transport, policy rails, and orchestration endpoints
    • data/ holds runtime artifacts, auth state, approvals, audit logs, and optional CLI caches
    • scripts/ contains local helpers for doctor, smoke tests, bridges, and release checks

    Repo Guide

    PathWhat It Contains
    controller/controller API, MCP transport, tests, and packaging
    browser-node/browser runtime and Playwright connection layer
    examples/copy-paste flows and MCP client setup
    integrations/langchain/LangChain, LangGraph, and CrewAI adapters
    docs/architecture, deployment, hardening, and launch docs
    scripts/doctor, smoke harnesses, stdio bridge, and auth helpers
    ops/supporting service templates and operational assets

    Common Commands

    CommandPurpose
    make helplist available repo commands
    make lintrun Ruff checks across the whole repo
    make testrun controller tests in Docker
    make test-localrun controller tests on host Python 3.11+
    make evalrun deterministic provider/profile eval scoring
    make doctorrun the local readiness smoke
    make release-auditrun the fuller release-validation pass
    make smoke-isolationverify per-session Docker isolation
    make smoke-reverse-sshverify reverse-SSH remote access

    Documentation Map

    If You Want To...Start Here
    understand the system shapedocs/architecture.md
    connect Claude, Codex, Antigravity, Cursor, or VS Codedocs/mcp-clients.md
    run the curl-first examplesexamples/README.md
    deploy on a trusted hostdocs/deployment.md
    review production constraintsdocs/production-hardening.md
    see what session isolation does and does not separatedocs/session-isolation-audit.md
    run the convergence harnessdocs/convergence-harness.md
    inspect release historyCHANGELOG.md
    see where the project is headedROADMAP.md

    Contributing

    If you want to help, start with:

    If Auto Browser is useful, a star helps other people find it. Sponsorship and tip options live in TIPS.md.