Skip to main content
Glama
README.md
# cursor-browser-cli

**Drive Cursor IDE’s built-in Browser Tab from any CLI agent or shell** — Grok Build, Claude Code, Codex, OpenCode, or plain terminal — without leaving Cursor and without spinning up a separate Chrome/Playwright stack.

Stay in a long chat in Cursor’s integrated terminal. Navigate, take **accessibility snapshots with refs**, click/type/fill by **ref**, wait for page state, lock the tab, screenshot, and inspect console/network/DOM — all on the **same** Browser Tab you already see in the IDE.

```
Grok Build · Claude Code · Codex · OpenCode · shell
              │
              │  CLI  (cursor-browser)
              │  or MCP stdio  (mcp/server.mjs)
              ▼
     localhost HTTP  127.0.0.1:<port>
              │
              ▼
   Cursor extension (workspace host)
              │  cursor.browserView.*
              ▼
     Cursor Browser Tab  (this project window)
```

---

## Why this exists

### The problem

Cursor ships **Browser Automation** for its own **Cursor Agent** (chat/composer with built-in browser tools). That is great when you stay inside Cursor Agent.

Many people do **not** stay there. A common workflow is:

1. Open a project in Cursor  
2. Run **Grok Build**, **Claude Code**, **Codex**, or another CLI agent **in the integrated terminal**  
3. Still want to use the **in-IDE Browser Tab** for UI checks, local apps, auth flows, visual verification, etc.

Those CLI agents **cannot** call Cursor Agent’s built-in browser tools. Without something else, you end up:

- Opening extra Chrome windows and tabs  
- Re-explaining context across tools  
- Running a separate Playwright/CDP stack just to “see” the app  
- Or bouncing back into Cursor Agent only for browser steps  

That breaks the flow: you wanted one IDE window, one Browser Tab, and multiple agents that can share it.

### The solution

**cursor-browser-cli** is a small local stack that lets **any** agent (or script) control Cursor’s Browser Tab the same way Cursor Agent does — via:

| Surface | Best for |
|---------|----------|
| **`cursor-browser` CLI** | Fast, scriptable loops; agents that shell out; one-off commands |
| **MCP server** (`mcp/server.mjs`) | Claude Code, Codex, Grok, and any stdio MCP client with native tool calling |
| **Agent skill** (`skill/SKILL.md`) | Teaches agents the snapshot → ref → wait loop and multi-window routing |

You keep working in the CLI agent. The Browser Tab stays inside Cursor. No second browser product required for day-to-day agent work.

### What this is *not*

| Need | Use instead |
|------|-------------|
| Cursor’s own Agent chat/composer | Built-in browser tools (no extra install) |
| External desktop browser / cookie sessions / full profile | A separate external-browser tool of your choice |
| Headless CI / pure automation outside Cursor | Playwright, Puppeteer, or a headless browser MCP |
| Native macOS UI outside the Browser Tab | Other OS automation tools |

This project targets one job: **multi-agent access to Cursor’s Browser Tab from CLI/MCP while you work inside Cursor.** It is intentionally independent of any external-browser stack so both can coexist without coupling.

---


## Bridge recovery (agents / post-reboot)

The CLI talks to a **localhost HTTP bridge** started by the Cursor extension. The in-IDE Browser Tab can work while that bridge is down.

```bash
cursor-browser ensure            # setup + doctor + heal + verify (preferred)
cursor-browser ensure --reload   # after upgrades — multi-window extension code reload
cursor-browser doctor            # ports, extension, version skew, stale state
cursor-browser recover           # self-heal: multi-window restart tokens + wait
cursor-browser recover --reload  # force all windows to reload extension code
cursor-browser windows           # must list live project ports + versions
```

`recover` / `ensure` are safe to run from Grok/Claude/Codex when you see `ECONNREFUSED`. They self-heal with file triggers only:

1. Prunes dead `instances.json` / port files  
2. Writes `~/.cursor-browser-cli/request-restart` (extension watches + polls → `restartServer()`)  
3. If still down and Cursor hosts are running: writes `request-reload` (extension reloads window → re-activate)  
4. Waits until a bridge port answers  

If the extension is not loaded at all, recover cannot start the server from outside Cursor — run once:

**Cmd+Shift+P → `Cursor Browser CLI: Restart Server`** (or Reload Window).

