YaviControl MCP Server
by Yavinesh2025
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).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues