Skip to content

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

Description

@idea404

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

  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/mcp@0.1.3
  • 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions