Can an AI agent use a browser extension? Measured — and the answer has two halves

Wiring our citation endpoint to a capture extension raised the obvious question: can an agent just use the extension itself? We loaded it into an agent-driven browser and tried. Two steps pass, two fail — and the line between them is not a bug. It is the permission the extension asks for, and it decides which kind of AI system can drive it.

3 August 2026 · Chromium 145.0.7632.6, extension loaded unpacked · raw data: without a gesture · with one

What was measured

A Playwright-driven Chromium — the same shape of thing browser-use, Playwright MCP and Chrome-DevTools MCP put underneath an agent — started with the extension loaded from a local folder. Then four checks, each recorded separately so it stays visible which one holds.

StepResultDetail
Extension loadspasses Full Page PDF Snap 2.10.0, ENABLED
Service worker wakespasses background.js, manifest readable
Sees the page without a gesturefails 0 of 2 tabs carry a URL
Captures without a gesturefails "Kein Tab geladen." — no tab loaded

Worth noting how the third one fails. chrome.tabs.query({}) returns the tabs — it just returns them with url and title empty. The extension is not blocked from running; it is blocked from seeing, and it reports that honestly instead of capturing something wrong.

Why: the extension asks for the narrow permission

Both builds declare activeTab and no host permissions at all — no <all_urls>, no site list:

BuildPermissionsHost permissions
Firefox activeTab, downloads, downloads.open, storage, menus, notifications, scripting, clipboardWrite none
Chrome / Chromium activeTab, downloads, downloads.open, storage, contextMenus, notifications, scripting none

activeTab grants access to the current tab only after a real user gesture: a click on the toolbar icon, the keyboard command, or a context-menu entry. Nothing else opens it — and a click dispatched by a script into page content is not one of them. That is exactly what the measurement shows, and it is the same design we argue for in the permissions measurement: an extension that cannot read every site cannot leak every site.

So the answer to “can an AI use this extension” is not yes or no. It is: an AI that can produce a real gesture can; one that only manipulates the DOM cannot. The extension does not check whether a human or a machine clicked — the browser checks whether a gesture happened at all.

The other half, measured: a real input event does open it

A permission that is granted by “a gesture” raises the obvious question — does the browser check who made it? It does not. It checks whether one reached the input layer at all. So the same test was repeated with the extension untouched and one thing changed: instead of a scripted click inside the document, the keyboard command was sent through the window system's own input path (X11's XTEST, the mechanism xdotool uses), to a visible browser window.

MomentTabs the extension can see
After loading, service worker awake, no gesture0
After Alt+Shift+Y as a real input event1

That is the whole finding in two rows. The extension went from blind to seeing the page, without a human in the room and without any change to the extension. A synthetic input event at window-system level satisfies activeTab. Which is exactly what a computer-use model produces when it moves the mouse and presses keys, and what an xdotool-driven agent produces when it clicks an extension's toolbar icon.

One honest gap: in this run the capture itself did not finish inside the measurement window — the driver closed the browser while the page was still being assembled (TargetClosedError), which says something about our timeout and nothing about the extension. The permission transition is the claim being made here, and it is the one that was measured.

Which systems clear that bar

Sorted by whether the system's actions reach the browser as input events or as protocol commands. That, not the vendor, is what decides it.

SystemHow it actsCan trigger the extension
Claude in Chromeextension-based agent clicking and typing in the live browser yes — and it works in the profile where the extension is already installed
ChatGPT agent mode, the ChatGPT browser extensionagent operating a browser session yes, same basis
Perplexity Comet and other agentic browsersbrowser with a built-in agent yes
Computer-use models driving a desktopsynthetic mouse and keyboard at OS level yes — measured above
Pixel-level MCP servers (screenshot plus xdotool) real input events, no CDP yes — this is the category the measurement reproduces
Bridge extensions to your own Chrome — chrome-use, browser-agent-bridge, openchrome native messaging into the browser you are signed into usually, depending on whether the bridge forwards real input or only DOM calls
Playwright / Puppeteer scripts, Playwright MCP, Chrome DevTools MCP CDP commands and DOM events no — measured: 0 tabs visible
Server-side readers and crawlersno browser at all no — and for half the work they do not need one

Anything in the “yes” rows inherits the user's session, logins and permissions. That is why those products ship per-site approval and blocklists for sensitive categories, and why the same property that makes the extension usable by an agent is the one that should make anyone deploying such an agent think about scope.

Setting it up, per route

An agent working in your own browser

Install once, then the agent uses it like you do. Nothing else is needed — the agent's clicks count.

Firefox, desktop and Android  Chrome 116+, Edge, Brave, Vivaldi

Edge asks once to allow extensions from other stores; Opera needs its Install Chrome Extensions add-on first. On Android only Firefox applies — Chrome for Android installs no extensions at all.

An agent driving its own browser

Load the unpacked build. This part is verified — it is steps one and two of the measurement:

ctx = p.chromium.launch_persistent_context(
    profile, headless=False,
    args=["--headless=new",
          f"--disable-extensions-except={ext}",
          f"--load-extension={ext}"])

Two traps, both cost time if you meet them cold. First, the service worker sleeps under Manifest V3: ctx.service_workers is empty until something wakes it, and an empty list looks exactly like “the extension did not load”. Read chrome://extensions instead — developerPrivate.getExtensionsInfo answers regardless. Second, on Chrome 150 --load-extension loads nothing and says nothing, and the flag that used to re-enable it is gone; there, Extensions.loadUnpacked over CDP is the working route (verified on 150.0.7871.187). Playwright's bundled Chromium 145 still honours the switch, which is what the measurement above used.

After that the agent still needs a gesture to trigger a capture. If it can only reach the DOM, it cannot produce one, and this route ends at “loaded but idle”.

No browser at all — often the better answer

Half the work needs no extension and no gesture. Ten of twenty sources in the reading-list measurement became complete citation records over plain HTTP, 0.4 s each, no account and no key. An agent should exhaust that first:

claude mcp add --transport http provinglab https://provinglab.dev/mcp

Where it cannot finish, the reply now carries a nextStep field naming what has to happen in a browser, with both install links and the disclosure that the tool is ours. Runnable recipes for Claude Code, Claude Desktop, other MCP clients, Python and a shell loop are on the recipes page.

What this does not claim