Skip to main content
Glama

OmniBrowser v2

A small attach-only Chrome/Chromium helper: independent CLI and persistent STDIO MCP, seven commands, no agent loop/broker/recipes or arbitrary eval tool. Python 3.10+; existing runtime dependencies only (Playwright, httpx, markdownify, MCP).

Install and entries

git clone https://github.com/huutrungle2001/OmniBrowser-v2.git OmniBrowser
cd OmniBrowser
python3 -m venv .venv
.venv/bin/python -m pip install -e .
export PATH="$PWD/.venv/bin:$PATH"
export OMNI_CDP_URL=http://127.0.0.1:17082

Chrome must already expose loopback CDP; Omni never launches a browser/profile. Default is port 17082; CLI accepts --cdp-url, MCP reads OMNI_CDP_URL. Playwright 1.64+ attach uses no_defaults=True, leaving download policy, focus and media defaults unchanged. Do not expose CDP to a network.

Root python /absolute/path/OmniBrowser/omni.py, installed omni, and omni-mcp work independently of cwd. --help/--version do not contact Chrome. For Python package imports from the checkout, use PYTHONPATH=src with safe-path mode or run outside the root: the development entry omni.py otherwise shadows the installed package in ordinary python -c/stdin imports.

Related MCP server: Chrome MCP Bridge

Commands

omni --list-tabs
omni --goto https://example.com --new-tab
omni --goto https://example.com --tab TAB_ID
omni --observe --tab TAB_ID
omni --observe --tab TAB_ID --cursor CURSOR_FROM_PREVIOUS_RESULT
omni --act --tab TAB_ID --ref REF --action click
omni --act --tab TAB_ID --ref REF --action fill --value 'text'
omni --act --tab TAB_ID --ref REF --action press --key Enter
omni --act --tab TAB_ID --ref REF --action select --value blue
omni --act --tab TAB_ID --ref REF --action select --label Blue
omni --act --tab TAB_ID --action click_at --x 320.5 --y 240
omni --scroll --tab TAB_ID --direction down --amount 600
omni --screenshot --tab TAB_ID --output /absolute/capture.png
omni --screenshot --tab TAB_ID --output /absolute/full.png --full-page
omni --download --tab TAB_ID --url https://example.com/notes.pdf --output ./notes.pdf

Use real returned tab IDs/refs, not indexes, text matches or guessed selectors. Only explicit goto --new-tab creates a tab; no implicit focus/tab selection. Irrelevant/ambiguous action arguments are rejected before browser interaction. Press is native element input; select is exact value OR label on native HTML select only (custom dropdowns return UNSUPPORTED_ACTION). No force/JS click, JS value assignment, action replay, hidden observe or post-action screenshot.

CLI stdout is one JSON object, optionally --pretty. Errors retain {"ok":false,"error":{"code":"STALE_REF","message":"..."}}. Exit codes: 0 success; 2 invalid/unsupported/limit; 3 connection/missing tab; 4 stale/unavailable target; 5 timeout; 6 file/download/auth; 1 internal. --timeout is milliseconds, default 10000 (download 120000); it is a phase bound, not a hard wall-clock interruption of CDP capture/local parsing. Filled/selected values and native call logs are omitted from errors/results; shell history/arguments can still expose CLI values.

Complete paged observation

Observe joins batch DOM/AX/DOMSnapshot captures using vendored browser-use snapshot/visibility/clickable/paint-order logic, then clean HTML -> markdown. It returns content, absolute links, named controls, frames, coverage, truncated, has_more, and next_cursor. Follow the cursor to continue all four streams (including frame metadata) until has_more=false/next_cursor=null.

Capture is best-effort, not an atomic snapshot: unrelated DOM mutations during capture do not reject the whole observation. Document identity, URL/baseURI and frame topology/loader checks remain; unresolved candidates and extraction limits are reported. Signed cursors still reject meaningful changes to cleaned results or document/frame identity, not arbitrary DOM mutation counts.

Main, nested same-origin, cross-origin and out-of-process iframe documents are read once in stable frame-tree order. Content has [Frame FRAME_ID] boundaries; every link/control carries frame_id. Frame metadata includes parent identity, sanitized URL/name, read status and concise reasons for unread frames. Duplicate sibling URLs/labels are not identities. Hidden/inert/fully clipped owners are filtered; partial clipping and transforms are not projected into child content. Ordinary below-viewport content is retained. Closed shadow roots remain unsupported.

