Skip to main content
Glama

telebrowser

Real browsers for coding agents that run on a remote, headless box. Agents drive a headful Chromium over CDP through an MCP server; when a site needs a human (sign-in, captcha, 2FA, a passkey prompt), you open the same browser from your own machine, do the step, and hand it back. Each project gets its own persistent profile, so a login made for one project is never reachable from another.

Built for a single-operator homelab: two containers, published only on 127.0.0.1, with the human view behind your own reverse proxy and authentication.

How it fits together

agent (pi, Claude Code, Codex, opencode)
  stdio MCP: telebrowser mcp           host launcher (bin/telebrowser, Node 22+, no deps)
    - resolves the project from the working directory
    - derives the project token from a shared secret
    - runs chrome-devtools-mcp against ws://127.0.0.1:<port>/t/<token>/devtools/browser
    - adds browser_handoff, browser_downloads, browser_info
    - one session per process: it sees only its own tabs
        |
        v
telebrowser-gateway                    sidecar sharing the browser container's network
    - the only published port, 127.0.0.1 on the host
    - token -> project; proxies /json/* and /devtools/* to that project's Chromium only
    - session endpoints relay CDP through a per-agent tab filter
    - starts browsers on demand, stops them after IDLE_MINUTES without CDP traffic
        |  run/want/<project>  (gateway -> supervisor)
        |  run/up/<project>    (supervisor -> gateway)
        v
telebrowser                            stock lscr.io/linuxserver/chromium (Selkies view on :3000)
    - browser/supervisor.sh, run as the s6 service svc-telebrowser: one Chromium per project with
      --user-data-dir=/data/projects/<p>/profile, debug port on 127.0.0.1 inside the
      container, window named [<p>] (used to raise it), downloads to /data/projects/<p>/downloads
    - browser/policies: no sync or sign-in, no password manager, no download prompt

Projects

The project is the directory name of the main checkout, so every worktree of a repository shares one browser. A submodule is its own project. Outside a git repository the project is scratch, which is wiped whenever it stops. TELEBROWSER_PROJECT or --project overrides it.

Agent sessions

Several agents can work in one project's browser at once without seeing each other. Every telebrowser mcp process is a session with its own CDP endpoint (/t/<token>/s/<session>/devtools/browser), relayed through a filter in the gateway (gateway/session.js):

  • a session starts on a blank tab of its own, and sees only the tabs it opened plus the popups those open; other agents' tabs and the human's do not exist for it

  • that first tab is the window's idle placeholder when there is one (an about:blank page no session owns: the tab Chromium starts with, or the one left when a session's tabs close), so what the agent does is what the human sees on opening the view; otherwise it is a new background tab

  • its new tabs open in the background, so they never take the window from whatever tab the human is looking at; browser_handoff, or select_page with bringToFront (Page.bringToFront or Target.activateTarget on an own tab), selects the agent's tab and raises the project's window

  • about 30 s after a session's last connection closes, its tabs are closed (never the window's last tab)

The human can click through every tab freely. The cookies and logins are the project's, shared by all its sessions. telebrowser ws and http in a shell stay unscoped and see every tab in the project.

Data

data/
  desktop/                    the container's /config (Selkies and labwc state; no browser profile)
  projects/<p>/profile/       Chromium user data: cookies and logins
  projects/<p>/downloads/     where downloads land (readable on the host)
  run/want, run/up, run/raise gateway/supervisor handshake
  run/supervisor.log

Related MCP server: CloakBrowser MCP

Security model

  • Network: the gateway is published on host 127.0.0.1 only. Each Chromium's debug port listens on 127.0.0.1 inside the container's network namespace, so nothing else on the Docker network reaches it except through the gateway. The human view (port 3000) is meant to sit behind a reverse proxy with its own authentication (WireGuard + Authelia in the homelab).

  • Tokens: a token is <project>.<HMAC-SHA256(secret, project)>. The gateway serves a token only its own project's browser and never lists other projects. This stops one project's agent, or a page prompt-injecting it, from reaching another project's signed-in sessions through the MCP tools or CDP.

  • Limits: every agent runs as the same Unix user. The secret, every profile directory and the Docker socket are all readable to that user, and Chromium's basic password store gives no real encryption at rest. The tokens prevent accidents and cross-project injection, not a deliberately hostile local process.

  • Profiles are not backed up by design. They hold live session cookies, and signing in again is cheap. A copy of a profile in a backup would be a copy of every login in it.

Launcher

telebrowser mcp | status | start | stop | focus | hold [min] | downloads | ws | http | project

Configuration: TELEBROWSER_URL, else the first line of ~/.config/telebrowser/url, else http://127.0.0.1:5858; the secret in TELEBROWSER_SECRET or ~/.config/telebrowser/secret. MCP client configuration, for any harness:

{ "telebrowser": { "command": "telebrowser", "args": ["mcp"] } }

The pinned chrome-devtools-mcp version is CHROME_DEVTOOLS_MCP in cli/mcp-proxy.js.

The proxy preserves upstream tools, context and pagination, and adds native MCP context:

  • telebrowser://usage: local usage, cost and throttling guidance.

  • telebrowser://status: token-free project/session configuration, not a live health probe.

  • telebrowser://downloads{?limit}: a bounded listing (default 50, maximum 200), costing one gateway status request plus local file metadata reads; never opens file contents.

  • browser-handoff-review: a planning prompt that does not execute the proposed task.

Initialization includes these instructions alongside upstream guidance. Our three tools have explicit behavioral annotations and runtime-validated inputs; browser_downloads also returns structured data with an output schema. browser_handoff changes browser state and is not read-only. browser_info still returns secret CDP endpoints for scripts: never log/commit them or copy them into resources. There is no known numeric browser/site API allowance: respect target-site throttling and Retry-After, avoid repeated polling, and inspect ambiguous state-changing outcomes before retrying. Context discovery never starts navigation or a handoff.

Gateway configuration

Variable

Default

TELEBROWSER_SECRET

required

at least 32 characters, same value as the launcher's

PORT

5858

listen port, inside the shared network namespace

IDLE_MINUTES

60

stop a browser after this long without CDP traffic (0: never)

HOST_DATA_DIR

/data

host path of /data, reported to agents so they can read downloads

VIEW_URL

the human view URL given to agents

DEBUG_PORT_START, DEBUG_PORT_END

9300, 9399

Chromium debug ports inside the container

READY_TIMEOUT_SECONDS

45

how long ensure waits for a browser to answer

Deploying

See the homelab stack (telebrowser/docker-compose.yaml there) for the reference deployment. The browser container mounts:

Repo path

Container path

browser/

/telebrowser

browser/s6/svc-telebrowser

/etc/s6-overlay/s6-rc.d/svc-telebrowser

browser/s6/svc-telebrowser.user

/etc/s6-overlay/s6-rc.d/user/contents.d/svc-telebrowser

browser/autostart, browser/menu.xml

/config/.config/labwc/ (replace the default shared browser)

browser/policies

/etc/chromium/policies/managed

Without a GPU, set SELKIES_ENCODER=jpeg,h264enc: software H.264 can leave the viewer on "waiting for stream". Leave the resolution dynamic so the desktop matches the viewer at full resolution. Windows share one desktop with no taskbar; telebrowser focus and browser_handoff raise a project's window with browser/raise-window.py (wlr-foreign-toplevel-management, since Wayland clients cannot raise themselves and labwc ignores Chromium's own activation). Chromium keeps its default app_id, so labwc draws no titlebar of its own.

Set S6_SERVICES_GRACETIME=20000 and a stop_grace_period of about 30 s. On shutdown the supervisor closes every browser with CDP Browser.close, which commits cookies to disk; a plain SIGTERM, or losing the system D-Bus first, makes Chromium drop the last ~30 s of cookie writes, i.e. a fresh sign-in. Create the host data directory as the PUID user before the first start, or Docker creates it root-owned. The gateway image is published as ghcr.io/justmytwospence/telebrowser-gateway by pushing a vX.Y.Z tag.

Development

npm test        # gateway, launcher and MCP proxy unit tests (node:test, no dependencies)

The skill for agents is in skill/telebrowser/SKILL.md. It is installed globally from the dotfiles, which keep a copy.

License

Functional Source License, Version 1.1, ALv2 Future License (FSL-1.1-ALv2). You may use, modify and self-host it for any purpose except offering a competing commercial product or service; each release becomes available under Apache-2.0 two years after it ships. Earlier releases published under MIT remain available under MIT.

Outside contributions require agreeing to a contributor license agreement; open an issue before sending a pull request.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to drive a live, persistent self-hosted Chromium session over MCP, running browser actions via Playwright/CDP, capturing page snapshots, screenshots and videos, and managing uploads and downloads in the same tabs and signed-in accounts a human user can take over at any time.
    23 npm
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to drive a Chromium-based browser for web research, testing, and automation with Playwright-compatible tools, persistent profiles, proxy matching, extension loading, and humanized input across local or containerized deployments.
    431 npm
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Lets AI coding tools drive the real, already-logged-in Chrome browser over local stdio—snapshotting and reading pages, clicking, typing, managing tabs, capturing screenshots, and inspecting network, console, storage, and cookies—without opening a remote-debugging port. It installs as a Chrome extension plus native-messaging bridge, so agents work in the user's authenticated sessions while passing bot detection.
    1 npm
    ISC
  • A
    license
    Not graded
    quality
    A
    maintenance
    Lets AI coding tools drive the real Chrome browser you already have open and logged into — navigating, reading pages, clicking, filling forms, capturing screenshots, and inspecting network, console, and storage state. It works through a Chrome extension rather than a remote-debugging port, so no extra debugging prompt appears and nothing else on the machine can hijack your logged-in browser.
    1 npm
    ISC