edge-agent-bridge

Driving Edge through Edge Agent Bridge: a guide for agents

You control the user’s real Microsoft Edge, on the tab they are looking at, with their cookies and sessions. Every action you take is visible to them. Work the way a careful colleague sitting next to them would.

The loop

  1. edge_snapshot first. It returns the page as a tree of roles and names with reference IDs:

    tab 1459 "Files - Example App" http://localhost:8080/files
    page "Files - Example App" url=http://localhost:8080/files
    - heading "Files" level=1
    - textbox "Search" [e1] value=""
    - button "Upload" [e2]
    - table "Files"
      - row "12080 spec.pdf 2026-09-01" [e4]
        - button "Open" [e5]
    
  2. Act by ref: edge_click {ref: "e5"}, edge_fill {ref: "e1", text: "spec"}. Refs are exact; text targets (target: "Open") are a fallback when you have no fresh snapshot. Only refs reach inside an iframe: a ref carries its frame (f31e1), while a text or selector target is looked up in the main frame alone and comes back target_not_found. If a control you can see in the snapshot under an iframe line will not resolve, use its ref.
  3. After anything that navigates or re-renders, snapshot again. Refs from an old snapshot fail with stale_snapshot; a removed element fails with stale_ref. Both mean: snapshot again.
  4. Prefer edge_wait over sleeping: {text: "Saved"}, {url: "/done"}, {load: true}, or {idle: true} for pages that fetch after load. A timeout tells you what was still loading.
  5. When a page misbehaves, read edge_console: it holds recent console output, uncaught exceptions, and any dialogs the bridge answered for you.

Read the tab line before acting

Every result carries tab: {id, title, url}. If the user didn’t name a page, confirm from the first snapshot that you are on the one they meant before you type into it. Your session pins to that tab afterwards, so a tab the user switches to will not receive your commands.

Filling forms the user started

Snapshots include current values (value="...", checked, selected option). Fill only the empty fields unless asked to change one. Passwords show as value="••••"; never ask for or echo the real value.

Things that are easy to get wrong

Errors

Every failure is {success: false, code, error}. Codes you will meet most:

code meaning what to do
stale_snapshot, stale_ref refs are from an old page state snapshot again
target_not_found nothing matched within the timeout snapshot, pick a ref
tab_not_found the pinned tab is gone tokenless: the next call re-resolves the active tab; session: expect sticky tab_closed until switch/new
extension_offline Edge is closed or the extension is off tell the user to open Edge and check the popup
extension_outdated the extension predates the daemon tell the user to update it
dialog_open a native dialog is waiting edge_dialog accept or dismiss
timeout the wait condition never held report what inflight or url shows
tab_busy another call holds the tab lock backoff 250ms·2ⁿ+jitter ≤4×, then surface
tab_closed session pin points at a closed tab switch or new to re-pin
stale_ref ref/frame no longer valid ladder runs automatically; on exhausted_fallback, snapshot fresh
unknown_session bad/expired token (incl. after daemon restart) client re-mints once; if repeated, session start
focus_lost target never took focus snapshot, check overlays, coords click
focus_unverifiable frame context unresolvable retry, coords-only click, or abort
focus_stolen focus moved mid-type resume with remaining
deadline_exceeded extension passed deadlineMs retry (locks/deadlines reset), split long writes
write_mismatch readback differs after retry report expected vs readback, stop
exhausted_fallback ref→text→scan→coords all failed read tried, snapshot, new approach
history_failed history query failed in the extension report error, retry once
group_failed tab-group call failed in the extension report error, check tab IDs

Setup (once per machine)

pip install edge-agent-bridge
edge-bridge setup          # registers the MCP server with the agent clients it finds
edge-bridge status         # daemon, extension, and pairing state

The extension comes from the Edge Add-ons store (“Edge Agent Bridge”); developers can load edge-bridge extension path unpacked instead.

Sessions: one tab per agent (v2.1.0+)

CLI invocations are stateless, so each agent holds a daemon-side session pinned to one tab:

edge-bridge session start            # prints sessionToken=<uuid>
export EDGE_BRIDGE_SESSION=<uuid>    # or pass --session <uuid> per call
edge-bridge switch 1459              # pin this session to your tab
edge-bridge session status           # shows pin state
edge-bridge session stop             # drop it server-side

Forgetting --tab is harmless inside a session: the daemon injects your pinned tab. An explicit --tab is a one-shot override and never changes the pin. Only switch and new re-pin. MCP clients get an implicit session per process automatically. If your tab closes, the session goes sticky tab_closed — every tab-scoped call fails until switch/new. There is no shared default session: two agents MUST use distinct tokens or they share one pin.

Multi-agent etiquette: own tab each. Per-tab locks serialize writers as defense-in-depth, not as an excuse to share a tab.

History and tab groups (v2.2.0+)

edge_history_search {text, maxResults?, startTime?, endTime?} searches titles and URLs (startTime/endTime are ms since epoch); edge_history_delete {url} removes one entry. edge_group_list {windowId?} lists tab groups; edge_group_move {tabIds, groupId?, title?, color?} moves tabs into an existing group, or a new one when groupId is omitted; edge_group_ungroup {tabIds} removes tabs from their groups. CLI mirrors: edge-bridge history search "query", history delete <url>, group list [--window-id N], group move <tab ids> [--group-id N] [--title T] [--color C], group ungroup <tab ids>.

Verified writes: match, write_mismatch, focus_stolen

edge_fill/edge_type verify every write. Results carry written (expected full value), readback (value read back after 100ms), and match:

Focus is asserted before every write (≤3 tries) and rechecked after the 1st char and every 8 chars while typing:

The fallback ladder and tab_busy

Ref-taking actions (click, dblclick, rightclick, hover, fill, type with target, select, check-radio, upload, drag) automatically retry ref → text/selector → fresh elements scan → coords from the best match (exact, then case-insensitive, then substring text match), with at most 2 extra scans per call. Explicit --x/--y skips the ladder; --no-fallback disables it. Total failure returns exhausted_fallback with tried: [{step, outcome}] — read it, snapshot fresh, try a new approach.

tab_busy means another writer holds your tab’s lock (10s acquire). Back off 250ms·2ⁿ with jitter, ≤4 retries, then surface or use another tab.