Each full Omni JSON page + newline is bounded to 15360 UTF-8 bytes by default (--max-bytes 2048..65536). Streams are interleaved fairly; positions in coverage.positions are [start,end) offsets for content characters/items. Only the returned page binds refs, at most 96 combined links/controls; candidate discovery is not globally capped at 96. An item that cannot fit by itself raises OUTPUT_BUDGET; names, URLs and refs are not clipped to fit.

Cursors are signed with a document-local key and a keyed fingerprint of cleaned extraction, exact frame/node identities, frame topology and document generations. They expose offsets, not raw content, credentials or editable values. Each call captures afresh; no disk registry or observation daemon. Cursors work between CLI processes and MCP. Malformed, wrong-tab/document, navigation or changed extracted state returns STALE_CURSOR and requires a fresh observe without cursor. Title-only/irrelevant chrome text outside the extracted main content is not itself a reason to invalidate.

truncated and *_omitted describe the CURRENT response, so they may remain true/nonzero on the final page because earlier pages held other items. has_more=false only ends recoverable pagination, not all site coverage. Unrecoverable aggregate limits remain explicit: 50000 processed DOM nodes, 3000 paint-filter nodes, 1000000 HTML characters before markdown, 32 read frames, 8 descendant levels, 256 discovery records/attachment attempts, and acquisition limits of 100000 snapshot nodes/8000000 string characters across target batches. CDP batch replies arrive before their size can be checked; a single batch and local rendering are not hard wall-clock interruptible. Overlong names are omitted. Check processing_limited, paint_filter_limited, extraction_content_truncated, frames_unread, frames_unrouted, frame_discovery_limited, frame_geometry_limited, frame statuses/limitations and omission/unsupported counts. Frame metadata is paginated too; omitted metadata does not imply a successful read.

Refs resolve from exact backend node objects into a document-local Map; no Nth element rebinding. They work across CLI/MCP, with at most 512 retained refs. Navigation/BFCache, detachment/reattachment, identity changes (e.g. href), another tab or eviction rejects a ref. On stale refs, observe/select again. Refs include an opaque exact frame/document identity and resolve only to the bound node in that document, including iframe/open Shadow DOM nodes. The 512-ref retention limit is per document. Transient writes/removals of watched identity attributes do not invalidate a ref when the exact node's stored identity is restored; a different final identity still rejects it. No raw DOM/AX, editable values, hidden fields, cookies or browser credentials are serialized. Visible page text/URLs are not a general secret-redaction service.

Screenshot, scroll and downloads

Screenshot is viewport PNG by default. Full-page uses bounded CDP capture beyond the viewport, without tab activation, viewport resize or hidden scrolling. Output is CSS-resolution PNG, limited to 6000000 pixels, 8192px per side and 4 MiB encoded-image bytes. Oversized capture returns SCREENSHOT_LIMIT. CLI requires output; MCP returns actual standard ImageContent plus JSON metadata, with optional explicit output file. Existing files/missing parents require --overwrite/--mkdir; publication is atomic and failures leave no partial PNG. Screenshots can reveal visible secrets; use only authorized pages.

For visual-only targets, capture a viewport screenshot and choose the point:

omni --screenshot --tab TAB_ID --output /absolute/viewport.png
omni --act --tab TAB_ID --action click_at --x 320 --y 240

click_at sends one native mouse click at finite fractional-capable coordinates in top-level viewport CSS pixels, including hits inside iframes. It requires both x/y, rejects ref/value/label/key, and checks current viewport bounds. Ref actions require a ref and reject coordinates. Success confirms input dispatch, not application intent. No screenshot, scroll, correction or retry is hidden inside the operation; canvas typing, dragging, resizing and rotating are not supported.

PNG metadata includes actual image dimensions, viewport CSS size, page-CSS capture origin/extent, page scroll, DPR, browser zoom and visual viewport scale. When image_to_input.supported=true, map original image pixel coordinates using input_x = viewport_origin.x + image_x * css_per_image_pixel.x (likewise y). This uses actual PNG dimensions rather than assuming device pixels equal CSS pixels. Account separately for any resizing by your image viewer/client. Full-page positions are not directly viewport click coordinates: take a viewport screenshot for clicking. Pinch zoom/nonzero visual viewport offsets are explicitly unsupported for coordinate input/mapping. Normal desktop browser zoom is accounted for through current CSS metrics, DPR and actual PNG dimensions. A screenshot is not a lock: capture again after layout, navigation, viewport or scroll changes. No automatic visual targeting or arbitrary JavaScript API is exposed.

