penpot/mcp/packages/browser-extension
alonso.torres c13fb65e99 ✨ Build the MCP browser extension for Firefox
Build one extension per browser, in dist/chrome and dist/firefox, from
the same source. A script makes each manifest. The code keeps the
chrome namespace, which Firefox implements with promises in Manifest V3.

The Firefox manifest runs the background as an event page, leaves out
the debugger permission, and sets the add-on ID and Firefox 140 as the
minimum version. Its extension page policy drops the default
upgrade-insecure-requests, which would turn ws:// servers into wss://.

Firefox has no debugger API, and it does not freeze background pages:
it only slows their timers, which the plugin's heartbeat survives. The
background therefore unfreezes in place only where the debugger API
exists, and otherwise brings the tab forward and goes back, as a
fallback that should rarely run.

Firefox stops an event page that is idle for 30 s, even while its
WebSocket is open, so the wake agent connection would drop and come
back with each alarm. While it holds a connection, the background calls
an extension API every 20 s, which Firefox counts as activity.
2026-10-26 11:41:57 +01:00
..

Penpot MCP Wake

A Chrome and Firefox extension that wakes a background Penpot tab when an MCP task needs it.

Chrome freezes background tabs, and a frozen tab cannot run the MCP plugin. When a task targets a frozen session, the MCP server asks this extension to activate the tab, waits for the plugin to answer again, and then runs the task.

How it works

  • The content script asks the Penpot page for its MCP session. The page answers with the session ID, MCP server URL, token and connection status.
  • The background (a service worker in Chrome, an event page in Firefox) maps each session to its tab and opens one WebSocket per MCP server and token, on the endpoint the plugin uses. It registers there as a wake agent.
  • When the server sends a wake request, the background wakes the tab as described below.

The popup turns waking on or off and lists the paired sessions and server connections.

How a tab is woken

  • Chrome unfreezes the page in place: the extension attaches the debugger to the tab, sets the page lifecycle state to active, and detaches, without switching tabs. Chrome shows a debugging notice while the extension is attached. Chrome does not allow debugger as an optional permission, so the extension requires it, and Chrome warns about it at install.
  • Firefox has no debugger API, but it does not freeze background pages either: it only slows their timers, which the plugin's heartbeat survives. The extension keeps tabs that hold a session from being unloaded.
  • Fallback, when the debugger fails in Chrome (for instance with DevTools open on the tab) and always in Firefox: the extension brings the tab forward, waits until the page reports that it runs again, then returns to the tab and window that had the focus. Whether the window comes forward depends on the window manager.

Build and load

From mcp/:

pnpm install
pnpm --filter mcp-common run build
pnpm --filter mcp-browser-extension run build

The build writes one extension per browser, in dist/chrome and dist/firefox.

  • Chrome: open chrome://extensions, turn on developer mode, choose "Load unpacked" and pick mcp/packages/browser-extension/dist/chrome.
  • Firefox (140 or later): open about:debugging#/runtime/this-firefox, choose "Load Temporary Add-on…" and pick mcp/packages/browser-extension/dist/firefox/manifest.json. Firefox removes temporary add-ons when it closes.

Reload Penpot tabs opened before the extension was loaded, so the content script runs in them.

The content script runs on https://design.penpot.app and on localhost at any port.

In Chrome, the service worker does not use the exception you grant a page for an untrusted certificate. With the development environment's self-signed certificate it fails with net::ERR_CERT_AUTHORITY_INVALID until Chrome trusts the certificate. On Linux, copy it out of the devenv's main container and add it to Chrome's certificate store:

docker cp <main-container>:/home/selfsigned.crt /tmp/penpot-devenv.crt
certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "Penpot devenv localhost" -i /tmp/penpot-devenv.crt

Restart Chrome afterwards.