#192·mcp

MV3 service worker killed on idle — popup shows stale 'Disconnect' state, WebSocket never reconnects

Author: idea404Created Jul 14, 2026Updated Jul 14, 2026

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:

javascript
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

  1. Install Browser MCP extension v1.3.4 in Helium/Chrome
  2. Connect to a tab — works fine
  3. Leave the browser idle for a few minutes (or force-kill the service worker via chrome://extensions)
  4. Open the popup — shows "Disconnect" (stale)
  5. Try any MCP tool — fails with "No connection to browser extension"
  6. 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

  1. Service worker keepalive: Use chrome.runtime.connect() or a lightweight port to keep the service worker alive while an MCP client is connected.
  2. Stale state detection: On popup open, verify the WebSocket is actually open before showing "Disconnect". If the WS is closed, show "Connect" instead.
  3. 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.