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.
- package/build/src/McpContext.js +24 -3
- package/build/src/browser-connection-manager.js +198 -0
- package/build/src/main.js +16 -3
- package/package.json +1 -1
package/build/src/McpContext.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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",
|