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-safe2. 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 exitA 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.logGuard decisions are appended to the log, so you can see exactly what was allowed and why something was denied.
Environment variables
| Variable | Default | Purpose |
|---|---|---|
PLAYWRIGHT_MCP_CDP_PORT | 9222 | Used by all three |
PLAYWRIGHT_MCP_PROFILE | ~/.config/google-chrome-for-testing | Launcher, wrapper |
PLAYWRIGHT_MCP_MODE | cdp | Set to local to launch a browser instead of attaching |
PLAYWRIGHT_MCP_LAUNCHER | ~/bin/chrome-cdp-profile | Wrapper's fallback when the port is dead |
BROWSER_GUARD_TTL_MS | 900000 | How long a tab claim stays valid |
PLAYWRIGHT_BROWSERS_PATH | ~/.cache/ms-playwright | Where 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.