Skip to main content
chrome-devtools-mcp gives an AI agent raw control of a live Chrome instance, and recent versions added extension tools (install, list, reload, trigger, and uninstall, plus service-worker logs). The Extension.js MCP server (@extension.dev/mcp) covers the same extension capabilities, but build-aware and across browsers. They are complementary, not competing. Run both: let chrome-devtools-mcp drive Chrome itself, and let Extension.js own everything that depends on your project: the build, hot reload, the generated manifest, storage, logs across every context, and the same workflow on Firefox, Edge, and Safari.

When to use which

A useful rule of thumb: if the task touches your project (source, build, manifest, reload), reach for Extension.js. If it touches the browser generically (pages, network, performance), reach for chrome-devtools-mcp.

Install both servers

Extension.js tools are namespaced under extension_*, so they never collide with chrome-devtools-mcp tools. An agent fluent in one transfers to the other.
The chrome-devtools-mcp extension tools (--categoryExtensions) currently require a pipe connection. Attaching to an already-running browser over a WebSocket endpoint (browserUrl / wsEndpoint) is not supported for the extension category until Chrome 149. Extension.js connects to the browser it launched for your dev session, so its extension tools work today.

Capability map

Every chrome-devtools-mcp extension verb has an Extension.js counterpart, plus a build platform around it.

How the action / command triggers work, and their one caveat

chrome-devtools-mcp triggers an action by clicking the real toolbar button, which needs a visible window and a genuine user gesture. Extension.js takes a different approach: it captures the extension’s chrome.action.onClicked (and chrome.commands.onCommand) listeners at build time and replays them on demand. That makes triggering scriptable and reproducible (the natural mode for agentic testing) and, because it only touches addListener, it works on both Chromium and Firefox (verified on both). A single background.service_worker source works on Firefox too: Extension.js translates it to a background.scripts event page for the Firefox target, since Firefox doesn’t run service-worker backgrounds.
Replay invokes your handler without a user gesture. A real toolbar click grants temporary activeTab permission and satisfies gesture-gated APIs (chrome.permissions.request, interactive identity.getAuthToken); a replay does not. So a handler that relies on activeTab (for example, chrome.scripting.executeScript on the active tab or captureVisibleTab) will behave differently than a real click. The result includes gesture: false, and a warning when your manifest declares activeTab. When you need true click fidelity, use chrome-devtools-mcp’s trigger_extension_action (below).

Need a genuine-gesture click? Use chrome-devtools-mcp

The two are complementary, not competing:
  • extension_open (surface: "action"): the everyday path. Replays the handler, cross-browser, headless, no gesture (activeTab not granted). Use it for the vast majority of handler testing.
  • chrome-devtools-mcp trigger_extension_action: a real action invocation through Puppeteer (over the supported pipe transport), so activeTab is granted exactly like a user click. Chromium only. Reach for it only when your handler genuinely depends on the gesture.
Since this page already recommends running both servers, let chrome-devtools-mcp own genuine-gesture browser driving and let Extension.js own the build-aware, cross-browser replay, with no need to duplicate it. See Reload and HMR.

Safe by construction

The replay wraps are injected only into your dev build: the agent bridge is gated on a control port that exists solely during extension dev/preview, and nothing is added to a production build. The addListener wraps delegate transparently to the originals, so your extension behaves identically with or without the bridge.

Next steps