The extension also **self-heals on a heartbeat**: if `activePort` is set but TCP is dead, it restarts the HTTP server without any CLI call.

Optional env: `CURSOR_BROWSER_NO_AUTO_RECOVER=1` disables auto-recover on connection errors.

## Features

### Multi-agent by design

- Same Browser Tab for **Grok Build**, **Claude Code**, **Codex**, **OpenCode**, shell scripts, and humans  
- **CLI** and **MCP** share the same extension HTTP API  
- **Workspace routing** so multiple Cursor windows do not step on each other  
- Optional **skill** files for Grok / Claude / agents that load `SKILL.md`

### Agent-grade interaction (ref model)

- **Accessibility snapshot with refs** (`e1`, `e5`, …) — YAML-style tree agents can read and act on  
- **Click / double-click / right-click / type / fill / hover by ref** (CSS selector fallback)  
- **`scroll`** window or element; **`select-option`** for `<select>`  
- **`wait`** for URL substring, visible text, ref, or CSS selector (reduces agent races)  
- **`--wait-nav`** after click to wait for URL change; **`--snap`** to attach a fresh snapshot after interact  
- **Lock / unlock** the tab during automation so accidental human input does not fight the agent  
- **Resize** viewport  
- **`open` / `nav`** print **snapshot text by default** (use `--json` for full payload)  

### Fast, CLI-first

- Single binary-style script on your `PATH`: `cursor-browser`  
- Low overhead: Node built-ins only, loopback HTTP, no runtime npm deps  
- Multi-window: `--workspace <folder>` or match `cwd`  
- **Single-tab policy**: reuse one tab; `close` extras for predictable automation  

### Under the hood (debug like DevTools)

- **Screenshot** (PNG path; MCP can return image content when available)  
- **inspect** — meta, counts, headings, links, inputs, body text  
- **console** / **network** via Cursor `getConsoleLogs` / `getNetworkRequests`  
- **eval** — run page JavaScript  

### Local and contained

- Listens on **127.0.0.1 only**  
- Per-window port (default base **17373**, auto-increments if busy)  
- State under `~/.cursor-browser-cli/`  
- Status bar shows `project :port` so you know which window is listening  

---

## Requirements

- **Cursor IDE** with Browser Tab / `cursor.browserView.*` APIs available  
- **Node.js ≥ 18** (CLI + MCP; no production npm dependencies)  
- macOS / Linux / Windows (where Cursor runs)

---

## Install (recommended: npm)

```bash
npm install -g cursor-browser-cli
```

That installs:

| Piece | What you get |
|-------|----------------|
| **CLI** | `cursor-browser` on your PATH |
| **MCP** | `cursor-browser-mcp` on your PATH |
| **Extension** | Copied into `~/.cursor/extensions/` (via `postinstall`) |
| **Skills** | Agent skill templates when those skill roots exist |

If the extension step was skipped (e.g. `npm i --ignore-scripts`), run:

```bash
cursor-browser setup
```

Then **reload each Cursor window** you use:

`Cmd+Shift+P` (or `Ctrl+Shift+P`) → **Developer: Reload Window**

Confirm the status bar shows something like `your-project :17375`, then:

```bash
cursor-browser windows
cursor-browser --workspace <project-folder> open https://example.com
cursor-browser --workspace <project-folder> snapshot
```

### Skip automatic setup

```bash
CURSOR_BROWSER_SKIP_SETUP=1 npm install -g cursor-browser-cli
cursor-browser setup   # when ready
```

### From source (optional)

```bash
git clone https://github.com/bcharleson/cursor-browser-cli.git
cd cursor-browser-cli
npm install          # runs setup
# or: ./scripts/install.sh
```

### Extension commands (inside Cursor)

| Command palette | Purpose |
|-----------------|---------|
| **Cursor Browser CLI: Show Status** | Health + workspace/port |
| **Cursor Browser CLI: Restart Server** | Restart the localhost HTTP server |

### Settings

| Setting | Default | Meaning |
|---------|---------|---------|
| `cursorBrowserCli.port` | `17373` | Preferred port (falls through if busy) |
| `cursorBrowserCli.enabled` | `true` | Start the HTTP server on activation |

---

## Quick start (agent loop)

This is the loop CLI agents should follow:

```bash
# 0) Discover this project → bridge port (required with multiple Cursor windows)
cursor-browser windows          # list: project → port
cursor-browser pin              # this cwd's project + port + export lines
eval $(cursor-browser pin --export)
# sets CURSOR_BROWSER_WORKSPACE + CURSOR_BROWSER_CLI_PORT
# prefer workspace name over port (ports can change after recover)

# or pin explicitly:
export WS=my-app   # folder name of the Cursor workspace

# 1) One clean tab + navigate → snapshot with [ref=e…] printed
cursor-browser --workspace $WS close
cursor-browser --workspace $WS open http://localhost:3000

# 2) Interact by ref from the snapshot
cursor-browser --workspace $WS click e5
cursor-browser --workspace $WS fill e3 "hello"
cursor-browser --workspace $WS press Enter

# 3) Wait for navigation / UI (avoid races)
cursor-browser --workspace $WS wait --url /results --timeout 15000
cursor-browser --workspace $WS wait --text "Success"

# 4) Visual / debug
cursor-browser --workspace $WS screenshot /tmp/out.png
cursor-browser --workspace $WS inspect
cursor-browser --workspace $WS console
```

**Rules of thumb**

1. **Pin first** — `windows` → `pin` → env or `--workspace` (never guess with multi-window).  
2. Take a **fresh `snapshot`** after navigation or large DOM changes before using refs.  
3. Prefer **ref** (`e12`) over CSS when the snapshot provides one.  
4. Prefer **`open` / `nav`** (they return snapshots) over bare navigate without a follow-up snap.  
5. Use **`wait`** after clicks that change URL or content.  
6. Use **`close`** if extra tabs pile up; keep one tab for reliability.  
7. With multiple Cursor windows, always pass **`--workspace`** (or pin via `eval $(cursor-browser pin --export)`).

---

## CLI reference

```text
cursor-browser [--workspace NAME|PATH] [--port N] <command> [args]
```

### Global flags

| Flag | Alias | Description |
|------|-------|-------------|
| `--workspace <name\|path>` | `-w`, `--project` | Target Cursor window by workspace folder name or absolute path |
| `--port <n>` | `-p` | Force a specific bridge port (from `pin` / `windows`) |
| `--json` | | Print full JSON (large trees stripped) instead of snapshot text |
| `--export` | | With `pin` / `resolve`: print shell exports only (`eval $(cursor-browser pin --export)`) |
| `--snap` | `--snapshot` | After interact, attach a fresh ref snapshot |
| `--wait-nav` | `--wait-navigation` | After click, wait for URL change |
| `--help` | `-h` | Show usage |

### Environment variables

| Variable | Purpose |
|----------|---------|
| `CURSOR_BROWSER_WORKSPACE` | Default workspace when `--workspace` is omitted |
| `CURSOR_BROWSER_CLI_PORT` | Default port when `--port` is omitted |

Legacy env names from earlier package renames may still be read by clients for compatibility.

### Routing commands

| Command | Description |
|---------|-------------|
| `windows` | List live bridges (project → port) |
| `pin` / `resolve` | Discover this project + port; agent pin step (`--export` for shell) |
| `whoami` / `status` / `health` | Resolved target + health + pin exports |
| `probe` | Low-level reachability check |

### Tabs and navigation

| Command | Description |
|---------|-------------|
| `open <url>` | Single-tab open/reuse + navigate + **snapshot** |
| `nav <url>` / `navigate <url>` | Navigate active tab + **snapshot** |
| `tabs` | List Browser Tab view IDs |
| `close [viewId]` | Close extras / specific tab (single-tab hygiene) |
| `select <viewId>` | Select a tab by view ID |
| `lock` / `unlock` | Lock tab from human input during automation |
| `back` / `forward` / `reload` | History and reload |
| `url` / `title` | Current URL or document title |

### Interaction (prefer refs from snapshot)

