chrome-devtools-mcp-for-extension 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,6 +10,7 @@ import { NetworkCollector, PageCollector } from './PageCollector.js';
10
10
  import { listPages } from './tools/pages.js';
11
11
  import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js';
12
12
  import { WaitForHelper } from './WaitForHelper.js';
13
+ import { BrowserConnectionManager, } from './browser-connection-manager.js';
13
14
  const DEFAULT_TIMEOUT = 5_000;
14
15
  const NAVIGATION_TIMEOUT = 10_000;
15
16
  function getNetworkMultiplierFromString(condition) {
@@ -29,6 +30,7 @@ function getNetworkMultiplierFromString(condition) {
29
30
  export class McpContext {
30
31
  browser;
31
32
  logger;
33
+ connectionManager;
32
34
  // The most recent page state.
33
35
  #pages = [];
34
36
  #selectedPageIdx = 0;
@@ -42,9 +44,14 @@ export class McpContext {
42
44
  #dialog;
43
45
  #nextSnapshotId = 1;
44
46
  #traceResults = [];
45
- constructor(browser, logger) {
47
+ constructor(browser, logger, browserFactory, connectionOptions) {
46
48
  this.browser = browser;
47
49
  this.logger = logger;
50
+ this.connectionManager = new BrowserConnectionManager(connectionOptions);
51
+ // Set up browser instance and factory for reconnection
52
+ if (browserFactory) {
53
+ this.connectionManager.setBrowser(browser, browserFactory);
54
+ }
48
55
  this.#networkCollector = new NetworkCollector(this.browser, (page, collect) => {
49
56
  page.on('request', request => {
50
57
  collect(request);
@@ -65,11 +72,25 @@ export class McpContext {
65
72
  await this.#networkCollector.init();
66
73
  await this.#consoleCollector.init();
67
74
  }
68
- static async from(browser, logger) {
69
- const context = new McpContext(browser, logger);
75
+ static async from(browser, logger, browserFactory, connectionOptions) {
76
+ const context = new McpContext(browser, logger, browserFactory, connectionOptions);
70
77
  await context.#init();
71
78
  return context;
72
79
  }
80
+ /**
81
+ * Update browser instance after reconnection
82
+ */
83
+ async updateBrowser(newBrowser) {
84
+ this.browser = newBrowser;
85
+ this.connectionManager.setBrowser(newBrowser, async () => newBrowser);
86
+ // Reinitialize collectors for new browser
87
+ await this.#networkCollector.init();
88
+ await this.#consoleCollector.init();
89
+ // Recreate pages snapshot
90
+ await this.createPagesSnapshot();
91
+ this.setSelectedPageIdx(0);
92
+ this.logger('Browser instance updated after reconnection');
93
+ }
73
94
  getNetworkRequests() {
74
95
  const page = this.getSelectedPage();
75
96
  return this.#networkCollector.getData(page);
@@ -0,0 +1,198 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /**
7
+ * Default connection manager options
8
+ */
9
+ const DEFAULT_OPTIONS = {
10
+ maxReconnectAttempts: 3,
11
+ initialRetryDelay: 1000, // 1 second
12
+ maxRetryDelay: 10000, // 10 seconds
13
+ enableLogging: true,
14
+ onReconnect: undefined,
15
+ };
16
+ /**
17
+ * Browser Connection Manager
18
+ *
19
+ * Provides automatic reconnection for CDP operations
20
+ */
21
+ export class BrowserConnectionManager {
22
+ reconnectAttempts = 0;
23
+ options;
24
+ browser = null;
25
+ browserFactory = null;
26
+ constructor(options = {}) {
27
+ this.options = { ...DEFAULT_OPTIONS, ...options };
28
+ }
29
+ /**
30
+ * Set browser instance and factory for reconnection
31
+ */
32
+ setBrowser(browser, factory) {
33
+ this.browser = browser;
34
+ this.browserFactory = factory;
35
+ }
36
+ /**
37
+ * Execute an operation with automatic retry on CDP connection errors
38
+ */
39
+ async executeWithRetry(operation, operationName) {
40
+ try {
41
+ return await operation();
42
+ }
43
+ catch (error) {
44
+ if (this.isCDPConnectionError(error)) {
45
+ this.log(`CDP connection error in ${operationName}, attempting reconnect...`);
46
+ return await this.retryWithReconnect(operation, operationName);
47
+ }
48
+ throw error;
49
+ }
50
+ }
51
+ /**
52
+ * Retry operation with exponential backoff
53
+ */
54
+ async retryWithReconnect(operation, operationName) {
55
+ const maxAttempts = this.options.maxReconnectAttempts ?? 3;
56
+ const initialDelay = this.options.initialRetryDelay ?? 1000;
57
+ const maxDelay = this.options.maxRetryDelay ?? 10000;
58
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
59
+ this.reconnectAttempts++;
60
+ const attemptNum = attempt + 1;
61
+ this.log(`Reconnect attempt ${attemptNum}/${maxAttempts} for ${operationName}...`);
62
+ // Exponential backoff with max delay
63
+ const delay = Math.min(initialDelay * Math.pow(2, attempt), maxDelay);
64
+ await this.sleep(delay);
65
+ try {
66
+ await this.reconnectBrowser();
67
+ this.log(`Reconnection successful, retrying ${operationName}...`);
68
+ return await operation();
69
+ }
70
+ catch (error) {
71
+ if (attempt === maxAttempts - 1) {
72
+ // Last attempt failed
73
+ throw this.createReconnectionFailedError(attemptNum, error);
74
+ }
75
+ this.log(`Reconnect attempt ${attemptNum} failed: ${error}`);
76
+ }
77
+ }
78
+ throw new Error('Reconnection logic error'); // Should never reach here
79
+ }
80
+ /**
81
+ * Check if error is a CDP connection error
82
+ */
83
+ isCDPConnectionError(error) {
84
+ const errorMessage = error?.message || '';
85
+ return (errorMessage.includes('Target closed') ||
86
+ errorMessage.includes('Protocol error') ||
87
+ errorMessage.includes('Session closed') ||
88
+ errorMessage.includes('Connection closed') ||
89
+ errorMessage.includes('WebSocket is not open'));
90
+ }
91
+ /**
92
+ * Reconnect browser instance
93
+ */
94
+ async reconnectBrowser() {
95
+ if (!this.browserFactory) {
96
+ throw new Error('Browser factory not set. Cannot reconnect.');
97
+ }
98
+ try {
99
+ // Close old browser if still connected
100
+ if (this.browser?.isConnected()) {
101
+ await this.browser.close().catch(() => {
102
+ // Ignore errors during close
103
+ });
104
+ }
105
+ }
106
+ catch (error) {
107
+ // Ignore errors during cleanup
108
+ }
109
+ // Create new browser instance
110
+ const newBrowser = await this.browserFactory();
111
+ this.browser = newBrowser;
112
+ this.log('Browser reconnected successfully');
113
+ // Notify callback if provided
114
+ if (this.options.onReconnect) {
115
+ await this.options.onReconnect(newBrowser);
116
+ }
117
+ }
118
+ /**
119
+ * Create user-friendly error for reconnection failure
120
+ */
121
+ createReconnectionFailedError(attempts, lastError) {
122
+ const message = `
123
+ ❌ Chrome DevTools接続エラー
124
+
125
+ ${attempts}回の再接続を試みましたが、Chrome DevToolsとの接続を回復できませんでした。
126
+
127
+ 📋 最後のエラー:
128
+ ${lastError?.message || 'Unknown error'}
129
+
130
+ 🔧 解決方法:
131
+ 1. Claude Codeを再起動してください
132
+ 2. Chromeブラウザを完全に終了して再起動してください
133
+ 3. chrome://extensions でChrome DevTools拡張機能を確認してください
134
+
135
+ 詳細: docs/troubleshooting.md#cdp-connection-error
136
+ `.trim();
137
+ const error = new Error(message);
138
+ error.name = 'CDPReconnectionError';
139
+ return error;
140
+ }
141
+ /**
142
+ * Log message if logging is enabled
143
+ */
144
+ log(message) {
145
+ if (this.options.enableLogging !== false) {
146
+ console.log(`[ConnectionManager] ${message}`);
147
+ }
148
+ }
149
+ /**
150
+ * Sleep for specified milliseconds
151
+ */
152
+ sleep(ms) {
153
+ return new Promise((resolve) => setTimeout(resolve, ms));
154
+ }
155
+ /**
156
+ * Get current browser instance
157
+ */
158
+ getBrowser() {
159
+ return this.browser;
160
+ }
161
+ /**
162
+ * Get total reconnection attempts made
163
+ */
164
+ getReconnectAttempts() {
165
+ return this.reconnectAttempts;
166
+ }
167
+ /**
168
+ * Reset reconnection attempt counter
169
+ */
170
+ resetReconnectAttempts() {
171
+ this.reconnectAttempts = 0;
172
+ }
173
+ /**
174
+ * Check if browser is currently connected
175
+ */
176
+ isConnected() {
177
+ return this.browser?.isConnected() ?? false;
178
+ }
179
+ }
180
+ /**
181
+ * Global connection manager instance
182
+ */
183
+ let globalConnectionManager = null;
184
+ /**
185
+ * Get or create global connection manager
186
+ */
187
+ export function getConnectionManager(options) {
188
+ if (!globalConnectionManager) {
189
+ globalConnectionManager = new BrowserConnectionManager(options);
190
+ }
191
+ return globalConnectionManager;
192
+ }
193
+ /**
194
+ * Reset global connection manager (for testing)
195
+ */
196
+ export function resetConnectionManager() {
197
+ globalConnectionManager = null;
198
+ }
package/build/src/main.js CHANGED
@@ -60,7 +60,7 @@ server.server.setRequestHandler(SetLevelRequestSchema, () => {
60
60
  let context;
61
61
  let uiHealthCheckRun = false; // Track if UI health check has been run
62
62
  async function getContext() {
63
- const browser = await resolveBrowser({
63
+ const browserOptions = {
64
64
  browserUrl: args.browserUrl,
65
65
  headless: args.headless,
66
66
  executablePath: args.executablePath,
@@ -73,10 +73,23 @@ async function getContext() {
73
73
  chromeProfile: args.chromeProfile,
74
74
  userDataDir: args.userDataDir,
75
75
  logFile,
76
- });
76
+ };
77
+ const browser = await resolveBrowser(browserOptions);
78
+ // Browser factory function for reconnection
79
+ const browserFactory = async () => {
80
+ logger('Reconnecting browser...');
81
+ return await resolveBrowser(browserOptions);
82
+ };
77
83
  // Always recreate context if browser reference changed or context doesn't exist
78
84
  if (!context || context.browser !== browser) {
79
- context = await McpContext.from(browser, logger);
85
+ // Connection manager options with reconnect callback
86
+ const connectionOptions = {
87
+ onReconnect: async (newBrowser) => {
88
+ logger('Updating context with reconnected browser...');
89
+ await context.updateBrowser(newBrowser);
90
+ },
91
+ };
92
+ context = await McpContext.from(browser, logger, browserFactory, connectionOptions);
80
93
  // Run UI health check only once per browser instance
81
94
  if (!uiHealthCheckRun) {
82
95
  uiHealthCheckRun = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-devtools-mcp-for-extension",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "MCP server for Chrome extension development with Web Store automation. Fork of chrome-devtools-mcp with extension-specific tools.",
5
5
  "type": "module",
6
6
  "bin": "./build/src/index.js",