Browser Controller MCP server
2026년 11월 14일 커밋MCP server와 extension을 통해 로그인 정보 및 쿠키를 포함한 기존 Chrome 브라우저 세션을 AI agent가 제어할 수 있게 합니다.
다른 이름 compnew2006/browser-controller
- 스타
- 9
- Tools
- 25
- 다운로드, 30일
- —
- 최신 버전
- —
- 마지막 커밋
- 2026년 11월 14일
- 라이선스
- MIT
Browser Controller MCP란?
Browser Controller은(는) MCP server입니다. MCP server와 extension을 통해 로그인 정보 및 쿠키를 포함한 기존 Chrome 브라우저 세션을 AI agent가 제어할 수 있게 합니다. 25개 도구를 제공합니다. 예: url, tabId, snapshot.
프로젝트 README에서 생성된 요약
Browser Controller MCP server 정보
Browser Controller를 사용하면 AI agent가 현재 실행 중인 Chrome, Edge 또는 Brave 브라우저 세션과 상호작용할 수 있습니다. Headless 자동화와 달리 이 도구는 기존 프로필을 사용하므로, agent가 재인증 과정 없이 실제 로그인 정보, 쿠키 및 인증된 세션에 접근할 수 있습니다. 로컬 데몬과 Chrome extension을 통해 작동하며, 모든 데이터는 클라우드 텔레메트리 없이 localhost를 통해 사용자의 머신에 안전하게 유지됩니다.
개발자는 Cursor, Claude Desktop, Windsurf와 같은 MCP 호환 클라이언트에서 이 기능을 사용할 수 있습니다. 이 시스템은 특정 탭 ID를 사용하여 여러 agent를 동시에 지원하므로, 사용자가 다른 탭을 탐색하는 동안 하나의 agent가 특정 탭에서 작업을 수행할 수 있습니다. 또한 Chrome DevTools Protocol을 통해 고정밀 상호작용을 제공하여 클릭과 타이핑이 신뢰할 수 있는 사용자 이벤트로 인식되도록 합니다. 서버에는 탐색, 요소 상호작용, JavaScript 평가를 위한 19개의 도구가 포함되어 있으며, 콘솔 로그 및 네트워크 요청 모니터링 기능도 제공합니다.
활용 사례
- 1자동 소프트웨어 검증AI agent가 로컬 애플리케이션으로 이동하여 UI와 상호작용함으로써 코드 수정 사항을 검증할 수 있습니다.
- 2멀티 에이전트 병렬 워크플로우서로 간섭 없이 서로 다른 브라우저 탭에서 여러 AI agent를 동시에 실행할 수 있습니다.
- 3디버깅 및 네트워크 검사웹 애플리케이션의 문제를 해결하기 위해 agent에게 콘솔 에러 분석이나 네트워크 트래픽 가로채기를 요청할 수 있습니다.
프로젝트의 README를 바탕으로 작성되었습니다. 모든 활용 사례는 해당 내용의 인용문과 대조하여 확인합니다.
Browser Controller MCP 도구 25
README에서 감지됨urltabIdsnapshotactionbrowserisolationaccesstrustedreadtargetquerymaxChars
| url | — | |
| tabId | — | |
| snapshot | — | |
| action | — | |
| browser | — | |
| isolation | — | |
| access | — | |
| trusted | — | |
| read | — | |
| target | — | |
| query | — | |
| maxChars | — |
도구 25개 모두 보기도구 수 줄이기
propertiesruleslistenersenableddefaultActionsiteRulesbuttonexpectobservehoverselectuntilactions
| properties | — | |
| rules | — | |
| listeners | — | |
| enabled | — | |
| defaultAction | — | |
| siteRules | — | |
| button | — | |
| expect | — | |
| observe | — | |
| hover | — | |
| select | — | |
| until | — | |
| actions | — |
구성
WS_PORT
| 이름 | 설명 | 유형 |
|---|---|---|
| WS_PORT | — |
자동 검사
2026년 11월 14일에 실행됨전체 보고서 →아직 인증되지 않았습니다. 서버는 80점 이상의 점수, 심각한 결함 없음, 그리고 검사에 충분한 데이터를 갖추어야 합니다. 검사 방식 · 점수를 높이기 위해 수정할 항목
- 소스 저장소 연결 및 접근 가능compnew2006/browser-controller is public and reachablegithub.com/compnew2006/browser-controller ↗통과
- 패키지가 이 저장소를 소스로 명시검사되지 않음
- 빌드 출처(Sigstore 증명)검사되지 않음
- 오픈 소스 라이선스 (OSI)MITspdx.org/licenses/MIT.html ↗통과
- 최근 1년 내 커밋 또는 릴리스Last commit or release 2026-11-09github.com/compnew2006/browser-controller ↗통과
- OpenSSF ScorecardOpenSSF Scorecard 6.9 / 10; weakest: Code-Review 0, Token-Permissions 0, CII-Best-Practices 0scorecard.dev/viewer/?uri=github.com/compnew2006/browser-con ↗부분 10/15
- 패키지 이름이 인기 패키지와 유사하지 않음검사되지 않음
- 패키지 및 해당 종속성에 중요한 보안 권고 없음검사되지 않음
- 모델에 대한 숨겨진 지침 없음25 tool descriptions and the README read; no override phrases, credential paths or hidden characters통과
- 파괴적인 도구에 라벨 표시됨No tool named like delete, drop or exec통과
- 원격 엔드포인트가 MCP 핸드셰이크에 응답검사되지 않음
README
GitHub에서 보기What this project solves
You ship a fix. Your agent says "done, please verify." You alt-tab to Chrome, navigate to the page, log in, click around, find the bug.
Your agent just wrote the code. It could also verify it. It already has your browser open right there. It just can't see it.
Now it can. Browser Controller gives any MCP-compatible AI agent (Cursor, Claude Desktop, Windsurf, …) direct control of the browser you already have open — your real sessions, your logins, your cookies. No headless browser, no fresh profile, no re-authentication.
Version 3.0 is a breaking release: 19 tools, one target locator, and text results with structured receipts for actions. See Migrating from 2.x.
Key capabilities
- Multiple agents at once. Cursor can drive tab 10 while Claude drives tab 11 — both through one shared daemon, neither blocking the other.
- Tab targeting, not "the active tab." Every page action names a
tabId. Move your mouse, switch tabs, watch YouTube — the agent keeps working on the tab you told it to. It never hijacks the page you're reading. - Tab ownership without lock calls. A tab that
browser_navigateopens for a session belongs to that session until it closes. Another session's call on that tab fails at once withTAB_LOCKED. - Per-tab isolation. Element refs are per page, and logs and network buffers are per tab. A ref from tab 10 can never click something in tab 20.
- Per-tab concurrency. Two actions on the same tab serialize (no races); actions on different tabs run in parallel.
- Agent-control shield. While a tool runs on a tab, you see a translucent blue inner frame and your input on that tab is blocked (mouse, keyboard, wheel). It disappears when the action finishes.
- Visible agent cursor (optional). Switch on Show agent cursor in the popup's Settings and agent mouse actions draw a pointer that glides to its target first. Off by default.
- Real, trusted input. Clicks, typing, keys, wheel and drags go through the Chrome DevTools Protocol, so the page sees
isTrustedevents and focus, Tab, Enter and autocomplete behave as they do for a person. There is no synthetic-event fallback: if the debugger cannot attach, input fails withDEBUGGER_UNAVAILABLE. The yellow "being debugged" banner shows while the debugger is attached. - One locator.
targettakes a ref (e12),text=Sign in, viewport coordinates (x,y), a CSS selector, or an accessible{ role, name, nth? }object. Shadow roots and same-origin iframes are searched; cross-origin iframes are listed as not readable. - Honest results. A success is compact text. A failure is an
isErrorresult whose text isCODE: message, so the agent acts on the code instead of guessing. - Batches.
browser_batchruns up to 50 tool calls in one round trip and stops at the first failing step (onError:"continue"runs the rest and names every failure). - The page is read after it settled. An action waits for the page to stop changing before the call returns, and a navigation waits for the page it loaded to draw, so the read that follows sees what the action produced, not the page before it.
browser_wait {until:"stable"}waits for the same thing on demand. - A session can be fenced in.
browser_tabs {action:"session"}opens the session for reading only (every call that changes something is refused), or isolates it (the agent touches the tabs it opened, and only those). - Console-style JavaScript.
browser_evaluateaccepts code as you'd type it in DevTools: top-levelawait, several statements withreturn, DOM nodes come back as readable descriptions. Over CDP, page CSP does not block it. - Console, network and mocks on demand.
browser_logsreads a tab's console (uncaught errors with stacks) and its network requests, with response bodies on request.browser_mockanswers or blocks one tab's requests, so the page really receives the fake response. - Frozen tabs don't freeze the agent.
browser_navigatereplaces a tab that stopped answering, and its result says so with anote:line naming the newtabId. - Several browsers. Every connected Chrome profile is its own connection.
browser_tabs { action: "profiles" }lists stable IDs, local labels, the default, and whether each profile has open tabs without switching the session. Use the ID to select a profile;automeans the earliest-connected default. Set a recognizable local label in the extension's Settings. - Authenticated local connection. Token + enrollment secret, so no other local process can silently drive your browser — and after a one-time
npm run setup:pairingthe extension pairs itself. Everything stays on localhost — no cloud, no telemetry. - Versioned compatibility handshake. The daemon and extension advertise a protocol version and capabilities before tool traffic is accepted. A peer from before 3.0 is refused with an actionable error instead of producing unexplained timeouts.
- Consoles and appliances. A hypervisor web client is an ordinary page to the agent, and the guest behind it is reachable through the same tools — the guest is a canvas, so it needs its own rules for reading, clicking and typing. The field guide is
docs/esxi-vm-console-automation.md.
How it works
Three pieces, all on your machine. Nothing leaves localhost. The daemon control plane is hard-bound to 127.0.0.1; there is no supported remote/n8n listener.
flowchart LR
subgraph Client["MCP client (Cursor / Claude / Windsurf)"]
A["agent"]
end
subgraph Thin["thin MCP client — mcp-server/dist/index.js"]
B["DaemonClient<br/>speaks MCP over stdio<br/>spawns daemon if not running<br/>gets its own sessionId"]
end
subgraph Daemon["daemon — single long-running process, owns port 7225"]
C["daemon.ts<br/>multiplexes N clients → 1 extension<br/>sessions · rate limit · heartbeats"]
D["bridge.ts (ExtensionBridge)<br/>HTTP /pair /status /kill<br/>WebSocket for the extension"]
end
subgraph Ext["Chrome extension (Manifest V3 service worker)"]
E["connection.js<br/>pairing · reconnect backoff"]
F["router.js<br/>tool dispatch"]
G["tab-concurrency.js<br/>per-tab mutex · ownership · shield"]
H["handlers/*<br/>interaction · inspection · evaluate · navigation · tabs<br/>logs · mock · emulate · cdp"]
I["page-exec.js safeExec<br/>chrome.scripting func: — CSP-safe"]
E --> F --> G --> H --> I
end
P["Page — your real sessions"]
A -->|stdio MCP| B
B -->|IPC socket + token| C
C --> D
D <-->|Authenticated WebSocket<br/>protocol version + capabilities| E
I --> P
Key idea: the first time any agent runs, the thin client spawns a background daemon that owns port 7225 and the extension connection. Every subsequent agent (even from a different MCP client) connects to that same daemon over a local IPC socket and gets its own sessionId. The extension sees one stable connection and routes each call to the exact tab the caller specified.
Quick Start
The project is not on the Chrome Web Store or npm — you install it from this repository. Two parts: the MCP server (runs on your machine, talks to your AI agent) and the Chrome extension (sits in your browser, executes commands).
Prerequisites: Node.js 22.18+ within the 22.x line, 24.11+ within the 24.x line, or 26+; and Chrome/Chromium/Edge. CI tests the minimum and latest releases of the 22, 24, and 26 lines. Later Node.js major versions are allowed by engines but are not covered by the current CI matrix.
1. Clone & build
git clone https://github.com/compnew2006/browser-controller.git
cd browser-controller
npm install
npm run build # compiles TypeScript → mcp-server/dist/
2. Load the Chrome extension
- Open
chrome://extensionsand enable Developer mode (toggle in the top right) - Click Load unpacked and select the
extension/folder from the cloned repo - Pin the Browser Controller icon to your toolbar
Gray dot = waiting for the daemon. Green = connected.
3. Add the MCP server to your client
Cursor: Settings → MCP → "Add new MCP server". Claude Desktop: edit claude_desktop_config.json. Windsurf: Settings → MCP. Any MCP-compatible client works.
Replace /path/to/browser-controller with the absolute path of your clone (Windows: use C:\\path\\to\\browser-controller\\mcp-server\\dist\\index.js):
{
"mcpServers": {
"browser-controller": {
"command": "node",
"args": ["/path/to/browser-controller/mcp-server/dist/index.js"]
}
}
}
By default the daemon names each connection after its parent IDE ("Cursor", "Claude", …). To override — e.g. when several agents share one IDE, or to label them by project — pass --agent <name> in the args. It takes priority over every auto-detection:
{
"mcpServers": {
"browser-controller": {
"command": "node",
"args": [
"/path/to/browser-controller/mcp-server/dist/index.js",
"--agent",
"My Project Agent"
]
}
}
}
The name appears in the popup's Connected Agents list. (You can also set the MCP_AGENT_NAME env var — equivalent.) Reconnecting with the same name replaces the old entry, so IDE restarts don't pile up duplicates.
4. Pair the extension with the daemon
Run this once from the repo root:
npm run setup:pairing
Then click Reload on Browser Controller in chrome://extensions. The extension pairs itself — nothing to copy or paste. Green dot = you're connected. Your agent can now see your browser.
What the command sets up:
- A small local helper (a Chrome native messaging host,
mcp-server/dist/native-host.js), registered with every Chromium browser it finds: Chrome (stable, Beta, Canary), Chromium, Edge and Brave. It is a per-user install (on Windows, underHKCU\Software\…\NativeMessagingHosts) and needs no admin rights. - Chrome lets only this extension's ID start the helper. An unpacked extension's ID comes from its folder path; the command prints the ID it computed, and it must match the one shown in
chrome://extensions. If you loaded the extension from another folder, pass its ID:npm run setup:pairing -- --extension-id <id>. - A launcher in
.native-host/that pins the currentnode, because a browser started from the Dock or Start menu does not see your shell'sPATH.
The extension asks the helper only when the daemon refuses its secret — the first time, and after you rotate the secrets — so a rotation needs no action either. Check the setup with npm run pairing:status (it never prints the secret) and remove it with npm run pairing:uninstall. Run npm run setup:pairing again after moving the repository or switching Node installations, since the launcher pins both paths. When pairing cannot happen, the popup's activity log says why and what to run.
Only the enrollment secret is needed — the extension fetches the auth token from the daemon by itself.
- Start the daemon once by asking your agent to "list my browser tabs". It creates the secrets in
~/.browser-controller/(Windows:%USERPROFILE%\.browser-controller\), and the MCP client prints the enrollment secret to its log on first run. - Copy the
secretvalue:cat ~/.browser-controller/enrollment.json - Click the extension icon → Settings tab → paste it into Enrollment Secret (leave the port at
7225unless you changedWS_PORT).
These secrets protect the local control channel. To rotate them without deleting vault and memory data, use
npm run pairing:rotateand then restart the daemon as described in SECURITY.md. Processes running as the same OS user can read the secrets; pairing is not a same-user sandbox.
Runtime lifecycle (authoritative daemon)
The daemon is the only process that owns the extension-facing runtime. MCP
clients are thin stdio adapters and may start it automatically, but deployment
scripts should use the lifecycle commands below so there is one restart owner.
Do not run a second daemon.js or install a launch supervisor that competes for
port 7225.
npm run build
npm run daemon:start # start, or report the already-running PID
npm run daemon:status # JSON health/runtime information
npm run daemon:health # authenticated daemon + extension check; exit 1 if unhealthy
npm run daemon:stop # graceful SIGTERM; removes runtime metadata
npm run daemon:restart # stop, then start; preserves token/enrollment
The daemon survives browser/Chrome restarts: token, enrollment secret, and
pairing remain in ~/.browser-controller/ (override with BC_STATE_DIR), and
the extension reconnects to the same endpoint. The lifecycle wrapper reads the
daemon.json PID and is safe to run repeatedly; a stale lock is removed only
when its recorded PID is not alive.
For service monitoring, backup boundaries and recovery drills, see the operations and recovery runbook.
MCP clients reconnect after every drop of their daemon connection (a restart,
a crash). Each try connects to the daemon that answers, or waits for one that
is starting (its daemon.lock names a live PID), or, when none is alive,
starts one (never with BROWSER_CONTROLLER_DAEMON_MODE=connect). Tries back
off from 0.25 s to 5 s; after 8 failed tries the client waits for its next tool
call to try again. A tool call made while the client reconnects waits up to
10 s for it. A call that was running when the connection dropped fails
NOT_CONNECTED: daemon connection closed and is not repeated: it may have run.
A client starts a daemon at most 3 times a minute, so a daemon that cannot stay
up is not restarted in a loop; calls then fail NOT_CONNECTED: Not connected to daemon: … with the reason and the path of daemon.log.
When several clients start a daemon at the same moment, the first one takes
daemon.lock. Each other one writes one line, … lock is held by live process <pid>; this daemon exits and clients use that one, and exits with code 0; its
client connects to the first. A daemon.log larger than 5 MB is moved to
daemon.log.1 before the next daemon starts (one old copy is kept).
Expected endpoints
- MCP endpoint: stdio, launched as
node mcp-server/dist/index.js(the standardmcpServerscommand/args form is shown above). - Extension runtime:
ws://127.0.0.1:7225by default, with HTTP/pair,/status, and/kill?sessionId=...on the same port. These are daemon/popup endpoints, not an MCP HTTP transport. - MCP client IPC:
~/.browser-controller/daemon.sockon Unix or\\.\pipe\browser-controlleron Windows. It is internal and token-authenticated. - State:
~/.browser-controller/{daemon.json,daemon.lock,token.json, enrollment.json,daemon.log}.WS_PORT,WS_HOST, andBC_STATE_DIRare the supported configuration overrides.
This preserves the real Chrome session workflow: no Playwright/headless browser is launched, and the existing Chrome profile, cookies, logins, and tabs remain the browser being controlled.
Permission and trust boundary
The unpacked extension deliberately requests Chrome's powerful debugger, scripting, webRequest, and <all_urls> permissions, plus nativeMessaging, which it uses only to ask the local pairing helper for the enrollment secret, and webNavigation, which it uses only to know which tab opened a new one. Together they let a connected MCP agent read network metadata, run functions in the pages you select, drive trusted input and evaluate JavaScript through the debugger, and answer requests in a tab with mock responses. A connected MCP agent can therefore read and change sensitive pages in your signed-in browser. Install the extension only from source you trust, pair it only with a trusted local daemon, and do not expose the daemon port beyond localhost. Chrome-protected pages such as chrome://, the Web Store, and DevTools remain inaccessible.
Using it
The model is tab-first: the agent always says which tab to act on. It never assumes "the active tab."
Basic workflow
- List tabs to get a
tabId:browser_tabs { action: "list" } → 123 [user, focused] "Inbox" mail.example.com/inbox 456 [own] "Sign in" login.example.com - Snapshot that tab to see its structure and get element refs. Use
queryon large pages:browser_snapshot { tabId: 456, query: "Sign in" } → textbox "Email" [e5] button "Sign in" [e3] - Act with the same
tabIdand atarget:browser_type { tabId: 456, target: "e5", text: "hello@example.com" } browser_type { tabId: 456, key: "Tab" } browser_click { tabId: 456, target: "text=Sign in" } - Wait for what you expect, then verify:
browser_wait { tabId: 456, target: "text=Welcome" } browser_text { tabId: 456 }
A ref (e5) is valid for the tab and page that produced it. If its element was re-rendered, the call finds the element again by its snapshot description and adds a note: e5 was re-found after the page changed line. The note appears in the results of click (also hover and drag), type with a target, fill, snapshot, text, scroll and wait with a target, and inspect_element. Screenshot finds the element again without the note. A ref from an earlier page of the tab (after a navigation or reload) fails REF_GONE ("from an earlier page"). A ref that was never issued in the tab fails at once with REF_NOT_FOUND. If the element cannot be found again, the call fails REF_GONE: take a new browser_snapshot.
Browser Controller MCP 질문
Browser Controller MCP는 어떤 도구를 제공하나요?
25개 도구를 제공합니다. 예: url, tabId, snapshot, action, browser.
Headless browser를 사용하나요?
아니요, 사용자가 이미 열어둔 브라우저를 직접 제어하며 실제 세션, 로그인 정보, 쿠키를 그대로 사용합니다.
여러 AI agent가 동시에 브라우저를 사용할 수 있나요?
네, 단일 공유 데몬을 통해 여러 agent가 서로 방해하지 않고 동시에 서로 다른 탭을 제어할 수 있습니다.
데이터가 클라우드로 전송되나요?
아니요, 모든 데이터는 로컬 머신에 머물며 localhost를 통해 통신합니다.