| Command | Description |
|---------|-------------|
| `snapshot` / `snap` / `refs` | Accessibility tree with refs (`e1`, …). Interactive by default |
| `click <ref\|css>` | Click element (`--snap`, `--wait-nav` optional) |
| `dblclick <ref\|css>` | Double-click |
| `rightclick <ref\|css>` | Right-click / context menu |
| `type <ref\|css> <text>` | Type (append) into element |
| `fill <ref\|css> <text>` | Clear and fill element |
| `select-option <ref\|css> <value\|label>` | Choose a `<select>` option |
| `scroll [ref\|css] [--y N] [--top N]` | Scroll window or element |
| `hover <ref>` | Hover by ref |
| `press <key>` | Key press (`Enter`, `Tab`, `Escape`, …) |

### Wait, viewport, capture, debug

| Command | Description |
|---------|-------------|
| `wait` / `wait-for` | Poll until condition (see flags below) |
| `resize <W> <H>` | Resize viewport |
| `screenshot [path.png]` | Capture viewport (default under `/tmp`) |
| `inspect` / `dom` | Structured page summary |
| `console` / `logs` | Console messages |
| `network` | Network requests |
| `eval` / `evaluate <js>` | Run JavaScript in the page |

#### `wait` flags

| Flag | Meaning |
|------|---------|
| `--url <substr>` | URL contains substring |
| `--text <str>` | Page/snapshot text contains string |
| `--ref <eN>` | Ref exists / is available |
| `--selector <css>` | CSS selector matches (also positional) |
| `--timeout <ms>` | Max wait (default **30000**) |

Examples:

```bash
cursor-browser --workspace my-app wait --url /dashboard --timeout 15000
cursor-browser --workspace my-app wait --text "Welcome"
cursor-browser --workspace my-app wait --ref e12
cursor-browser --workspace my-app wait --selector "button.save"
```

### Multi-window routing order

When you have several Cursor projects open, the CLI picks a target in this order:

1. `--port` / `CURSOR_BROWSER_CLI_PORT`  
2. `--workspace` / `CURSOR_BROWSER_WORKSPACE` / `CURSOR_BROWSER_PROJECT`  
3. Match current `cwd` to a registered workspace folder (strong match only)  
4. Single open instance  
5. **Fail closed** — never silently open another project when multiple bridges are live  

Shared `~/.cursor-browser-cli/port` is only used when **no** instances are registered (recovery), not as a multi-window fallback.

```bash
cursor-browser windows
cursor-browser pin
eval $(cursor-browser pin --export)
cursor-browser whoami
cursor-browser open http://localhost:3000
# or without env:
cursor-browser --workspace my-app open http://localhost:3000
```

`pin` / `resolve` output (human):

```text
project:  my-app
path:     /Users/you/Developer/my-app
port:     17375
how:      cwd (score 100)
live:     yes

# Pin this project for the rest of the shell/session:
export CURSOR_BROWSER_WORKSPACE='my-app' CURSOR_BROWSER_CLI_PORT=17375
```

---

## MCP server

Stdio MCP server for agents that prefer tools over shelling out.

After `npm install -g cursor-browser-cli`, the bin **`cursor-browser-mcp`** is on your PATH.

### Register

```bash
# Grok Build
grok mcp add cursor-browser -- cursor-browser-mcp

# Claude Code
claude mcp add cursor-browser -- cursor-browser-mcp
```

Any stdio MCP client:

```json
{
  "mcpServers": {
    "cursor-browser": {
      "command": "cursor-browser-mcp"
    }
  }
}
```

Fallback (from a clone or if the bin is not on PATH):

```json
{
  "mcpServers": {
    "cursor-browser": {
      "command": "node",
      "args": ["/absolute/path/to/cursor-browser-cli/mcp/server.mjs"]
    }
  }
}
```

Optional env on the server process:

- `CURSOR_BROWSER_WORKSPACE`  
- `CURSOR_BROWSER_CLI_PORT`  

Or pass `workspace` on each tool call. **With multiple Cursor projects open, `workspace` is required** (MCP fails closed instead of opening the wrong project).

### Agent pin flow (MCP)

1. `browser_windows` — list project → port  
2. `browser_resolve` (or `browser_pin`) — returns `workspace`, `port`, and pin instructions  
3. Pass that `workspace` on every subsequent `browser_*` call  

### Tools

All tools accept optional **`workspace`** (project folder name or path) unless noted. With multiple live bridges, pass it every time.

