From 067d0ecab35b009fa541c36bd540c12355bbb482 Mon Sep 17 00:00:00 2001 From: Gaurav Ghosh Date: Tue, 17 Feb 2026 07:06:25 -0800 Subject: [PATCH] fix: update chrome-devtools-mcp dependency, and add transport error handling. --- .../src/agents/browser/browserManager.test.ts | 5 +++- .../core/src/agents/browser/browserManager.ts | 18 ++++++++++--- .../src/agents/browser/mcpToolWrapper.test.ts | 3 ++- .../core/src/agents/browser/mcpToolWrapper.ts | 26 ++++++++++++++++--- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/core/src/agents/browser/browserManager.test.ts b/packages/core/src/agents/browser/browserManager.test.ts index a8445c9bc9..284d240ad3 100644 --- a/packages/core/src/agents/browser/browserManager.test.ts +++ b/packages/core/src/agents/browser/browserManager.test.ts @@ -133,6 +133,7 @@ describe('BrowserManager', () => { expect(result).toEqual({ content: [{ type: 'text', text: 'Tool result' }], + isError: false, }); }); }); @@ -280,7 +281,9 @@ describe('BrowserManager', () => { await expect(manager.ensureConnection()).rejects.toThrow( /Failed to connect to existing Chrome instance/, ); - await expect(manager.ensureConnection()).rejects.toThrow( + // Create a fresh manager to verify the error message includes remediation steps + const manager2 = new BrowserManager(existingConfig); + await expect(manager2.ensureConnection()).rejects.toThrow( /chrome:\/\/inspect\/#remote-debugging/, ); }); diff --git a/packages/core/src/agents/browser/browserManager.ts b/packages/core/src/agents/browser/browserManager.ts index a79f6d3b19..61203e6588 100644 --- a/packages/core/src/agents/browser/browserManager.ts +++ b/packages/core/src/agents/browser/browserManager.ts @@ -23,9 +23,8 @@ import type { Tool as McpTool } from '@modelcontextprotocol/sdk/types.js'; import { debugLogger } from '../../utils/debugLogger.js'; import type { Config } from '../../config/config.js'; -// Pin chrome-devtools-mcp version for reproducibility -// v0.13.0+ required for --experimental-vision support -const CHROME_DEVTOOLS_MCP_VERSION = '0.13.0'; +// Pin chrome-devtools-mcp version for reproducibility. +const CHROME_DEVTOOLS_MCP_VERSION = '0.17.1'; // Default timeout for MCP operations const MCP_TIMEOUT_MS = 60_000; @@ -277,6 +276,19 @@ export class BrowserManager { args: mcpArgs, }); + this.mcpTransport.onclose = () => { + debugLogger.error( + 'chrome-devtools-mcp transport closed unexpectedly. ' + + 'The MCP server process may have crashed.', + ); + this.rawMcpClient = undefined; + }; + this.mcpTransport.onerror = (error: Error) => { + debugLogger.error( + `chrome-devtools-mcp transport error: ${error.message}`, + ); + }; + // Connect to MCP server — use a shorter timeout for 'existing' mode // since it should connect quickly if remote debugging is enabled. const connectTimeoutMs = diff --git a/packages/core/src/agents/browser/mcpToolWrapper.test.ts b/packages/core/src/agents/browser/mcpToolWrapper.test.ts index 0cc8f52d36..49b5accc0c 100644 --- a/packages/core/src/agents/browser/mcpToolWrapper.test.ts +++ b/packages/core/src/agents/browser/mcpToolWrapper.test.ts @@ -70,9 +70,10 @@ describe('mcpToolWrapper', () => { mockMessageBus, ); - expect(tools).toHaveLength(2); + expect(tools).toHaveLength(3); expect(tools[0].name).toBe('take_snapshot'); expect(tools[1].name).toBe('click'); + expect(tools[2].name).toBe('type_text'); }); it('should return tools with correct description', async () => { diff --git a/packages/core/src/agents/browser/mcpToolWrapper.ts b/packages/core/src/agents/browser/mcpToolWrapper.ts index 10e18eb72c..7db274c58e 100644 --- a/packages/core/src/agents/browser/mcpToolWrapper.ts +++ b/packages/core/src/agents/browser/mcpToolWrapper.ts @@ -80,13 +80,25 @@ class McpToolInvocation extends BaseToolInvocation< async execute(signal: AbortSignal): Promise { try { - // Call the MCP tool via BrowserManager's isolated client - const result: McpToolCallResult = await this.browserManager.callTool( + const callToolPromise = this.browserManager.callTool( this.toolName, this.params, signal, ); + const result: McpToolCallResult = await (signal.aborted + ? Promise.reject(signal.reason ?? new Error('Operation cancelled')) + : Promise.race([ + callToolPromise, + new Promise((_resolve, reject) => { + signal.addEventListener( + 'abort', + () => reject(signal.reason ?? new Error('Operation cancelled')), + { once: true }, + ); + }), + ])); + // Extract text content from MCP response let textContent = ''; if (result.content && Array.isArray(result.content)) { @@ -180,13 +192,21 @@ class TypeTextInvocation extends BaseToolInvocation< }; } - override async execute(): Promise { + override async execute(signal: AbortSignal): Promise { try { const chars = [...this.text]; // Handle Unicode correctly let successCount = 0; let lastError: string | undefined; for (const char of chars) { + if (signal.aborted) { + return { + llmContent: `Error: Operation cancelled after typing ${successCount}/${chars.length} characters.`, + returnDisplay: `Operation cancelled after typing ${successCount}/${chars.length} characters.`, + error: { message: 'Operation cancelled' }, + }; + } + // Map special characters to key names const key = char === ' ' ? 'Space' : char; const result: McpToolCallResult = await this.browserManager.callTool(