Skip to content
osb

Install

Three pieces: a launcher that owns the browser, a wrapper the Playwright MCP invokes, and a plugin that guards tab ownership. The order below matters only for the last step — the browser itself no longer has to be running first.

1. Clone and link

git clone https://github.com/edrayel/opencode-shared-browser ~/dev/opencode-shared-browser
cp ~/dev/opencode-shared-browser/bin/chrome-cdp-profile ~/bin/
cp ~/dev/opencode-shared-browser/bin/playwright-mcp-chromium-safe ~/bin/
cp ~/dev/opencode-shared-browser/plugins/browser-guard.ts ~/.config/opencode/plugins/
chmod +x ~/bin/chrome-cdp-profile ~/bin/playwright-mcp-chromium-safe

2. Register the plugin

Add it to the plugin array in ~/.config/opencode/opencode.jsonc:

"plugin": [
  "opencode-supermemory@2.0.15",
  "/home/you/.config/opencode/plugins/browser-guard.ts"
]

3. Point the MCP at the wrapper

Replace the playwright entry's command. The wrapper passes--cdp-endpoint, and deliberately does not pass--executable-path, --isolated,--user-data-dir or --no-sandbox: in CDP mode the MCP never launches a browser, so those do not apply.

"playwright": {
  "type": "local",
  "timeout": 30000,
  "command": ["/home/you/bin/playwright-mcp-chromium-safe"],
  "enabled": true
}

4. Start the browser

Optional but worth doing, so the first tool call is not waiting on a Chrome launch. If the port is dead when opencode starts, the wrapper starts the browser itself.

$ ~/bin/chrome-cdp-profile
chrome-cdp-profile: up on 9222 (profile ~/.config/google-chrome-for-testing)

Signing in

Point the profile at whatever Chrome holds your sessions. By default the launcher uses a Chrome for Testing profile at~/.config/google-chrome-for-testing. Open it once by hand, sign in, and close it properly — see below.

Use a dedicated profile, not your daily browser profile. Every opencode instance attached over CDP can act as you.

Stopping cleanly

--stop sends CDP Browser.close rather than a signal. This matters more than it sounds:

$ pkill -f "remote-debugging-port=9222"     # leaves exit_type unset
$ ~/bin/chrome-cdp-profile --stop                # records a clean exit

A SIGTERM kill leaves exit_type unset in the profile, which Chrome reads as a crash and answers by restoring the previous session — regardless of flags. That is how yesterday's tabs come back as unowned tabs that block every close.

Also note that pkill -f "remote-debugging-port=9222" matches its own invoking shell and kills it. Use a bracket pattern if you must:pkill -f '[r]emote-debugging-port=9222'.

Verifying

$ curl -s http://127.0.0.1:9222/json/version | head -2
$ tail ~/.local/share/opencode/mcp-logs/browser-guard.log

Guard decisions are appended to the log, so you can see exactly what was allowed and why something was denied.

Environment variables

VariableDefaultPurpose
PLAYWRIGHT_MCP_CDP_PORT9222Used by all three
PLAYWRIGHT_MCP_PROFILE~/.config/google-chrome-for-testingLauncher, wrapper
PLAYWRIGHT_MCP_MODEcdpSet to local to launch a browser instead of attaching
PLAYWRIGHT_MCP_LAUNCHER~/bin/chrome-cdp-profileWrapper's fallback when the port is dead
BROWSER_GUARD_TTL_MS900000How long a tab claim stays valid
PLAYWRIGHT_BROWSERS_PATH~/.cache/ms-playwrightWhere Chromium is resolved from

Troubleshooting

The MCP reports no browser on 9222. It tried to start one and failed. Usually a stale profile lock or a missing Chromium build — run the launcher by hand to see the real error.

No browser tools in the session at all. opencode drops an MCP that failed during startup, so the tools stay missing until it restarts. The reason is in the MCP log; search for FATAL.

Chromium exits with "No usable sandbox". Ubuntu 23.10 and later disable unprivileged user namespaces. The launcher passes--no-sandbox already; drop it if your distribution allows sandboxes.