| Tool | Purpose |
|------|---------|
| `browser_windows` | List live bridges (project → port) |
| `browser_resolve` / `browser_pin` | Discover project + port for this agent; pin before open |
| `browser_status` | Health + workspace for a window |
| `browser_open` | Open/reuse single tab, navigate, return ref snapshot |
| `browser_navigate` | Navigate active tab + snapshot |
| `browser_snapshot` | Accessibility snapshot with refs (`interactive` optional) |
| `browser_click` | Click by `ref` or `selector` (`snapshot`, `waitNavigation` optional) |
| `browser_dblclick` | Double-click |
| `browser_rightclick` | Right-click / context menu |
| `browser_type` | Type (append) by `ref` or `selector` |
| `browser_fill` | Clear + fill by `ref` or `selector` |
| `browser_scroll` | Scroll window or element |
| `browser_select_option` | Choose a `<select>` option by value/label/index |
| `browser_hover` | Hover by `ref` |
| `browser_press` | Press key (`Enter` submits forms) |
| `browser_wait` | Wait for URL/text/ref/selector (`timeoutMs`, etc.) |
| `browser_lock` / `browser_unlock` | Tab lock |
| `browser_resize` | Viewport size (`width`, `height`) |
| `browser_screenshot` | PNG (+ image content when data URL is available) |
| `browser_inspect` | DOM/meta/links/inputs/body summary |
| `browser_console` | Console log dump |
| `browser_network` | Network request dump |
| `browser_evaluate` | Run `script` in page |
| `browser_tabs` | List view IDs |
| `browser_url` / `browser_title` | Current URL / title |
| `browser_back` / `browser_forward` / `browser_reload` | History / reload |

**Screenshot note:** When the page returns a data URL, the MCP layer can attach an **image** content block for vision-capable models, plus a text payload with the saved path.

---

## Skills

`npm install -g` / `cursor-browser setup` copies `skill/SKILL.md` to:

- `~/.grok/skills/cursor-browser/SKILL.md`  
- `~/.claude/skills/cursor-browser/SKILL.md`  
- `~/.agents/skills/cursor-browser/SKILL.md`  
- `~/.cursor/skills/cursor-browser/SKILL.md`  
- `~/.codex/skills/cursor-browser/SKILL.md`  

The skill describes **only** the Cursor Browser Tab. It does not depend on or install any external-browser stack.

The skill teaches agents:

- Always route the correct Cursor window (`windows` / `--workspace`)  
- Preferred **snapshot → ref click/fill → wait** loop  
- When to screenshot, inspect, console, network  
- Failure modes (stale refs, wrong project, connection refused)

---

## Architecture

```
┌─ Cursor window: your-project ──────────────────────────┐
│  Extension host HTTP  127.0.0.1:1737x                   │
│         │                                              │
│         │  cursor.browserView.*                        │
│         ▼                                              │
│  Browser Tab                                           │
│  · refs via data-cursor-ref after snapshot             │
│  · DOM click/type/fill (not raw CDP Input)             │
└────────────────────────────────────────────────────────┘
                    ▲
                    │  loopback only
     CLI  ·  MCP  ·  scripts
   (Grok / Claude Code / Codex / shell)
```

| Piece | Role |
|-------|------|
| `extension/` | VS Code/Cursor extension: HTTP API, workspace registry, status bar, `cursor.browserView.*` |
| `cli/cursor-browser` | Multi-window client; resolves port; pretty-prints actions |
| `mcp/server.mjs` | Stdio MCP → same HTTP actions |
| `skill/SKILL.md` | Agent instructions for the preferred loop |
| `scripts/install.sh` | CLI symlink, extension copy, skills, MCP hints |

**On-disk state** (`~/.cursor-browser-cli/`):

| File | Purpose |
|------|---------|
| `instances.json` | Registered windows (workspace paths, ports, PIDs) |
| `port` | Last/default port hint |
| `bridge.log` | Extension host log |

Ports start at **17373** and try up to **32** candidates if the preferred port is taken (one port per Cursor window).

---

## How agents should use it

### Typical product/UI session

1. `browser_windows` or `cursor-browser windows` — list project → port  
2. `browser_resolve` / `cursor-browser pin` — pin this project (env or `workspace` arg)  
3. `open` / `browser_open` on `http://localhost:…` or staging URL  
4. Read refs from the snapshot  
5. `click` / `fill` / `press`  
6. `wait` for URL or text  
7. New `snapshot` after major UI change  
8. `screenshot` or `inspect` when stuck  

