From d32a8c901e3f7a2edb8f888ec9c28f95257f71c5 Mon Sep 17 00:00:00 2001 From: "Tommy D. Rossi" Date: Mon, 23 Feb 2026 15:19:06 +0100 Subject: [PATCH] fix: auto-select extension with active targets when multiple Chrome profiles are connected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When multiple Chrome profiles have the playwriter extension installed, the MCP fails with 'Multiple extensions connected. Specify extensionId.' even if only one profile has playwriter-enabled tabs. The relay's getExtensionConnection fallback now checks if exactly one extension has active targets (connectedTargets > 0) and auto-selects it. Falls through to the existing error when zero or multiple extensions are active, so no regressions for single-extension setups. The fix lives in the relay so all clients (MCP, Stagehand, raw CDP) benefit. CLI session creation is unaffected — it still prompts the user to pick a browser with --browser when multiple profiles are connected. Fixes #52 --- playwriter/src/cdp-relay.ts | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/playwriter/src/cdp-relay.ts b/playwriter/src/cdp-relay.ts index aef6fad..274c640 100644 --- a/playwriter/src/cdp-relay.ts +++ b/playwriter/src/cdp-relay.ts @@ -146,10 +146,26 @@ export async function startPlayWriterCDPRelayServer({ return null } - const fallbackId = getDefaultExtensionId() - if (fallbackId) { - return extensionConnections.get(fallbackId) || null + // Single extension — use it directly + if (extensionConnections.size === 1) { + const fallbackId = getDefaultExtensionId() + if (fallbackId) { + return extensionConnections.get(fallbackId) || null + } } + + // Multiple extensions — auto-select if exactly one has active targets. + // This handles the common case of multiple Chrome profiles with the extension + // installed, where only one profile has playwriter-enabled tabs. (#52) + if (extensionConnections.size > 1) { + const activeExtensions = Array.from(extensionConnections.values()).filter((ext) => { + return ext.connectedTargets.size > 0 + }) + if (activeExtensions.length === 1) { + return activeExtensions[0] + } + } + return null } @@ -838,10 +854,12 @@ export async function startPlayWriterCDPRelayServer({ const clientId = c.req.param('clientId') || 'default' const url = new URL(c.req.url, 'http://localhost') const requestedExtensionId = url.searchParams.get('extensionId') - const resolvedExtension = getExtensionConnection(requestedExtensionId) - const allowDefault = !requestedExtensionId && extensionConnections.size === 1 - const defaultExtension = allowDefault ? getExtensionConnection(null, { allowFallback: true }) : null - const clientExtensionId = resolvedExtension?.id || defaultExtension?.id || null + // When extensionId is explicit, resolve directly. Otherwise use fallback which + // handles single-extension and uniquely-active-extension cases (#52). + const resolvedExtension = requestedExtensionId + ? getExtensionConnection(requestedExtensionId) + : getExtensionConnection(null, { allowFallback: true }) + const clientExtensionId = resolvedExtension?.id || null return { async onOpen(_event, ws) {