One script tag turns any web page into a device your AI coding tool can drive: DOM and shadow DOM traversal, full-page screenshots, clicks, swipes, typing, and console logs — over synchronous MCP.
The OmniDebugLink Web SDK is a pure ES module with zero dependencies. Include it in a page — plain HTML, a bundler-based SPA (Vite, CRA, Next), or a Capacitor WebView — and it opens a persistent connection to the relay. Your AI tool then calls tasks on that live page over MCP: inspect the DOM including shadow roots, capture screenshots, read console output, click buttons, and fill forms.
Everything the page can do is announced to the server in a handshake when it connects, so list_tasks in your MCP client shows the complete task set immediately — no configuration file to maintain on the AI side.
No npm install, no build step, no bundler plugin. The CDN build is an IIFE that exposes a global OmniDebugLink; there is also an ESM build for bundlers and the plain source module if you prefer to bundle it yourself. html2canvas 1.4.1 is bundled into the release builds, so screenshots work from a single file. Without a version tag the CDN always serves the latest release; pin a tag for reproducible builds.
ui_traverse walks the DOM and recurses into every shadow root, returning a flat list capped at 3000 nodes with depth and path — a token-efficient way to see real page structure. find_objects matches by tag, id, className, data-testid, or text substring, orders matches most-specific-first (the element that renders the text, not its ancestors), and returns center 0-1 coordinates so a click can be aimed with no geometry work. Screenshots handle shadow content too: the capture clones the live document and flattens shadow roots into the clone, so inline styles survive (scoped styles approximately).
screenshot captures the viewport by default, in the same coordinate system as find_objects and tap_screen — what the AI sees and what it clicks line up. Pass fullPage: true to capture the whole document, no matter how tall it is; the result reports the real canvas dimensions. Captures come back as native MCP image blocks. If html2canvas cannot render something, an SVG foreignObject fallback is used.
ui_click is delivered physically since v0.2.1: the target's center is hit-tested with elementFromPoint and the click fires on whatever actually sits there, so overlays and guide masks receive it exactly like a human tap. The result's via and clicked fields report what happened, and when the center cannot be hit-tested the located element itself is used.
Set OmniDebugLink.actionsEnabled = false and every write task is refused while read tasks keep working. The flag is announced with the handshake, so your AI tool knows the page is observe-only; flip it back to re-enable input.
OmniDebugLink.tasks.register(type, handler, description, payloadSchema) publishes app-specific checks next to the built-ins, and registry changes are re-announced automatically.
CDN, IIFE build (exposes a global OmniDebugLink):
<!-- no @version = always serves the latest release -->
<script src="https://cdn.jsdelivr.net/gh/omnidebuglink/omnidebuglink_web/dist/omnidebuglink.min.js"></script>
<script>OmniDebugLink.start('<clientToken>')</script>
ESM import, for bundlers and modern apps:
import { OmniDebugLink } from 'https://cdn.jsdelivr.net/gh/omnidebuglink/omnidebuglink_web/dist/omnidebuglink.esm.min.js';
Pin a release for reproducible builds by adding the tag:
https://cdn.jsdelivr.net/gh/omnidebuglink/[email protected]/dist/omnidebuglink.min.js
Or import the source module directly and bundle it yourself:
<script type="module">
import { OmniDebugLink } from './omnidebuglink.js';
OmniDebugLink.start('<clientToken>');
</script>
API surface: OmniDebugLink.start(token) (the relay URL is baked in — pass the bare token), stop(), the read-only actionsEnabled flag, and tasks.register(...). In React, call start() in useEffect and stop() in the cleanup.
Sign in and create a client token in the console. One pair per page or WebView you want to debug.
<script src="https://cdn.jsdelivr.net/gh/omnidebuglink/omnidebuglink_web/dist/omnidebuglink.min.js"></script>
<script>OmniDebugLink.start('<clientToken>')</script>
Reconnect with backoff and heartbeats are built in; the page keeps itself online.
claude mcp add --transport http odl \ "https://api.omnidebuglink.dev/mcp"
Ask it to list_tasks — all built-in web tasks appear — then try ui_traverse or a screenshot of the live page.
Read tasks — observation only, always available:
| Task | What it does |
|---|---|
ui_traverse | DOM + shadow DOM snapshot as a flat list (3000-node cap) with depth/path — token-efficient structure inspection. |
find_objects | Search by tag / id / className / data-testid / text substring; matches ordered most-specific-first, with center 0-1 coords. |
view_component | One element in depth: rect, computed style, attributes, current form value. |
wait_for | Polls every 200 ms until a selector appears or text is found; timeout returns found: false, not an error. |
screenshot | Viewport capture in the same coordinate system as find_objects/tap_screen; fullPage: true captures the whole document. |
read_logs | Console ring buffer (500 entries, nothing before start) with level / contains / limit / sinceMs filters. |
get_state | URL, title, viewport, navigator, and performance navigation timing. |
get_perf | Navigation timing, named marks, and resource counts. |
prefs | localStorage: read (get / list) and, gated by actionsEnabled, write / delete. |
Write tasks — all gated by actionsEnabled:
| Task | What it does |
|---|---|
ui_click | Physically delivered click: the element's center is hit-tested via elementFromPoint and the click fires on whatever actually sits there, so overlays receive it like a real tap. Locates by selector or text. |
tap_screen | Tap at normalized 0-1 coordinates (top-left origin) via elementFromPoint. |
swipe | PointerEvent gesture with native-behavior compensation: scrolls the nearest scrollable ancestor, resolves range sliders by x position. |
long_press | Pointer down, hold (holdMs), up. |
input_text | Writes through the prototype setter so the browser's internal value updates; React controlled components are supported via a value-tracker reset. |
send_key | Soft-dispatched KeyboardEvent: enter / tab / escape / backspace / arrows; scroll keys are compensated when focus is outside forms. |
Basics: echo, ping, get_stats. All coordinates are normalized 0-1 with a top-left origin — the same convention as Android, Flutter, and iOS (Unity is bottom-left).
wait_for uses a wall-clock deadline so it is late rather than wrong.html2canvas drops CSS rules that fail to parse, so prefer ui_traverse or view_component for exact checks.read_logs only sees output produced after the SDK started.