fix: update chrome-devtools-mcp dependency, and add transport error handling.

This commit is contained in:
Gaurav Ghosh
2026-02-17 07:06:25 -08:00
parent fb1b2891cc
commit 067d0ecab3
4 changed files with 44 additions and 8 deletions
@@ -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/,
);
});
@@ -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 =
@@ -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 () => {
@@ -80,13 +80,25 @@ class McpToolInvocation extends BaseToolInvocation<
async execute(signal: AbortSignal): Promise<ToolResult> {
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<never>((_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<ToolResult> {
override async execute(signal: AbortSignal): Promise<ToolResult> {
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(