MV3 service worker killed on idle — popup shows stale 'Disconnect' state, WebSocket never reconnects
Summary
The extension's background service worker (MV3) gets killed by the browser after a period of inactivity. When the user opens the popup, it shows "Disconnect" (read from storage), but the service worker hasn't actually re-established the WebSocket to ws://localhost:9009. All MCP tool calls fail with "No connection to browser extension" despite the UI indicating a connection.
Root cause
The background service worker in background.js polls every 1s:
const e = setInterval(async () => {
let n = await pd.getValue() // selectedTabId from storage
if (n && !await xC(n) && (pd.setValue(null), n = null), t || !n) return
const r = RC.defaultWsPort // 9009
t = new WebSocket("ws://localhost:" + r)
...
}, 1000)When the service worker is killed (browser idle / memory pressure), the setInterval stops. The selectedTabId persists in storage, so on next popup open the UI reads it and shows "Disconnect", but the service worker hasn't started yet — and even when it does start, the polling interval may not fire before the MCP client sends tools/list.
Steps to reproduce
- Install Browser MCP extension v1.3.4 in Helium/Chrome
- Connect to a tab — works fine
- Leave the browser idle for a few minutes (or force-kill the service worker via
chrome://extensions) - Open the popup — shows "Disconnect" (stale)
- Try any MCP tool — fails with "No connection to browser extension"
- Clicking "Disconnect" then "Connect" again fixes it (restarts the service worker)
Environment
- Extension: v1.3.4
- Server: @browsermcp/[email protected]
- Browser: Helium (Chromium-based, macOS)
- Client: OpenCode desktop app
Suggested fix
- Service worker keepalive: Use
chrome.runtime.connect()or a lightweight port to keep the service worker alive while an MCP client is connected. - Stale state detection: On popup open, verify the WebSocket is actually open before showing "Disconnect". If the WS is closed, show "Connect" instead.
- Lazy reconnect: When the service worker starts (or the popup opens), immediately attempt to reconnect the WebSocket rather than waiting for the next 1s poll interval.
Source: BrowserMCP/mcp