Skip to main content
Glama
README.md
# YaviControl

**Let Claude Code and Codex drive the Chrome you're already signed into.**

Most browser-automation tooling for AI agents launches its own clean browser —
no cookies, no sessions, no logins. That's the right default for CI, and the
wrong one when you want an agent to read your Gmail, post from your LinkedIn,
pull a report out of a dashboard behind SSO, or check something on a site you
authenticated into six months ago.

YaviControl is a Manifest V3 extension plus a small local bridge. Load the
extension into your everyday Chrome profile and your agent works the browser
you already use — logged in, extensions and all. Load it into the managed
profile instead when you want isolation. Same tool, your choice.

```
Claude Code ─stdio(MCP)─▶ mcp/server.js ─HTTP─┐
                           ├─▶ bridge/server.js ─WS─▶ YaviControl extension ─▶ Chrome
Codex / CLI ─HTTP POST────── tools/yavi.js ───┘   (hub: HTTP + WS, dependency-free)
```

Everything is loopback-only and dependency-free apart from the MCP SDK.

## Why not Playwright MCP or Chrome DevTools MCP?

Use those when you want a disposable, reproducible browser — they're excellent
at it, and they're maintained by Microsoft and Google respectively.

Reach for YaviControl when the session *is* the point:

| | Playwright MCP / Chrome DevTools MCP | YaviControl |
| --- | --- | --- |
| Browser used | Spawns its own instance by default | Whichever profile you load the extension into |
| Your logins | Absent — fresh profile | Present, if you use your everyday profile |
| Setup | `npx`, nothing to install in Chrome | Load an unpacked extension once |
| Raw CDP | Partial | Full `debugger.*` passthrough |
| Trusted input | Varies | CDP clicks/keys that work on reCAPTCHA, Stripe, Turnstile |
| Best for | Tests, scraping public pages, CI | Anything behind a login you already have |

They are not mutually exclusive — plenty of people run both and pick per task.

## Requirements

- Google Chrome 116 or newer
- Node.js 18 or newer

## Quick start

```bash
git clone https://github.com/Yavinesh2025/YaviControl.git
cd YaviControl
npm --prefix mcp install
```

Then pick how you want the extension to run.

### Option A — your everyday Chrome (keeps your logins)

1. Open `chrome://extensions`
2. Enable **Developer mode**
3. **Load unpacked** → select the `extension/` folder
4. Start the bridge: `npm start` (or double-click `start-bridge.cmd`)

