by JohnConnorNPC
Provides a tiled, multi‑host terminal interface that runs entirely in a web browser, streaming real PTYs from any machine over WebSocket and allowing persistent sessions, AI agent interaction, and full‑screen TUI control.
Browserland creates a virtual desktop inside a single browser tab where each window is a live terminal backed by a real PTY on a host machine. Brokers on each host register agents, and the browser connects to one or many brokers (via token‑authenticated WebSocket) to display, tile, split, and drag terminals across virtual desktops.
pip install -e .
python -m webterm.broker # prints a URL with a token
python -m webterm.agent -- bash -l # Linux example
python -m webterm.mcptool # stdio MCP server
Q: Do I need to set a token manually?
python -m webterm.broker --print-token.Q: What happens to terminals when the browser closes?
Q: Can I launch arbitrary commands via the UI?
Q: How does the MCP server authenticate?
WEB_TERMINAL_MCP_TOKEN or webterm_mcp.json). The token is independent of the UI password.Q: Is Windows supported?
pip install -e "[windows]") to get ConPTY/WinPTY support.Q: Can I run Browserland without internet?
A remote control plane for every machine you own: install Browserland on each one, and a single browser tab becomes a desktop of live terminals across all of them — including the AI coding agents running inside.
Point a browser at one broker and you get a full windowed desktop of live terminals: tile them, tab them, split them, and drag them across virtual desktops. Each terminal is a real PTY running on some machine, streamed to the browser over a WebSocket. Add your other machines as hosts (token-auth'd, e.g. over Tailscale) and their terminals appear in the same desktop — launch a shell on the laptop, the desktop, and the server from one + menu, side by side.
The shells keep running even when no browser is attached. Close the tab, come back tomorrow, and the screen heals from a snapshot — exactly where you left it.
The name says it plainly: a whole little desktop — windows, terminals, and your
fleet of AI coding agents — living entirely in a browser tab. (webterm is the
Python package and module name.)
Browserland also exposes an MCP server, letting LLM harnesses drive the terminals directly — including full-screen TUIs.
⚠️ Breaking change: a token is now required on every connection
Browserland used to treat loopback as an auth exemption — with no
auth_tokenset (the shipped default), anything on the machine could read every terminal's title/pid/cwd, attach to any PTY, and reach host-wide file read/write. That was never sound:tailscale servein front of a127.0.0.1bind makes every tailnet request arrive from loopback, and any web page you have open can reach loopback too. A token is now required on every route and every interface, with no exemption and no opt-out.New install? Nothing to do. The broker mints its own token on first run and prints a ready-to-open URL. You never have to create one.
Were you running without a token? Then on your next start:
- The browser will ask for a password. Get it with
python -m webterm.broker --print-token— it's also minted intowebterm_token.jsonbeside your state store.- Terminals launched by the previous run cannot reconnect and must be relaunched once. Their shells keep running, but they were started without the token and it can't be injected into a live process. This is the one unavoidable loss — finish or accept whatever is in them first.
- Scripts calling
/sessions,/launchor/file/*over loopback now get401. SendAuthorization: Bearer <token>.Running a fleet? Set
auth_tokenyourself inbroker_config.jsonbefore the cutover, so every host shares one password you already know instead of a different minted value per machine.Full details, a does-this-affect-me table, and recovery steps:
docs/UPGRADING.md.
Browserland is two small programs and a browser:
pty.openpty on Linux, ConPTY/WinPTY on Windows. An agent runs a command
(bash, cmd.exe, an AI coding agent, anything), keeps a ring buffer of
recent output, and streams the terminal over a single WebSocket.The wire format is a deliberately small set of JSON frames over one WebSocket, so any producer that speaks them can register with the broker — Browserland's own agent is just the reference implementation.
┌─────────┐ binary ANSI + JSON ┌────────┐ /ws?session=<id> ┌─────────┐
│ agent │ ──────────────────▶ │ broker │ ──────────────────▶ │ browser │
│ (PTY + │ ◀────────────────── │ │ ◀────────────────── │ xterm.js│
│ ConPTY/ │ input/resize/ └────────┘ input/paste/resize └─────────┘
│ openpty)│ snapshot_please
└─────────┘
Because the PTY lives in the agent, not the browser, terminals survive browser reloads and even broker restarts — the agent reconnects with backoff and the browser's attach triggers a snapshot redraw from the ring buffer.
The part people miss: Browserland is a remote control plane, not just a terminal for the machine it runs on. Run a broker on each of your machines, then register them as hosts in the UI (Control Panel → Hosts — a name, a URL, and that broker's token; a Tailscale address works great). One browser tab then fronts the whole fleet:
one browser tab
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ broker │ │ broker │ │ broker │ one per machine
│ laptop │ │ desktop │ │ server │ (LAN / Tailscale)
└─┬─────┬─┘ └─┬─────┬─┘ └─┬─────┬─┘
▼ ▼ ▼ ▼ ▼ ▼
shell claude shell build shell claude your PTYs + agents
--hosts list lets
an AI harness enumerate and drive terminals across all brokers through
a single tool surface.docs/SETUP.md walks through joining machines step by
step.
The desktop with tiled and floating terminals
/file/* and per-mod store APIs.ctx API — a mod is one folder with a manifest and
an entry script, and it can add window kinds, per-terminal title-bar
widgets, taskbar chips, Control-Panel settings, and in-app help pages.pty.openpty; Windows auto-selects ConPTY or
WinPTY — even headless/detached agents acquire a hidden console and re-enable
Ctrl-C so they run ConPTY with a working interrupt and live resize, falling
back to WinPTY only if console acquisition fails (or when forced via
--pty-backend winpty).claude / codex / grok / opencode / hermes) and tracks live OSC title +
working directory, with an opt-in per-window git-status widget (the git
mod).The desktop's app windows and most of its optional chrome ship as mods —
the terminal pipeline, window manager, the multi-host connection model, and
MCP surfaces stay core (only the optional sharing of that host list across
browsers is a mod). A mod is a self-contained folder under webterm/broker/mods/<id>/
holding a manifest (mod.json), one entry script, and optional CSS + an
in-app help page. Mods are trusted first-party code: the broker splices an
explicit allow-list of them into the single served page (there is no runtime
plugin install). Each registers through a versioned ctx API that exposes
per-terminal-window hooks, app-window kinds (which appear in the + launch
menu), taskbar chips, Control-Panel settings, a durable per-mod server store
with revision history, an in-desktop copy/paste observer, and help cards.
A mod's settings sync across your browsers via the broker's shared state;
its enable/disable toggle is deliberately per-browser — flip it in
Control Panel → Mods. A broker-side mods_enabled master switch gates
the whole system, and a broker can pin individual mods on or off for every
browser that loads its page. The desktop ships with eighteen:
| Mod | What it adds | Default |
|---|---|---|
theme |
color-scheme picker for the desktop chrome | on |
pattern |
desktop background patterns | on |
clock |
taskbar clock with timezone picker | on |
help |
the in-app help window + ? chip | on |
sticky |
sticky notes | on |
editor |
CodeMirror 6 text editor | on |
file-manager |
dual-pane file manager | on |
task-manager |
live per-host process list | on |
scratchpad |
server-backed notes, synced across browsers with revision history | on |
agent-docs |
AGENTS.md / CLAUDE.md one-click openers on terminal title bars | on |
recorder |
terminal session recorder + fixed-size player (speed, continuous rewind, timestamped notes) | on |
host-registry |
optional shared broker list — publish/pull your host list across browsers | on |
mousemode |
title-bar chip while a full-screen app owns the mouse | on |
mod-sync |
copy this broker's mod setup to your other brokers (all or selected) | on |
git |
per-terminal git branch + dirty-state widget | off |
aistatus |
AI-provider status chip + window | off |
clipboard |
rolling history of copies/pastes made through the desktop | off |
termfont |
terminal font picker | off |
clipboard and aistatus are off by default on principle — clipboards carry
secrets, and status polling talks to third-party endpoints; git and
termfont are simply opt-in preferences. Enabling any of them is one click
in the Mods pane.
The broker exposes a token-gated /mcp/* HTTP API and ships a stdio
MCP server (webterm.mcptool), so any MCP
client or AI harness can list, observe, drive, and launch terminals. The
agents are just producers; the broker stays the sole authority — every MCP call
is gated by the same per-window access modes and the master enable switch.
read_screen renders the current
screen of a live terminal by replaying its PTY ring buffer through
pyte, so a harness can read full-screen
apps (btop, htop, vim, less) — not just line-oriented scrollback — and
send_input types into them. Without pyte the read falls back to a
dependency-free in-house renderer that still returns a bounded rendered
grid (degraded: true is now reserved for a rare last-ditch raw decode).list_terminals enumerates running
sessions (id, title, cwd, agent, kind, cols/rows, mode), so a harness can
attach to the exact console a person is using right now, read its state, and
(in readwrite) drive it. Sessions persist across browser reloads and broker
restarts, so the handle stays valid.Each tool maps to a broker endpoint and returns its JSON. Window ids are
namespaced "<host>:<int>" strings so one server can front several brokers (see
Multi-host below); with a single broker the host is default
("default:12345").
| Tool | Endpoint | Notes |
|---|---|---|
mcp_info(host?) |
GET /mcp/info |
feature flags (allow_launch, default_mode). Omit host → dict keyed by host name |
list_terminals |
GET /mcp/terminals |
{"terminals":[…], "errors":{host:msg}}: all hosts merged, each terminal's host set to the config name (broker's machine hostname preserved as machine_host) + namespaced id; a down host lands in errors without sinking the rest. Each terminal carries a pyte flag — false = the agent lacks pyte, so attr_runs/keyframe-repair are unavailable and sparse alt-screen frames are flagged partial only (#134) |
list_profiles(host?) |
GET /mcp/profiles |
launchable profile names + default. Omit host → dict keyed by host name |
read_screen(id) |
POST /mcp/read |
screen rendered as a bounded plain-text grid (pyte, or a dependency-free fallback); result carries content_hash, stable_hash (the cursor-blind digest — a cursor blink in place doesn't change it, a cursor move does), and idle_ms (best-effort ms since the last PTY output — absent from older agents, unreliable for a perpetually-animating app). Waits (one call, timeout_ms-bounded, mutually exclusive): wait_for_change / wait_for_text / wait_for_regex / wait_for_idle (block until the screen settles — stable_hash unchanged for N ms) |
send_input(id, data) |
POST /mcp/input |
target window must be in readwrite mode; newlines are sent as Enter (CR) so commands run (incl. on PowerShell) |
send_keys(id, keys, delay_ms?) |
POST /mcp/input |
send control/escape keys — ["C-c"], ["Esc"], ["Up","Enter"] — that plain text can't express; delay_ms (or a per-terminal set_pace default) writes one key per POST for a frame-polling TUI |
set_pace(id, pace_ms) |
POST /mcp/pace |
readwrite; set a per-terminal DEFAULT send_keys pacing (ms, capped 1000, 0 disables) so multi-key sends auto-pace without passing delay_ms — for a frame-polling TUI (Dwarf Fortress). Broker-local + ephemeral (resets on agent reconnect) |
reset_terminal(id) |
POST /mcp/reset |
readwrite; correlated round-trip that wipes the agent's screen-render buffer so the next read_screen starts clean (502 on a non-agent producer) |
flush_input(id) |
POST /mcp/flush |
readwrite; correlated round-trip that discards keystrokes queued to the app but not yet consumed — the input-side mirror of reset (502 on a non-agent producer; a no-op on a Windows/ConPTY agent) |
launch_terminal(profile?, cols=80, rows=24, title?, cwd?, host?) |
POST /mcp/launch |
broker must have allow_launch enabled; host required when multiple hosts are configured |
Multi-host. Pass --hosts (or $BROWSERLAND_MCP_HOSTS) a JSON array of
{name, url, token} descriptors to serve N brokers from one server process;
every id-taking tool routes on the "<host>:…" prefix. The single
--broker-url/--token form is the one-host shorthand (default). See
webterm/mcptool/README.md for details.
The
send_inputtool maps newlines indatato a carriage return — the byte a real Enter key sends — so a line submits on PowerShell/PSReadLine (which treats a bare line-feed as a soft continuation) and on a Unix shell alike. The rawPOST /mcp/inputendpoint stays verbatim: drive it directly to send a literal LF or hand-crafted control/escape bytes.
Access is layered and opt-in — nothing is reachable until you turn it on:
/mcp/* call returns
403 mcp_disabled.off / read / readwrite, with a global
default_mode for new windows. off hides a window entirely; read allows
observation; readwrite additionally allows send_input.allow_launch is a separate gate for launch_terminal.auth_token
(the UI password): the broker pins it with WEB_TERMINAL_MCP_TOKEN (or the
webterm_mcp.json sidecar), and the MCP client passes the same secret via
BROWSERLAND_MCP_TOKEN (as the harness examples below show).claude mcp add browserland \
--env BROWSERLAND_MCP_TOKEN=… \
--env BROWSERLAND_MCP_URL=http://127.0.0.1:4445 \
-- python -m webterm.mcptool
Any other stdio MCP client (Hermes, your own, …) registers the same way — point it at the launch command and pass the two env vars:
{
"mcpServers": {
"browserland": {
"command": "python",
"args": ["-m", "webterm.mcptool"],
"env": {
"BROWSERLAND_MCP_TOKEN": "…",
"BROWSERLAND_MCP_URL": "http://127.0.0.1:4445"
}
}
}
}
Or run the server directly, talking to the local broker:
BROWSERLAND_MCP_TOKEN=… python -m webterm.mcptool
BROWSERLAND_MCP_TOKEN=… browserland-mcp --broker-url http://127.0.0.1:4445
For the full HTTP contract, error table, and config sidecar, see
docs/TECHNICAL.md and
webterm/mcptool/README.md.
You need Python ≥ 3.9. Install from a checkout:
pip install -e .
# broker (default 127.0.0.1:4445) — mints a token on first run and prints
# the URL to open, token included
python -m webterm.broker
# the token again, any time (never mints one):
python -m webterm.broker --print-token
# an agent running cmd.exe, registered with the local broker
python -m webterm.agent -- cmd.exe
# then open the printed http://127.0.0.1:4445/?token=... and click the
# session (or "new terminal")
Windows agents also need a PTY backend: pip install -e ".[windows]" (pulls in
pywinpty).
./launchers/run-broker.sh # broker
./launchers/run-agent.sh -- bash -l # agent
The launcher scripts bootstrap a virtualenv with the runtime dependencies on
first run. The broker prints the URL to open, token included; python -m webterm.broker --print-token reprints it later. run-agent.sh picks the token
up from webterm_token.json automatically, so a hand-started agent on the same
box needs no setup.
pip install -e . # core: broker + agent
pip install -e ".[windows]" # + pywinpty (Windows PTY backend)
pip install -e ".[pyte]" # + pyte (tier-2 snapshot rendering)
pip install -e ".[procs]" # + psutil (task manager, agent badge, live cwd)
pip install -e ".[mcp]" # + the stdio MCP server (Python ≥ 3.10)
pip install -e ".[dev]" # + pytest for the test suite
psutil (the procs extra) is best-effort: it powers the task-manager
process list, the foreground-agent badge, and live-cwd tracking.
Without it the agent still runs and still destroys windows — those three views
just degrade (empty list / no badge / no cwd).
Mix and match, e.g. pip install -e ".[pyte,mcp,dev]". The mcp extra requires
Python ≥ 3.10 (the MCP SDK), while everything else runs on Python ≥ 3.9.
See MCP & AI agent access for running the server.
| Path | What |
|---|---|
webterm/agent/ |
Headless producer: PTY backends, output ring buffer, OSC-title sniffer, reconnecting WebSocket client |
webterm/broker/ |
Web server: desktop UI (ui.py assembles the served page from ordered *.html/*.css/*.js fragments), /ws relay, producer WS, session list, profiles-only launch |
webterm/broker/mods/ |
The bundled desktop mods — one folder per mod: mod.json manifest, entry script, optional CSS + in-app help page |
webterm/mcptool/ |
The shipped stdio MCP server wrapping the broker's /mcp/* API |
webterm/protocol.py |
The single source of truth for the JSON frame shapes |
launchers/ |
venv-bootstrapping run scripts (and systemd units) for both OSes |
tests/ |
pytest suite: protocol, snapshots, agent↔broker integration, real-PTY round trips |
New here, or setting up more than one machine? Start with
docs/SETUP.md — the onboarding guide: the broker / agent /
browser mental model, joining machines over Tailscale via Control Panel → Hosts,
what not to hand-edit, and running the broker unattended in the background
(Windows Task Scheduler / Linux systemd). It's written to be followed by a human
or a coding agent.
Upgrading an existing install? docs/UPGRADING.md is
the breaking-change record — what changed, whether it affects you, and how to
recover. Most recently: a token is now required on every connection,
including loopback (#142), which strands terminals launched by a previously
tokenless broker.
Adding or editing launch profiles (WSL / zsh / PowerShell / Git-Bash) is covered
in docs/PROFILES.md — the recipe catalog, the three
profile fields, the webterm_profiles.json sidecar-vs-broker_config rule, and
the browser-realm-only editing model.
The full engineering reference lives in docs/TECHNICAL.md:
Released under the MIT License — see LICENSE.
not the.primeagen@gmail.com but hi :)
Please log in to share your review and rating for this MCP.
Explore related MCPs that share similar capabilities and solve comparable challenges
by modelcontextprotocol
A Model Context Protocol server for Git repository interaction and automation.
by zed-industries
A high‑performance, multiplayer code editor designed for speed and collaboration.
by modelcontextprotocol
Model Context Protocol Servers
by modelcontextprotocol
A Model Context Protocol server that provides time and timezone conversion capabilities.
by cline
An autonomous coding assistant that can create and edit files, execute terminal commands, and interact with a browser directly from your IDE, operating step‑by‑step with explicit user permission.
by upstash
Provides up-to-date, version‑specific library documentation and code examples directly inside LLM prompts, eliminating outdated information and hallucinated APIs.
by daytonaio
Provides a secure, elastic infrastructure that creates isolated sandboxes for running AI‑generated code with sub‑90 ms startup, unlimited persistence, and OCI/Docker compatibility.
by continuedev
Enables faster shipping of code by integrating continuous AI agents across IDEs, terminals, and CI pipelines, offering chat, edit, autocomplete, and customizable agent workflows.
by github
Connects AI tools directly to GitHub, enabling natural‑language interactions for repository browsing, issue and pull‑request management, CI/CD monitoring, code‑security analysis, and team collaboration.