# OmniBrowse OmniBrowse is a **Model Context Protocol (MCP)** server providing a private-mode text-based headless browser for AI agents. No Selenium, Playwright, or Chromium — just Node.js with Chrome TLS impersonation (curl-impersonate), jsdom JavaScript execution, full DOM/input handling, and 31 tools designed for AI browsing workflows. Pages are parsed into an accessibility-style text snapshot with interactive element refs (`r1`, `r2`, …). The model clicks, types, scrolls, and runs JavaScript — each action triggers the real protocol (HTTP/curl-impersonate with true Chrome JA3, or local schemes like data:/file:/ftp:/ipfs:). ## Install Add to your MCP client config (Claude Desktop, Kilo, Cursor, etc.): ```json { "mcpServers": { "web-browser": { "command": "npx", "args": ["-y", "omnibrowse"] } } } ``` > Until the npm package is published, run locally: > > ```json > { > "mcpServers": { > "web-browser": { > "command": "node", > "args": ["/absolute/path/to/omnibrowse/dist/index.js"] > } > } > } > ``` ## Tools | # | Tool | Description | |---|------|-------------| | 1 | `navigate` | Open a URL (http/https/data/file/ftp/gopher/ipfs/about/javascript). Set js=true for jsdom SPA rendering. Set newSession=true to wipe history/cookies before navigating. Returns an accessibility snapshot. | | 2 | `read_snapshot` | Re-read the current viewport of the page. | | 3 | `scroll_read_more` | Advance to the next chunk of a long page (viewport-sized scroll). | | 4 | `click` | Click an element by ref. Links navigate; buttons/submit inputs submit their form with all filled fields. | | 5 | `type_text` | Type into a textbox/textarea by ref. `submit=true` submits the enclosing form afterward. | | 6 | `select_option` | Choose option(s) in a `` by absolute path. Submit the form afterwards (click on submit button or press Enter) to upload via multipart/form-data. | | 29 | `screenshot` | Capture a structured text "screenshot" (full-page layout dump) of the current page. Pair with view_source for raw HTML. | | 30 | `handle_dialog` | Accept/dismiss an alert/confirm/prompt dialog raised by page JS during the last jsdom run (with optional `response` for prompts). | | 31 | `dialog_list` | List any dialogs (alert/confirm/prompt) queued from the last jsdom run, with the next-to-handle marked. | All tools with an empty input schema use `z.object({})` for MCP SDK compatibility. ## Protocols HTTP/HTTPS goes through `curl-impersonate` with `--impersonate chrome131`, producing a true Chrome JA3 fingerprint. Local protocols are handled directly without network access: | Scheme | Handler | |--------|---------| | `data:` | Decodes base64 or percent-encoded payload, returns the MIME body. | | `blob:` | Unresolvable outside the originating JS context; an error note explains to use read_snapshot. | | `file:` | Reads regular files (sniffs MIME from extension) and generates directory listings (via `ls -la` with HTML-safe escaping). | | `javascript:` | Returns the source code for evaluate_js to run in page context. | | `about:` | Built-in pages: privatebrowsing, version, networking, preferences, blank, blobs, srcdoc. Default shows scheme + kind. | | `ftp:` / `ftps:` | Delegates to curl-impersonate. Directory listings are detected and wrapped in HTML `
`. |
| `gopher:` | Fetches the selector text via curl-impersonate. |
| `ipfs:` / `ipns:` | Resolves through public gateways in order: ipfs.io → dweb.link → cloudflare-ipfs.com → w3s.link. |
| `ws:` / `wss:` | Returns informational text (WebSocket endpoints are detected but not connected). |
| `mailto:` / `tel:` / `sms:` | External scheme — handled by the OS, not navigable. |

## Private browsing posture

- **Cookies** live only in memory for the current MCP session, scoped per hostname. Third-party cookies (cross-registrable-domain Set-Cookie) are dropped. SameSite=None without Secure is rejected.
- **DNT: 1** and **Sec-GPC: 1** sent on every HTTP request.
- **Referer** suppressed to origin for cross-origin navigations.
- **No persistent cache, history, or storage** written to disk between sessions.
- **WebRTC** (`RTCPeerConnection`) blocked entirely. **getUserMedia/getDisplayMedia** denied. **ServiceWorker.register** throws SecurityError.
- **Geolocation, notifications, clipboard** resolve silently without user prompts (denied/granted respectively).
- **Canvas/WebGL/AudioContext** return plausible null contexts with no real pixels or audio output (fingerprint-safe).
- **crypto.subtle** backed by Node's real WebCrypto — modern auth flows (PKCE, JWE) work.
- **fetch/XHR** observed and recorded for the network-log tool.

## Content rendering

Non-HTML resources fetched via `fetch_resource` are handled by content-type-specific renderers:

| Content-Type | Renderer |
|--------------|----------|
| `image/*` | Dimensions via `file` / `identify` (ImageMagick). OCR via tesseract+imagemagick pipeline when installed. |
| `application/pdf` | Text extraction via `pdftotext`, metadata via `pdfinfo`. |
| `application/json` (and `*+json`) | Pretty-printed with top-level object/array summary. |
| `application/xml` (and `*+xml`) | Root element detection + raw text display. |
| `audio/*`, `video/*` | ffprobe stream metadata when installed (codec, duration, resolution). |
| Archive types (`application/zip`,`x-tar`,`gzip`, etc.) | Descriptive note (not expanded inline). |
| `text/*` | Raw text with 30KB truncation. |

## How it works

| Layer | File | Description |
|-------|------|-------------|
| **TLS/HTTP** | `src/curl.ts` | Detects `curl-impersonate` binary (style-1 single binary or style-2 per-browser binaries), probes supported `--impersonate` targets, falls back to plain curl with crafted Chrome TLS ciphers/curves. Emits Chrome User-Agent, sec-ch-ua headers, HTTP/2/3, redirect chain tracking. |
| **Private HTTP** | `src/http.ts` | `HttpClient` wraps curlFetch with in-memory per-hostname CookieJar, SameSite enforcement, third-party cookie dropping, DNT/GPC headers, cross-origin Referer policy, DNS resolution via `dig` with caching, multipart POST assembly. |
| **Protocols** | `src/protocols.ts` | Handles all non-HTTP(S) URL schemes: data URI base64 decoding, file URI read+directory, ftp/gopher via curl, ipfs via public gateways, about: internal pages, javascript: eval forwarding, ws/wss info, external schemes. |
| **DOM** | `src/dom.ts` | `node-html-parser` builds a tree; `buildPage()` walks it once producing an accessibility snapshot with `[L]` text lines and `[rN]` refs for interactive elements. Supports all HTML5 input types (slider, spinbutton, datetime, searchbox, color, file), `
`/``, iframes, media elements (video/audio/canvas/object), heading markers, safe viewport scrolling with line-count guard. | | **JS Execution** | `src/jsdom.ts` | jsdom with `runScripts: "dangerously"`, real Internet ResourceLoader for subresources, Web API stubs (crypto.subtle→Node WebCrypto, Canvas/WebGL→null context, AudioContext→stub, Notification/Permission/Clipboard/Geolocation→silent deny, ServiceWorker/WebRTC→blocked), console+network capture. | | **Content** | `src/content.ts` | Non-HTML renderers: JSON pretty-print+summary, XML root detection, image dimensions+OCR (optional tesseract), PDF text extraction (optional pdftotext), media probe (optional ffprobe), archive descriptions. | | **Session** | `src/browser.ts` | `Session` tracks page history, form state (filled values, toggled checkboxes, selected options), scroll position, and viewport size. `performAction()` routes all actions to the correct protocol fetch + render pipeline. | | **MCP Server** | `src/index.ts` | 31 tools registered via `@modelcontextprotocol/sdk` McpServer over `StdioServerTransport`. Stdio only — no HTTP server, no ports. | ## Build from source ```bash npm install npm run build # outputs dist/ node dist/index.js # run the server (reads JSON-RPC from stdin) ``` ## Test ```bash node test/test-comprehensive.mjs # 31 automated checks: navigation, JS, all protocols, tools, resources node test/test-ai-browse.mjs # Realistic 20-step AI browsing session (example.com → HN → DDG → resources → JS eval → protocols) ``` ## License MIT