Skip to content

Commit 7deb543

Browse files
authored
Browser: support non-Chrome existing-session profiles via userDataDir (#48170)
Merged via squash. Prepared head SHA: e490035 Co-authored-by: velvet-shark <[email protected]> Co-authored-by: velvet-shark <[email protected]> Reviewed-by: @velvet-shark
1 parent 3e360ec commit 7deb543

34 files changed

+650
-126
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Docs: https://docs.openclaw.ai
2828
- Refactor/channels: remove the legacy channel shim directories and point channel-specific imports directly at the extension-owned implementations. (#45967) thanks @scoootscooob.
2929
- Docs/Zalo: clarify the Marketplace-bot support matrix and config guidance so the Zalo channel docs match current Bot Creator behavior more closely. (#47552) Thanks @No898.
3030
- secrets: harden read-only SecretRef command paths and diagnostics. (#47794) Thanks @joshavant.
31+
- Browser/existing-session: support `browser.profiles.<name>.userDataDir` so Chrome DevTools MCP can attach to Brave, Edge, and other Chromium-based browsers through their own user data directories. (#48170) thanks @velvet-shark.
3132

3233
### Breaking
3334

docs/cli/browser.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ Use the built-in `user` profile, or create your own `existing-session` profile:
9191
```bash
9292
openclaw browser --browser-profile user tabs
9393
openclaw browser create-profile --name chrome-live --driver existing-session
94+
openclaw browser create-profile --name brave-live --driver existing-session --user-data-dir "~/Library/Application Support/BraveSoftware/Brave-Browser"
9495
openclaw browser --browser-profile chrome-live tabs
9596
```
9697

docs/gateway/configuration-reference.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2443,6 +2443,12 @@ See [Plugins](/tools/plugin).
24432443
openclaw: { cdpPort: 18800, color: "#FF4500" },
24442444
work: { cdpPort: 18801, color: "#0066CC" },
24452445
user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
2446+
brave: {
2447+
driver: "existing-session",
2448+
attachOnly: true,
2449+
userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser",
2450+
color: "#FB542B",
2451+
},
24462452
remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" },
24472453
},
24482454
color: "#FF4500",
@@ -2463,6 +2469,8 @@ See [Plugins](/tools/plugin).
24632469
- In strict mode, use `ssrfPolicy.hostnameAllowlist` and `ssrfPolicy.allowedHostnames` for explicit exceptions.
24642470
- Remote profiles are attach-only (start/stop/reset disabled).
24652471
- `existing-session` profiles are host-only and use Chrome MCP instead of CDP.
2472+
- `existing-session` profiles can set `userDataDir` to target a specific
2473+
Chromium-based browser profile such as Brave or Edge.
24662474
- Auto-detect order: default browser if Chromium-based → Chrome → Brave → Edge → Chromium → Chrome Canary.
24672475
- Control service: loopback only (port derived from `gateway.port`, default `18791`).
24682476
- `extraArgs` appends extra launch flags to local Chromium startup (for example

docs/gateway/doctor.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,18 +155,20 @@ normalizes it to the current host-local Chrome MCP attach model:
155155
Doctor also audits the host-local Chrome MCP path when you use `defaultProfile:
156156
"user"` or a configured `existing-session` profile:
157157

158-
- checks whether Google Chrome is installed on the same host
158+
- checks whether Google Chrome is installed on the same host for default
159+
auto-connect profiles
159160
- checks the detected Chrome version and warns when it is below Chrome 144
160-
- reminds you to enable remote debugging in Chrome at
161-
`chrome://inspect/#remote-debugging`
161+
- reminds you to enable remote debugging in the browser inspect page (for
162+
example `chrome://inspect/#remote-debugging`, `brave://inspect/#remote-debugging`,
163+
or `edge://inspect/#remote-debugging`)
162164

163165
Doctor cannot enable the Chrome-side setting for you. Host-local Chrome MCP
164166
still requires:
165167

166-
- Google Chrome 144+ on the gateway/node host
167-
- Chrome running locally
168-
- remote debugging enabled in Chrome
169-
- approving the first attach consent prompt in Chrome
168+
- a Chromium-based browser 144+ on the gateway/node host
169+
- the browser running locally
170+
- remote debugging enabled in that browser
171+
- approving the first attach consent prompt in the browser
170172

171173
This check does **not** apply to Docker, sandbox, remote-browser, or other
172174
headless flows. Those continue to use raw CDP.

docs/tools/browser.md

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ Browser settings live in `~/.openclaw/openclaw.json`.
8888
attachOnly: true,
8989
color: "#00AA00",
9090
},
91+
brave: {
92+
driver: "existing-session",
93+
attachOnly: true,
94+
userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser",
95+
color: "#FB542B",
96+
},
9197
remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" },
9298
},
9399
},
@@ -114,6 +120,8 @@ Notes:
114120
- Local `openclaw` profiles auto-assign `cdpPort`/`cdpUrl` — set those only for remote CDP.
115121
- `driver: "existing-session"` uses Chrome DevTools MCP instead of raw CDP. Do
116122
not set `cdpUrl` for that driver.
123+
- Set `browser.profiles.<name>.userDataDir` when an existing-session profile
124+
should attach to a non-default Chromium user profile such as Brave or Edge.
117125

118126
## Use Brave (or another Chromium-based browser)
119127

@@ -289,11 +297,11 @@ Defaults:
289297

290298
All control endpoints accept `?profile=<name>`; the CLI uses `--browser-profile`.
291299

292-
## Chrome existing-session via MCP
300+
## Existing-session via Chrome DevTools MCP
293301

294-
OpenClaw can also attach to a running Chrome profile through the official
295-
Chrome DevTools MCP server. This reuses the tabs and login state already open in
296-
that Chrome profile.
302+
OpenClaw can also attach to a running Chromium-based browser profile through the
303+
official Chrome DevTools MCP server. This reuses the tabs and login state
304+
already open in that browser profile.
297305

298306
Official background and setup references:
299307

@@ -305,13 +313,41 @@ Built-in profile:
305313
- `user`
306314

307315
Optional: create your own custom existing-session profile if you want a
308-
different name or color.
316+
different name, color, or browser data directory.
317+
318+
Default behavior:
319+
320+
- The built-in `user` profile uses Chrome MCP auto-connect, which targets the
321+
default local Google Chrome profile.
322+
323+
Use `userDataDir` for Brave, Edge, Chromium, or a non-default Chrome profile:
324+
325+
```json5
326+
{
327+
browser: {
328+
profiles: {
329+
brave: {
330+
driver: "existing-session",
331+
attachOnly: true,
332+
userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser",
333+
color: "#FB542B",
334+
},
335+
},
336+
},
337+
}
338+
```
339+
340+
Then in the matching browser:
341+
342+
1. Open that browser's inspect page for remote debugging.
343+
2. Enable remote debugging.
344+
3. Keep the browser running and approve the connection prompt when OpenClaw attaches.
309345

310-
Then in Chrome:
346+
Common inspect pages:
311347

312-
1. Open `chrome://inspect/#remote-debugging`
313-
2. Enable remote debugging
314-
3. Keep Chrome running and approve the connection prompt when OpenClaw attaches
348+
- Chrome: `chrome://inspect/#remote-debugging`
349+
- Brave: `brave://inspect/#remote-debugging`
350+
- Edge: `edge://inspect/#remote-debugging`
315351

316352
Live attach smoke test:
317353

@@ -327,17 +363,17 @@ What success looks like:
327363
- `status` shows `driver: existing-session`
328364
- `status` shows `transport: chrome-mcp`
329365
- `status` shows `running: true`
330-
- `tabs` lists your already-open Chrome tabs
366+
- `tabs` lists your already-open browser tabs
331367
- `snapshot` returns refs from the selected live tab
332368

333369
What to check if attach does not work:
334370

335-
- Chrome is version `144+`
336-
- remote debugging is enabled at `chrome://inspect/#remote-debugging`
337-
- Chrome showed and you accepted the attach consent prompt
371+
- the target Chromium-based browser is version `144+`
372+
- remote debugging is enabled in that browser's inspect page
373+
- the browser showed and you accepted the attach consent prompt
338374
- `openclaw doctor` migrates old extension-based browser config and checks that
339-
Chrome is installed locally with a compatible version, but it cannot enable
340-
Chrome-side remote debugging for you
375+
Chrome is installed locally for default auto-connect profiles, but it cannot
376+
enable browser-side remote debugging for you
341377

342378
Agent use:
343379

@@ -351,10 +387,11 @@ Notes:
351387

352388
- This path is higher-risk than the isolated `openclaw` profile because it can
353389
act inside your signed-in browser session.
354-
- OpenClaw does not launch Chrome for this driver; it attaches to an existing
355-
session only.
356-
- OpenClaw uses the official Chrome DevTools MCP `--autoConnect` flow here, not
357-
the legacy default-profile remote debugging port workflow.
390+
- OpenClaw does not launch the browser for this driver; it attaches to an
391+
existing session only.
392+
- OpenClaw uses the official Chrome DevTools MCP `--autoConnect` flow here. If
393+
`userDataDir` is set, OpenClaw passes it through to target that explicit
394+
Chromium user data directory.
358395
- Existing-session screenshots support page captures and `--ref` element
359396
captures from snapshots, but not CSS `--element` selectors.
360397
- Existing-session `wait --url` supports exact, substring, and glob patterns

src/agents/tools/browser-tool.actions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ export async function executeActAction(params: {
347347
}
348348
if (!tabs.length) {
349349
throw new Error(
350-
`No Chrome tabs found for profile="${profile}". Make sure Chrome (v144+) is running and has open tabs, then retry.`,
350+
`No browser tabs found for profile="${profile}". Make sure the configured Chromium-based browser (v144+) is running and has open tabs, then retry.`,
351351
{ cause: err },
352352
);
353353
}

src/agents/tools/browser-tool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ export function createBrowserTool(opts?: {
307307
description: [
308308
"Control the browser via OpenClaw's browser control server (status/start/stop/profiles/tabs/open/snapshot/screenshot/actions).",
309309
"Browser choice: omit profile by default for the isolated OpenClaw-managed browser (`openclaw`).",
310-
'For the logged-in user browser on the local host, use profile="user". Chrome (v144+) must be running. Use only when existing logins/cookies matter and the user is present.',
310+
'For the logged-in user browser on the local host, use profile="user". A supported Chromium-based browser (v144+) must be running. Use only when existing logins/cookies matter and the user is present.',
311311
'When a node-hosted browser proxy is available, the tool may auto-route to it. Pin a node with node=<id|name> or target="node".',
312312
"When using refs from snapshot (e.g. e12), keep the same tab: prefer passing targetId from the snapshot response into subsequent actions (act/click/type/etc).",
313313
'For stable, self-resolving refs across calls, use snapshot with refs="aria" (Playwright aria-ref ids). Default refs="role" are role+name-based.',

src/browser/chrome-mcp.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { beforeEach, describe, expect, it, vi } from "vitest";
22
import {
3+
buildChromeMcpArgs,
34
evaluateChromeMcpScript,
45
listChromeMcpTabs,
56
openChromeMcpTab,
@@ -103,6 +104,18 @@ describe("chrome MCP page parsing", () => {
103104
]);
104105
});
105106

107+
it("adds --userDataDir when an explicit Chromium profile path is configured", () => {
108+
expect(buildChromeMcpArgs("/tmp/brave-profile")).toEqual([
109+
"-y",
110+
"chrome-devtools-mcp@latest",
111+
"--autoConnect",
112+
"--experimentalStructuredContent",
113+
"--experimental-page-id-routing",
114+
"--userDataDir",
115+
"/tmp/brave-profile",
116+
]);
117+
});
118+
106119
it("parses new_page text responses and returns the created tab", async () => {
107120
const factory: ChromeMcpSessionFactory = async () => createFakeSession();
108121
setChromeMcpSessionFactoryForTest(factory);
@@ -250,6 +263,33 @@ describe("chrome MCP page parsing", () => {
250263
expect(tabs).toHaveLength(2);
251264
});
252265

266+
it("creates a fresh session when userDataDir changes for the same profile", async () => {
267+
const createdSessions: ChromeMcpSession[] = [];
268+
const closeMocks: Array<ReturnType<typeof vi.fn>> = [];
269+
const factoryCalls: Array<{ profileName: string; userDataDir?: string }> = [];
270+
const factory: ChromeMcpSessionFactory = async (profileName, userDataDir) => {
271+
factoryCalls.push({ profileName, userDataDir });
272+
const session = createFakeSession();
273+
const closeMock = vi.fn().mockResolvedValue(undefined);
274+
session.client.close = closeMock as typeof session.client.close;
275+
createdSessions.push(session);
276+
closeMocks.push(closeMock);
277+
return session;
278+
};
279+
setChromeMcpSessionFactoryForTest(factory);
280+
281+
await listChromeMcpTabs("chrome-live", "/tmp/brave-a");
282+
await listChromeMcpTabs("chrome-live", "/tmp/brave-b");
283+
284+
expect(factoryCalls).toEqual([
285+
{ profileName: "chrome-live", userDataDir: "/tmp/brave-a" },
286+
{ profileName: "chrome-live", userDataDir: "/tmp/brave-b" },
287+
]);
288+
expect(createdSessions).toHaveLength(2);
289+
expect(closeMocks[0]).toHaveBeenCalledTimes(1);
290+
expect(closeMocks[1]).not.toHaveBeenCalled();
291+
});
292+
253293
it("clears failed pending sessions so the next call can retry", async () => {
254294
let factoryCalls = 0;
255295
const factory: ChromeMcpSessionFactory = async () => {

0 commit comments

Comments
 (0)