AI agents: MCP and the agent-friendly actions
@extension-use/mcp turns a paired browser into MCP tools. Any MCP client (Claude Code, Claude Desktop, IDE agents, your own) can then read and act in your real browser, signed in as you. Every call still goes through the extension's grants and site policy, shows "Controlled by remote: …" on the page, and stops when you press Pause all control.
agent ──MCP (stdio)──▶ extension-use-mcp ──WebSocket──▶ relay ◀── your browser (paired)Set it up
Start a relay and pair your browser. See the remote control guide.
Give the agent its own token, limited to what it needs:
bashnpx @extension-use/relay token create agent --grants "runtime.*,tabs.*,page.*,dom.*,wait.*,overlay.*,capture.*,run.*"Register the MCP server with your client. For Claude Code:
bashclaude mcp add extension-use -e EXTENSION_USE_TOKEN=eu_ctl_… -- npx -y @extension-use/mcp --relay http://127.0.0.1:7777For clients with a JSON config:
json{ "mcpServers": { "extension-use": { "command": "npx", "args": ["-y", "@extension-use/mcp", "--relay", "http://127.0.0.1:7777"], "env": { "EXTENSION_USE_TOKEN": "eu_ctl_…" } } } }
Options:
--device <id>: pick a browser when several are paired. The default is the most recently connected one.--include "dom.*,tabs.*"and--exclude "tabs.close": narrow the tools further.
What the agent gets
One tool per action the token allows, named with underscores (
dom_click,tabs_open,run_sequence). The list comes from the browser itself (runtime.describe), so your extension's own actions (app_save, …) appear too, with their descriptions and schemas.Server instructions that teach the snapshot → ref → act loop.
dom_snapshotreturns a compact text tree. By default it covers only the visible part of the page (viewportOnly: true); passviewportOnly: falsefor the whole page. Interactive elements carry refs:- heading "Order form" [level=1] [ref=e3] - textbox "Email" [ref=e7]: "" - button "Save draft" [ref=e12]The agent then calls
dom_click { "locator": { "ref": "e12" } }.capture_screenshotandcapture_fullPagereturn an image the model can see.dom_htmlreturns the page's markup when the agent needs more than the snapshot. Through MCP it is cut at 50,000 characters unless the agent passesmaxLength, or alocatorfor one part.edit_*tools change the page (sanitized HTML, text, attributes, removing elements). They are marked destructive, as aretabs_close,windows_close,downloads_cancel,bookmarks_remove,history_delete,cookies_setandcookies_remove, so MCP clients can ask before running them. Leave them out with--exclude "edit.*".Browser data (
downloads_*,bookmarks_*,history_*,cookies_*) appears only if the extension enables those opt-in groups. Cookie values can sign someone in as the person, so think twice before giving an agentcookies.*.run_sequencebatches known steps into one call (see below).Errors come back as tool errors with a code, such as
INVALID_INPUT: locator: …orBLOCKED_BY_POLICY: …. A paused browser returnsBLOCKED_BY_POLICYwith "paused", and the instructions tell the agent to stop and ask you.No browser yet: the server offers a single
browser_statustool that explains what's missing.
run.sequence: several steps, one round trip
{
"steps": [
{ "action": "dom.fill", "input": { "locator": { "ref": "e7" }, "value": "me@example.com" } },
{ "action": "dom.click", "input": { "locator": { "ref": "e12" } }, "pauseMs": 300 },
{ "action": "wait.for", "input": { "locator": { "text": "Saved" } } }
],
"onError": "stop"
}- Steps are data only: no conditions, loops or expressions. Logic stays in the controller, which keeps the extension within store policy.
- Each step is checked as its own call: grants, input checks, site policy and audit all apply. It can't nest another
run.sequence. - The result lists every step's
ok/outputorerror.onError: "continue"keeps going after a failure, and each step can override it.
runtime.describe and typed clients
runtime.describe lists the actions a controller may call, with JSON schemas for their input and output. It's what the MCP server uses, and it powers codegen for your own actions:
EXTENSION_USE_TOKEN=eu_ctl_… npx extension-use codegen --relay http://127.0.0.1:7777 --out src/extension-actions.tsOr, from a saved JSON.stringify(await ext.runtime.describe()):
npx extension-use codegen --from describe.json --out src/extension-actions.tsimport type { CoreActions } from "@extension-use/client";
import type { HostActions } from "./extension-actions";
const ext = await connect<CoreActions & HostActions>(frame());
await ext.app.save({ note: "read later" }); // typed input and outputUse it from your own agent code
import { createExtensionUseMcpServer } from "@extension-use/mcp";
import { connect } from "@extension-use/client";
import { remote } from "@extension-use/client/remote";
const { server } = createExtensionUseMcpServer({
connect: () => connect(remote({ relay: "http://127.0.0.1:7777", token })),
include: ["dom.*", "page.*"],
});
await server.connect(yourTransport); // any MCP server transportOr skip MCP and drive the client directly, as in recipe 5.
Safety notes
- The browser is signed in as you. Give agents narrow tokens, and revoke them when you're done (
token revoke agent). - Password and payment fields are refused by default, and extension stores and
chrome://pages are always blocked. Add your own blocked sites withpolicy.blockedSitesindefineHost(). - Every action shows in the page, on the toolbar badge and in the control panel's activity log. Pause all control stops everything at once.