Scroll sends one native wheel input to a hit-tested main-document location with no inner scroller in its ancestry. Direction is up/down; amount is 1..10000 CSS pixels, default 75% viewport height capped at 800 (minimum 100). It returns before/after positions, movement, document/viewport sizes and at_current_bottom. This is not a claim that lazy loading is finished or an infinite list is complete. No inner-pane/iframe scrolling, focus switching, implicit observe or repeated auto-scroll.

HTTP downloads preserve the existing scoped in-memory cookie jar, redirects, streaming/byte cap (512 MiB default), HTML/PDF checks and atomic no-clobber output. No browser Save dialog or download-policy changes. POST/blob, partitioned-cookie auth, browser-only tokens, client certificates and challenges have no fallback.

MCP, skill and source layout

Exactly seven tools: list_tabs, goto, observe, act, download, screenshot, scroll. MCP uses snake_case arguments (tab_id, full_page, max_bytes, cursor, etc.). For example: act(tab_id, ref, action="press", key="Enter"); act(tab_id, action="click_at", x=320, y=240); observe(tab_id, cursor=next_cursor); screenshot(tab_id). Results/errors share CLI objects in text + structuredContent; screenshot adds a real PNG ImageContent. Observe's byte budget excludes MCP protocol/duplicate representation overhead.

Add an MCP registration to ~/.codex/config.toml, replacing the example paths with your checkout's absolute paths:

[mcp_servers.omni]
command = "/absolute/path/OmniBrowser/.venv/bin/python"
args = ["/absolute/path/OmniBrowser/omni_mcp.py"]
tool_timeout_sec = 180
[mcp_servers.omni.env]
OMNI_CDP_URL = "http://127.0.0.1:17082"

One dedicated thread owns sync Playwright, attaches lazily and serializes MCP calls. CLI is independent with per-call connections. Broken transport is dropped for a later call; no replay. Shutdown disconnects, not Chrome/user tabs. Other clients need agent coordination. Already-running clients may need MCP reconnect/new session for changed schemas; no other agent/session was restarted. Install the bundled skill by linking skills/omnibrowser into ~/.codex/skills/omnibrowser. See skills/omnibrowser/SKILL.md. The product name is OmniBrowser; CLI omni and MCP registration omni remain unchanged.

