diff --git a/extension/src/connect.ts b/extension/src/connect.ts index 1bbdd4c..a827fb4 100644 --- a/extension/src/connect.ts +++ b/extension/src/connect.ts @@ -69,6 +69,7 @@ class ConnectPage { continueBtn.addEventListener('click', async () => { buttonRow.style.display = 'none'; + this._tabListContainer.style.display = 'none'; try { const selectedTab = this._selectedTab; if (!selectedTab) { diff --git a/src/browserServerBackend.ts b/src/browserServerBackend.ts index a5ab434..2c5c54c 100644 --- a/src/browserServerBackend.ts +++ b/src/browserServerBackend.ts @@ -30,6 +30,8 @@ import type { Tool } from './tools/tool.js'; export class BrowserServerBackend implements ServerBackend { name = 'Playwright'; version = packageJSON.version; + onclose?: () => void; + private _tools: Tool[]; private _context: Context; private _sessionLog: SessionLog | undefined; @@ -61,6 +63,7 @@ export class BrowserServerBackend implements ServerBackend { } serverClosed() { + this.onclose?.(); void this._context.dispose().catch(logUnhandledError); } } diff --git a/src/extension/cdpRelay.ts b/src/extension/cdpRelay.ts index 7b28b7c..bf45d0b 100644 --- a/src/extension/cdpRelay.ts +++ b/src/extension/cdpRelay.ts @@ -123,8 +123,13 @@ export class CDPRelayServer { } stop(): void { - this._closePlaywrightConnection('Server stopped'); - this._closeExtensionConnection('Server stopped'); + this.closeConnections('Server stopped'); + this._wss.close(); + } + + closeConnections(reason: string) { + this._closePlaywrightConnection(reason); + this._closeExtensionConnection(reason); } private _onConnection(ws: WebSocket, request: http.IncomingMessage): void { @@ -141,6 +146,11 @@ export class CDPRelayServer { } private _handlePlaywrightConnection(ws: WebSocket): void { + if (this._playwrightConnection) { + debugLogger('Rejecting second Playwright connection'); + ws.close(1000, 'Another CDP client already connected'); + return; + } this._playwrightConnection = ws; ws.on('message', async data => { try { @@ -311,6 +321,11 @@ class ExtensionContextFactory implements BrowserContextFactory { }; } + clientDisconnected() { + this._relay.closeConnections('MCP client disconnected'); + this._browserPromise = undefined; + } + private async _obtainBrowser(clientInfo: { name: string, version: string }): Promise { await this._relay.ensureExtensionConnectionForMCPContext(clientInfo); const browser = await playwright.chromium.connectOverCDP(this._relay.cdpEndpoint()); diff --git a/src/extension/main.ts b/src/extension/main.ts index 760aef6..f6d519c 100644 --- a/src/extension/main.ts +++ b/src/extension/main.ts @@ -22,6 +22,18 @@ import type { FullConfig } from '../config.js'; export async function runWithExtension(config: FullConfig, abortController: AbortController) { const contextFactory = await startCDPRelayServer(config.browser.launchOptions.channel || 'chrome', abortController); - const serverBackendFactory = () => new BrowserServerBackend(config, contextFactory); + + let backend: BrowserServerBackend | undefined; + const serverBackendFactory = () => { + if (backend) + throw new Error('Another MCP client is still connected. Only one connection at a time is allowed.'); + backend = new BrowserServerBackend(config, contextFactory); + backend.onclose = () => { + contextFactory.clientDisconnected(); + backend = undefined; + }; + return backend; + }; + await mcpTransport.start(serverBackendFactory, config.server); }