### Multi-project day

Always discover then pin:

```bash
cursor-browser windows
cursor-browser pin
eval $(cursor-browser pin --export)

cursor-browser --workspace project-a open http://localhost:3000
cursor-browser --workspace project-b open http://localhost:4000
```

Or set `CURSOR_BROWSER_WORKSPACE` in that agent’s shell profile / MCP env.

### Switching agents mid-project

The Browser Tab is owned by the **Cursor window**, not by a single agent process. You can:

1. Use Cursor Agent for some steps (built-in tools)  
2. Switch to Claude Code / Grok / Codex in the terminal  
3. Continue with `cursor-browser` or MCP on the **same** tab  

That continuity is the whole point of this tool.

---

## Security

- The HTTP server binds to **loopback only** (`127.0.0.1`).  
- Anyone who can reach that port on your machine can drive the Browser Tab (navigate, click, **run page JS**, read console/network).  
- Treat it like a **local debugger**: do not tunnel or expose the port; do not run on untrusted multi-user machines without isolation.  
- `eval` / `browser_evaluate` execute arbitrary page JavaScript — only run code you trust.

---

## Troubleshooting

| Symptom | Fix |
|---------|-----|
| Connection refused / empty `windows` | **`cursor-browser doctor`** then **`cursor-browser recover`**. Clears stale ports, asks extension to restart (file trigger), optionally reloads Cursor. Browser Tab open ≠ bridge up. |
| After reboot bridge dead | Extension host did not re-bind HTTP. `recover` → wait → `windows`. Ensure **Cursor Browser CLI** extension is Enabled. |
| Wrong project / wrong app | `cursor-browser windows` → `pin` → `--workspace <name>` or `eval $(cursor-browser pin --export)` |
| Multiple windows; cannot guess | Pass `--workspace` / MCP `workspace`; CLI and MCP fail closed |
| Stale ref / element not found | New `snapshot` / `open` / `nav`; never reuse refs across big DOM changes |
| Race / empty or intermediate page | `wait --url` / `--text` / `--ref` / `--selector` with a higher `--timeout` |
| Extra tabs / flaky targeting | `close`, then `open` or `nav` to enforce single-tab |
| MCP tools missing | Re-register MCP with **absolute** path to `mcp/server.mjs`; restart the agent |
| CLI not found | Ensure `~/.local/bin` is on `PATH`, or call the script by full path |
| CDP Input blocked | Expected — use `click` / `type` / `fill` / `press` (DOM events), not raw CDP Input |

Logs: `~/.cursor-browser-cli/bridge.log`

---

## Repo layout

```text
cursor-browser-cli/
├── README.md
├── LICENSE                 # MIT
├── package.json            # npm package (bins + postinstall setup)
├── cli/
│   └── cursor-browser      # CLI entry (Node)
├── extension/
│   ├── package.json
│   ├── extension.js        # HTTP API + cursor.browserView.*
│   └── snapshot.js         # Accessibility snapshot + refs
├── mcp/
│   └── server.mjs          # MCP stdio server → bin: cursor-browser-mcp
├── scripts/
│   ├── setup.js            # extension + skills install
│   └── install.sh          # thin wrapper → setup.js
└── skill/
    └── SKILL.md            # Agent skill template
```

**No runtime npm dependencies** — Node built-ins only (`http`, `fs`, `path`, `os`, etc.).

---

## Development notes

- Extension activation: `onStartupFinished`  
- Preferred port configurable via `cursorBrowserCli.port`  
- Clients still understand legacy state dirs / names from earlier renames for smoother upgrades  
- After changing extension code: `cursor-browser setup` (or `npm run setup`) and **Reload Window**  
- Publish: `npm publish` (requires npm login)

---

## License

[MIT](./LICENSE) © Brandon Charleson

---

## Contributing / sharing

This repo is intended to be shared as open source so CLI agents in Cursor can share one Browser Tab.

If you publish or fork:

1. Keep the **why** clear: multi-agent access to Cursor’s Browser Tab  
2. Document both **CLI** and **MCP** equally  
3. Stress **workspace routing** and the **snapshot → ref → wait** loop  

Issues and PRs that improve multi-window routing, snapshot quality, or agent docs are especially welcome.