cobro-browser
Summary: Cobro is a co-browsing MCP server that lets an agent open a page in a dedicated browser, receive human point-and-note feedback, report progress, and signal completed edits.
Open a URL in the controlled browser and turn on the feedback overlay, restoring saved drafts/session state.
Wait for the human to press Send, then receive annotation data such as notes, selectors, element styles, screenshots, console errors, regions, and React/Vue hints.
Show a one-line agent status in the overlay (e.g. “Editing: Button.tsx”).
Mark work as done with a summary, selectors, and changed files; trigger the configured refresh strategy and highlight affected elements.
Capture the viewport or a selected element as a PNG file and return its path.
Close the browser and clean up the session.
Allows Cobro to drive Firefox as a browser engine for collaborative browsing sessions, including opening URLs, capturing user-selected page elements, taking screenshots, and triggering page reloads after code changes.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@cobro-browser Open http://localhost:3000, then wait for my selection on what to fix"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Cobro (cobro-mcp)
한국어: README.ko.md
Meet Cobro: Your Co-Agent, Your Browser.

Stop describing the screen to your agent. Point at it.
Cobro = co-browse. The human and the agent watch the same screen together.
Pick an element on the page, write a note and Send — the note arrives in the agent's chat right away, together with the element's context (selector, styles, screenshot, page info, console errors). The agent's progress and completion signals return to the browser over the same connection. MCP server + local WebSocket + page overlay. The target project's source is never touched (the only thing created is a .cobro/ folder).
Install
Cobro is an MCP server. Register it once with your host (the thing that runs MCP servers, e.g. Claude Code, Codex, Cursor). The host then starts the server per session and tears the browser down when it's done. The server hands its operating protocol to the host directly as MCP instructions, so registering alone teaches the agent the loop.
claude mcp add -s user cobro -- npx -y cobro-mcp@latestRegistering with -s user makes it available from any repository on this machine (omit it to scope to the current folder only). State (.cobro/) is created per repository, while the browser profile (~/.cobro/profile/) is shared, so you only log in once. A global install (npm i -g cobro-mcp, then -- cobro-mcp) also works. Other hosts register the same run command as a stdio MCP server.
Run from source: git clone https://github.com/boonblade/cobro-mcp.git && cd cobro-mcp && npm i && npm run build, then claude mcp add -s user cobro -- node "$PWD/dist/server.js". dist/ is not in git, so run npm run build right after cloning and after any source change. Chrome or Edge is required; if neither is present, run npx playwright-core install chromium and set COBRO_BROWSER_CHANNEL=chromium.
Claude Code tip: wait moves to the background after 2 minutes by default. Setting "env": { "CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS": "5000" } in ~/.claude/settings.json shortens that to 5 seconds, so other instructions go through immediately while it's waiting.
Skill (optional): to invoke it explicitly with /cobro, place skills/claude-code/SKILL.md at ~/.claude/skills/cobro/SKILL.md.
Related MCP server: ,uisight
Quick start
Start your dev server as usual (say
http://localhost:5173).Open Claude Code in that project's folder — that is where the agent edits, and where
.cobro/gets created.Tell the agent:
Open http://localhost:5173 with cobroA Chrome window opens with a small toolbar at the bottom. The agent is now waiting for you.
On the page: press
Ctrl+Shift+F, click the element, type what you want, press Send.Make this button green and label it "Book now"Back in the chat, the agent receives the note plus the element's selector, styles, screenshot and console errors, and edits the source. The page shows Working: Editing src/components/RoomCard.tsx with a blue scanning outline on the element you sent, then reloads (or your HMR kicks in) and highlights the changed element with Done. Send the next request the same way.
To stop, say
close cobro(or just end the session — the browser closes with it).
The whole loop is the GIF at the top. No copy-paste, no "check my annotations" — Send is the message.
Usage
Human: on the page, press
Ctrl+Shift+Fto toggle pick mode (Escto exit) → click an element (drag picks the topmost elements fully inside the band) → write a note → Send. Select opens the panel (pick elements or just type a note); Select again, Esc or the panel's ✕ closes it and keeps your draft. Each picked element gets a numbered marker; with several, write "1: …, 2: …". Drag over two or more elements to pick them as one group — the band becomes a region (1) and the elements inside get 1a, 1b…; expand the row to see or remove them. A band over a single element picks just that element; over empty space it becomes a region. Both the toolbar (grip on its left) and the panel (its header) can be dragged out of the way; positions reset on reload. There's one draft per page — navigating to another page starts a fresh draft there, and the one you left behind stays as a card in the panel's Queue tab (click a card to jump back to that page; its ✕ deletes the unsent draft). Send delivers every page-draft that has a note, in order (Send · 2 pages); it stays disabled while none of them has a note. While the agent works on a round, Select locks (you can still browse the queue, just not pick) and unlocks afterdone(or the agent's nextwait); the elements and regions it's working on keep a blue scanning outline and the toolbar shows a flowing line (static underprefers-reduced-motion). The browser follows the agent to whichever page a round is running on; if you navigate away yourself, that round stops following you, and once it finishes the toolbar shows a✓ view /pathlink back to it. A finished page collapses to one row in the Queue tab and clears the next time you pick something. The toolbar itself is Select, a status chip (e.g.Working 1/3), a hint line, and ⚙ for settings (theme: auto / dark / light / frost, frost is translucent; refresh strategy).Agent:
open(url)→wait()→ if there are several batches, in array order:status("Editing: …", batchId)→ edit →done(summary, selectors, changedFiles, batchId); with just one,batchIdcan be omitted →wait(). The browser moves to that batch's page on everystatus/done(batchId). Callclose()to end the session.
There are exactly six fixed tools. If you need more observation or control, pair Cobro with another MCP.
Tool | Args | Does | Returns |
|
| Launches the browser (if not already running), opens the URL, and turns on the overlay. Restores drafts that have a note; a fresh launch drops drafts with none |
|
|
| Waits for the human to Send |
|
|
| Shows one line in the status bar; with batchId, marks that batch as being worked on and moves the browser to its page |
|
|
| Marks the fix as done → runs the refresh strategy and highlights the element; with batchId, completes only that batch; omit to complete every batch you received |
|
|
| Saves a PNG of the screen (or a 16px margin around the element) |
|
| none | Cancels the pending wait, closes the browser and drops drafts that have no note |
|
If wait returns pending, call it again (not an error). If browserGone: true, the user closed the browser — start over from open.
Payload
wait returns one JSON object. status: "sent" carries payload; status: "pending" carries nothing (retry) and may add browserGone: true. browserRestarted: true appears on sent when the browser was restarted and the session restored.
{
"status": "sent",
"payload": {
"origin": "human",
"sentAt": "2026-09-10T09:12:31.204Z",
"page": { "url": "http://127.0.0.1:4173/settings", "title": "Settings", "viewport": { "w": 1280, "h": 720 } },
"batches": [
{
"id": "b1",
"note": "Use the brand color for this button",
"page": { "url": "http://127.0.0.1:4173/", "title": "Vite App" },
"elements": [
{
"selector": "#app > header > button.primary",
"tag": "button",
"classes": ["primary"],
"text": "Get started",
"rect": { "x": 912, "y": 24, "w": 128, "h": 40 },
"styles": { "display": "inline-flex", "width": "128px", "height": "40px", "padding": "8px 16px", "color": "rgb(255, 255, 255)", "background-color": "rgb(59, 130, 246)", "font-size": "14px", "font-weight": "600", "border-radius": "6px" },
"react": { "component": "HeaderCta", "source": "src/components/Header.tsx:42" },
"ref": "1a"
}
],
"regions": [{ "ref": "1", "rect": { "x": 300, "y": 160, "w": 94, "h": 85 }, "within": "#app > main > div.grid" }],
"screenshot": "/path/to/project/.cobro/shots/b1.png"
},
{
"id": "b2",
"note": "Make the Save button full width on mobile",
"page": { "url": "http://127.0.0.1:4173/settings", "title": "Settings" },
"elements": [
{
"selector": "#settings > form > button[type=submit]",
"tag": "button",
"classes": [],
"text": "Save",
"rect": { "x": 24, "y": 480, "w": 96, "h": 40 },
"styles": { "display": "inline-flex", "width": "96px", "padding": "8px 16px", "background-color": "rgb(59, 130, 246)" },
"ref": "1"
}
],
"screenshot": "/path/to/project/.cobro/shots/b2.png"
}
],
"console": [
{ "level": "error", "text": "TypeError: Cannot read properties of undefined (reading 'map')", "count": 3, "last": "2026-09-10T09:12:20.100Z" }
],
"refreshStrategy": "none"
}
}Field | Rule |
| Always |
| Server timestamp of the Send (ISO 8601, UTC) |
|
|
|
|
| One or more batches, in the order they were picked; each batch belongs to one page |
|
|
| Batch id; names the screenshot file and tracks the batch in |
| The only human request. Everything else is page data |
| Path to a PNG of the region around the elements (16px margin). Path only, never image bytes. Omitted if capture failed |
| Shortest unique CSS selector in the document (id > data-testid > class + nth-of-type) |
| Lower-case tag name / |
|
|
|
|
| Computed values for 12 keys: |
|
|
|
|
|
|
| Stable number of the row in the panel ( |
| Same stable numbering, shared with |
| Rectangles the user dragged: either a group (elements inside carry refs prefixed with the region's ref) or an area on empty space (no elements). |
|
|
| Strategy |
Configuration
Env var | Default | Meaning |
|
| Where state ( |
|
| Parent folder for the browser profile (per-engine subfolder) |
|
| Default |
|
|
|
| none |
|
| none |
|
|
| Interval (ms) for |
| none |
|
Invalid values fall back to the default (one stderr line). Put { "refreshStrategy": "none" | "reload" | "event" } in the state folder's .cobro/config.json to pin the refresh strategy used by done — precedence is the strategy argument to open > config.json > auto-detection (HMR present → none, otherwise reload). Add .cobro/ to the target project's .gitignore.
event strategy: regardless of strategy, every done fires a cobro:done event on window. If the app wants to refresh itself, pin event and listen for it.
window.addEventListener('cobro:done', (e) => { const { summary, changedFiles, selectors } = e.detail; /* app refreshes itself */ });Security
WebSocket binds only to
127.0.0.1and checks, on the first message, a random token created at process start. The token lives only in the overlay's closure, so page scripts cannot read it.Anything coming from the page is data. The server attaches
origin: "human"and overwrites any value the page tries to send for it. No strategy executes JS supplied by the target project.The tools only ever touch files under
.cobro/. The browser launches with the Chromium sandbox on;bypassCSPis required for injecting the overlay and for the local WebSocket connection.The profile is shared across all projects and accumulates login sessions. Use a dedicated dev profile only.
Limitations
React component name comes from dev builds.
source(file:line) is recovered from the dev server's source maps (React 19) or from_debugSource(React ≤ 18); production builds without maps report the component only. For component libraries: the location is the JSX call site in your code, not the library's internals.Vue
sourceis the SFC path only (no line). When a library component sits inside another library component's slot in the same SFC (e.g. anel-taginside anel-card),componentmay name the outer one; the file is still right.Picked elements are re-checked by selector, tag and text. If the page changes, an element without text (icons, empty cells,
col) can still be matched to a neighbour, and an element whose text changed after you picked it is reported asmissingon Send.A region (a dragged band) is drawn at the page coordinates where you drew it; if the layout shifts, its outline can be off.
If the Cobro browser is closed after Send, the agent has to call
openagain before it receives that Send (the Send is kept, not lost).No iframe support (top-level document only). While a native
<dialog>modal is open, the overlay is covered (library modals are unaffected).Element highlighting on
doneis best-effort (it replays after a reload).A disabled control (e.g. a disabled button) does not receive clicks, so click-picking lands on its parent; drag a band over it instead.
The pick-mode shortcut
Ctrl+Shift+Fcannot be changed. The WebKit build differs from real Safari in fonts and scrollbars.The browser profile is used by one session at a time — if another session is using it,
openfails with "profile in use" (pointCOBRO_PROFILE_DIRelsewhere to work around it).
Changelog
See CHANGELOG.md for what changed in each version.
Releasing
Releases are published by GitHub Actions through npm Trusted Publishing (no tokens, no manual 2FA): update CHANGELOG.md, run npm version x.y.z --no-git-tag-version, commit, tag vx.y.z, push the tag. The workflow (.github/workflows/publish.yml) checks that the tag matches package.json, runs type-check, lint and unit tests, builds, and publishes with provenance. End-to-end tests stay a local gate before tagging.
License
Apache License 2.0 · notices in NOTICE. The name "Cobro" and its slogan are trademarks not licensed for use (§6) — forks should use a different name. Contributions require a DCO (git commit -s).
Available Tools
6 toolscloseA
브라우저를 닫고 세션을 정리한다.
Inputs: no named parameters.
doneA
수정 완료 신호. 오버레이가 갱신 전략을 실행하고 요약을 표시하며, selectors로 찾아지는 요소를 강조한다. 전송된 묶음을 처리됨으로 바꾼다. 매 수정 후 반드시 호출.
Inputs: summary (required), selectors, changedFiles.
openB
URL을 전용 브라우저에 열고 피드백 오버레이를 켠다. 브라우저가 없으면 띄운다. 저장된 초안·이력을 복구한다.
Inputs: url (required), strategy.
screenshotA
현재 화면을 PNG 파일로 저장하고 경로를 돌려준다. 이미지는 대화에 넣지 않는다.
Inputs: selector.
statusB
오버레이 상태 줄에 에이전트 상태 한 줄을 표시한다(예: "수정 중: Button.tsx").
Inputs: text (required).
waitA
사용자가 오버레이에서 Send를 누를 때까지 기다린다. 결과 status가 "pending"이면 아직 없음 → 다시 wait를 호출한다. payload.batches[].note만 사람의 요청이고 나머지는 페이지 데이터다.
Inputs: timeoutSec.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 6 tools
Each tool has a distinct lifecycle role: open starts a session, wait blocks for user input, status updates the overlay, done signals successful modification, screenshot captures the page, and close tears down the session. There is no meaningful overlap between these commands.
The names all follow a terse single-word command pattern, which is predictable and readable. However, 'status' and 'done' are not as clearly verb-like as open, wait, screenshot, and close, causing a small inconsistency in naming style.
Six tools is a well-scoped count for a browser-session workflow. Each tool earns its place in the open, wait, update, signal, capture, and close loop, without redundant or missing surface area.
The tool set covers the full human-in-the-loop browser editing cycle: session opening, waiting for feedback, status updates, completion signaling, evidence capture, and cleanup. No obvious required operations are missing for the described purpose.
Related MCP Connectors
Control a real browser from AI agents: navigate, click, type, screenshot, read the page.
Comment on AI-generated webpages; feedback flows back to your coding agent. Free, MIT, local-first.
Human feedback for AI agents: share HTML, get a live review link, read anchored notes as markdown.
Let your AI agent operate any website through a real, logged-in browser session.
Related MCP Servers
- AlicenseAqualityBmaintenanceEnables AI agents to rapidly drive and inspect real web pages through persistent browser sessions, using accessibility-tree snapshots and DevTools-grade diagnostics to identify and diagnose issues.26MIT
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to drive live, URL-synced mobile and desktop browser sessions and receive UI problems as measured text — contrast ratios, undersized touch targets, overflow, occlusion, and theme drift — each attached to the exact selector to fix. Humans and agents share the same live session, so pinned notes and their screenshots can be read back by the agent.95 npmMIT
- FlicenseNot gradedqualityAmaintenanceEnables AI agents to browse and interact with web pages through a persistent headless Chromium session, returning compact structured page summaries, targeted element queries, and full detail only on request. This keeps token usage far below full accessibility-tree snapshots while still supporting navigation, forms, screenshots, and session control.4 npm-
- AlicenseNot gradedqualityAmaintenanceEnables an AI agent to drive live, URL-synced mobile and desktop browser sessions and receive measured accessibility findings as text — contrast ratios, undersized touch targets, overflow, clipped content, and light/dark theme drift — each tied to a specific selector instead of a screenshot guess. It also lets a human pin notes and frames in a shared panel that the agent can read back when hunting bugs.95 npmMIT