Each top-level operation is implemented once in src/omni/commands/*.py. Entries stay tiny; CLI/MCP share validation and dispatch. Canonical layout:

omni.py, omni_mcp.py            thin development entries
src/omni/
  cli.py, mcp_server.py         validation/dispatch, persistent STDIO owner
  errors.py, models.py
  browser/                     connection.py, tabs.py, frames.py, targets.py, refs.js
  reading/                     capture.py, render.py, pagination.py
  commands/                    list_tabs.py, goto.py, observe.py, act.py,
                               download.py, screenshot.py, scroll.py
  _vendor/browser_use_dom/      small existing MIT DOM subset

There is no backward-compatibility layer. Former package-root cdp, tabs, reader, pagination, refs.js, observe, actions, download and scanner.js are removed. Python callers must import the canonical modules above; tab command convenience exports are gone. Old refs/cursors require a fresh observation. Non-editable wheels include omni/browser/refs.js. Vendor MIT/provenance is in src/omni/_vendor/browser_use_dom/UPSTREAM.md.

Verification

This revision was exercised directly through actual CLI/MCP on a harmless temporary local demo in a newly-created tab of existing Chrome. No pytest, automated suite, mocks, benchmark or new fixture project was added/run; existing test callers received canonical import, registry/resource and changed observation-schema maintenance only. No Moodle/account/agent-fetch tab was used. Existing fixture-based checks are included under tests/; local handoff notes and screenshots are not published.

Direct checks on 2026-10-21, Chrome 155, existing authorized session:

  • Same-origin siblings, nested documents, cross-origin OOPIF and a nested OOPIF read/action checks passed. Separate CDP type: iframe targets confirmed actual OOPIFs; duplicate URLs/labels changed only their selected sibling.

  • CLI → MCP and MCP → CLI refs worked; native click/fill/press and native select by label/value passed. Navigation, detachment, node reinsertion and restored identity mutations rejected old refs. Changed child state/navigation rejected cursors.

  • Alternating real CLI/MCP continuation recovered 230 links, 233 controls (including three extension groups), three frame records and both final content rows in 35 pages at a 4096-byte budget; largest actual response was 3944 bytes. Sibling candidates appeared on the first page; the global cap returned 96 refs.

  • Hidden/inert/fully clipped owners were skipped; transformed owners were marked geometry-limited. A 37-frame page reported 32 reads and five explicit limits.

  • Viewport screenshots guided native main/OOPIF canvas clicks, including after main-document scrolling. Missing/conflicting/non-finite/bool/out-of-bounds coordinates were rejected; MCP rejection checks left the click count unchanged. Full-page mapping was marked unsupported for direct viewport input.

  • Seven-tool STDIO listing, actual MCP PNG ImageContent, help/version and an outside-checkout non-editable wheel/resource check passed; legacy paths were absent from the wheel. No other MCP clients were restarted.

Display verification used DPR 2 at 100% desktop browser zoom, including fractional scroll offsets. Other DPR/browser zoom modes were not directly verified; pinch zoom/offset visual viewports are explicitly unsupported, not silently corrected.

Available Tools

7 tools
actB

Native click/fill/press/select on an exact observed ref. No replay or hidden observe. select is native HTML only.

Inputs: ref (required), action (required), tab_id (required), key, +3 more.

downloadB

Cookie-scoped HTTP GET streaming; atomic output, no Save dialog. No POST/blob/challenge fallback.

Inputs: url (required), output (required), tab_id (required), mkdir, +3 more.

gotoA

Navigate an explicit tab or create one only with new_tab=true. No hidden observe.

Inputs: url (required), tab_id, new_tab, timeout.

list_tabsB

List stable page-target IDs, titles and URLs without selecting/focusing a tab.

Inputs: timeout.

observeC

Clean content, links and controls with exact refs; continue all streams using next_cursor. No field values.

Inputs: tab_id (required), cursor, timeout, max_bytes.

screenshotB

Return actual PNG ImageContent and metadata; optional safe output file. No focus/resize/scroll. Max 6M pixels, 8192px/side, 4 MiB.

Inputs: tab_id (required), mkdir, output, timeout, +2 more.

scrollC

One main-document native wheel input. CSS pixels; reports before/after movement, not lazy-load completeness. Inner panes unsupported.

Inputs: tab_id (required), direction (required), amount, timeout.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 7 tools

    Disambiguation5/5

    Each tool targets a distinct browser primitive: list_tabs (enumerate), goto (navigate), observe (read state), act (mutate state), download (fetch file), screenshot (capture image), scroll (viewport movement). The descriptions explicitly delimit boundaries (e.g. observe returns refs but no field values, act works only on observed refs), leaving no real overlap. observe vs screenshot are the closest pair but are clearly separated as DOM-content vs PNG image.

    Naming Consistency4/5

    Six of seven names are clean single-verb actions (goto, observe, act, download, screenshot, scroll), giving a predictable action-oriented convention. list_tabs is the lone verb_noun outlier, a minor deviation rather than a different style. Overall readable and consistent.

    Tool Count5/5

    Seven tools is well-scoped for a browser automation server, covering the essential perceive/act/navigate/capture primitives. Each tool earns its place with no redundant or filler entries. Falls squarely in the ideal 3-15 range.

    Completeness4/5

    Core browser lifecycle is covered: tab enumeration, navigation, observation, interaction, scrolling, screenshots, and downloads. Minor gaps exist, such as no explicit tab-close or history back/forward operation and no dedicated wait-for-load primitive, but agents can work around these via goto and repeated observe. The surface is solid for the stated purpose.

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables AI agents to control the Google Chrome browser through a Node.js WebSocket bridge and a dedicated browser extension. It provides tools for capturing screenshots, executing JavaScript, managing tabs, and extracting page content via the MCP protocol.
      2
      -
    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables AI assistants to control and interact with a Chrome browser via MCP, providing tools for navigation, screenshots, clicking, form filling, content extraction, and tab management.
      1
      -
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables local opencode agents to control a live Chrome browser via MCP tools, including tab management, JavaScript execution, clicking, form filling, page reading, screenshots, and console log retrieval.
      2
      MIT
    • A
      license
      Not graded
      quality
      A
      maintenance
      Enables AI agents to control, read, and verify a live Chrome browser through 46 schema-validated MCP tools, including DOM pruning, coordinate-aware interaction, and trusted CDP input events.
      77 npm
      41
      AGPL 3.0