The toolbar badge shows **ON** in green once connected. This is the mode that
gives an agent access to your signed-in sessions — see
[Security notes](#security-notes) before you use it, because it means exactly
what it sounds like.

### Option B — managed profile (isolated, no logins)

```bash
node tools/launch-chrome.js
```

One command: starts the bridge, opens Chrome with a dedicated profile at
`%LOCALAPPDATA%\YaviControl\ChromeProfile`, loads the extension over CDP
(`Extensions.loadUnpacked`, which still works on Chrome 137+ after branded
Chrome dropped `--load-extension`), and verifies the handshake.

It also passes `--silent-debugger-extension-api`, `--no-default-browser-check`,
and `--hide-crash-restore-bubble`, so the debugger infobar never shifts page
geometry — which keeps trusted click coordinates accurate — and startup bubbles
never cover the page.

Windows shortcut: double-click `start-yavichrome.cmd`, or `npm run chrome`.

### Confirm it works

```bash
node tools/yavi.js doctor
```

`[READY] Browser control is available.` means the bridge and the extension are
both connected.

## Use it from Claude Code (MCP)

```bash
claude mcp add --transport stdio yavicontrol -- node "$PWD/mcp/server.js"
```

Or add it to `~/.claude.json` with an absolute path:

```json
{
  "mcpServers": {
    "yavicontrol": {
      "type": "stdio",
      "command": "node",
      "args": ["C:/path/to/YaviControl/mcp/server.js"]
    }
  }
}
```

Restart Claude Code, run `/mcp` to confirm `yavicontrol` is connected, then ask
for something like *"Go to news.ycombinator.com and list the top 5 story
titles."*

The MCP server starts the hub automatically. If no extension is connected it
will also launch the managed profile for you — set `YAVICONTROL_AUTOLAUNCH=0` to
disable that, which you want if you're using Option A.

**Tools:** `browser_navigate`, `browser_screenshot`, `browser_get_content`,
`browser_click`, `browser_type`, `browser_press_key`, `browser_evaluate`,
`browser_wait_for`, `browser_scroll`, `browser_list_tabs`, `browser_switch_tab`,
`browser_back`, `browser_forward`.

## Use it from Codex or the terminal (HTTP + CLI)

Start the hub if nothing else has:

```bash
node bridge/server.js     # or: npm start
```

Change the port with `YAVICONTROL_PORT=32200` (then save the matching URL in the
extension popup).

```bash
node tools/yavi.js health
node tools/yavi.js commands
node tools/yavi.js open https://example.com
node tools/yavi.js command tabs.query '{"active":true,"currentWindow":true}'
node tools/yavi.js command browser_click '{"selector":"#submit"}'
node tools/yavi.js command browser_type '{"selector":"[contenteditable]","text":"hello"}'
node tools/yavi.js eval active "document.title"
node tools/yavi.js cdp 123 Runtime.evaluate '{"expression":"location.href","returnByValue":true}'
node tools/yavi.js screenshot active screenshot.png
```

### HTTP API

`POST /command`:

```json
{ "command": "tabs.query", "params": { "active": true, "currentWindow": true } }
```

```json
{ "id": "cmd-...", "type": "response", "ok": true, "result": [] }
```

If more than one Chrome profile is connected, `GET /health` lists each
`extension.sessions[].sessionId`. Pass that `sessionId` in the command body, or
set `YAVICONTROL_SESSION_ID`, so commands can't silently hit the wrong profile.

Both front-ends converge on the same hub, so Claude Code and Codex can share one
running browser.

## Control surface

**High-level `browser_*` tools** — shadow-DOM-piercing selectors, wait and
scroll helpers, plus:

- `browser_click` is **trusted by default** (mode `"auto"`): a real CDP click
  that works on reCAPTCHA, Stripe, and Turnstile, with a synthetic fallback.
  `x`/`y` are CSS pixels, matching screenshot pixels 1:1.
- `browser_press_key` sends trusted CDP key presses (Enter, Tab, arrows, …).
- `browser_type` handles regular inputs **and** contenteditable rich editors
  (Gmail, Notion, Docs-style).
- `browser_screenshot` auto-scales the PNG so 1 image pixel = 1 CSS pixel,
  fixing the Windows DPI/zoom "clicked in the wrong place" problem.

**Low-level Chrome + CDP:**

- `tabs.*` / `windows.*` — navigation and window control
- `scripting.execute` — injected script functions
- `debugger.*` — raw Chrome DevTools Protocol
- `input.click` / `input.type` — via the debugger protocol
- `cookies.getAll`, `history.search`, `downloads.download`, `storage.*`
- `chrome.call` — call any available Chrome extension API by path

`node tools/yavi.js commands` prints the live list.

## Extension popup

The toolbar action shows live bridge state, a manual **Reconnect**, and the
WebSocket URL. The URL is deliberately restricted to `ws://127.0.0.1:<port>/ws`
or `ws://localhost:<port>/ws`.

## Extension ID

Pinned to:

```
kjgcjhjkedeiliffkaajbmbaenifdcna
```

Chrome normally derives an unpacked extension's ID from its install path, so it
would otherwise differ per machine and change whenever the folder moves. The
`key` field in `extension/manifest.json` holds the public half of an RSA
keypair, and Chrome derives the ID from that instead. `node tools/validate.js`
re-derives it and fails if the two ever drift apart.

The private key is not in this repository. It's needed only to sign a `.crx` —
the pinned ID survives without it:

```powershell
chrome.exe --pack-extension=extension --pack-extension-key=yavicontrol-key.pem
```

## Troubleshooting

Start with `node tools/yavi.js doctor`.

| Symptom | Recovery |
| --- | --- |
| Bridge is not reachable | Run `node bridge/server.js`, or `node tools/launch-chrome.js` for the full stack. |
| Bridge running, Chrome not connected | Confirm the unpacked extension is enabled, or open the managed profile with `node tools/launch-chrome.js`. |
| Popup says auto reconnect is paused | Choose **Reconnect**, or re-enable **Auto reconnect** and save. |
| More than one Chrome session connected | Run `doctor`, then set `YAVICONTROL_SESSION_ID` to the session you want. |
| Port already in use | Reuse the running bridge, or set `YAVICONTROL_PORT` and save the same port in the popup. |
| Extension updated while popup was open | Close and reopen the popup so it binds to the new service worker. |
| A Chrome internal page won't respond | Navigate to a normal `http://`/`https://` page — Chrome blocks extensions on internal and Web Store pages. |

## Validate

```bash
node tools/validate.js   # static checks: manifest, icons, pinned ID, command coverage
node tools/smoke.js      # isolated bridge + request-protection checks
node tools/test.js       # popup / reconnect / bridge regression tests
node tools/launch-chrome.js --temp-profile --exit-after-verify   # full handshake
```

## Security notes

This is authorised local browser automation for a single operator. It requests
broad Chrome permissions and exposes raw CDP on purpose. Read this section
before using Option A.

- **Running in your everyday profile means an agent can act as you** on every
  site you're signed into — read mail, send messages, move money. That is the
  feature, and it is also the risk. Use the managed profile if you aren't
  comfortable with that.
- **Any local process that can reach the bridge can drive your browser.** The
  bridge refuses non-loopback hosts, requires `Content-Type: application/json`,
  and rejects browser `Origin` headers, so a drive-by web page can't send
  commands. WebSocket clients must present a `chrome-extension://<id>` origin.
  But there is no per-caller authentication — run only software you trust.
- **`browser_evaluate`, `scripting.execute`, and `debugger.eval` run arbitrary
  JavaScript in the page by design.** `browser_evaluate` runs in the page's MAIN
  world and can be blocked by a strict page CSP; use `debugger.eval` there. Only
  send code you trust.
- **Chrome shows a "debugging this browser" banner** while a debugger-based
  command is attached. Expected. The managed profile suppresses it with
  `--silent-debugger-extension-api` so it can't shift page geometry; a manually
  loaded extension will still show it.
- **Not distributable via the Chrome Web Store.** Runtime evaluation of
  externally supplied code violates the store's Remote Code policy. Install by
  loading the extension unpacked.

## Licence

MIT — see [LICENSE](LICENSE).