@mcp-b/chrome-devtools-mcp 1.7.1 → 1.7.2
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/browser.js +95 -45
- package/package.json +1 -1
package/build/src/browser.js
CHANGED
|
@@ -10,6 +10,61 @@ import { logger } from './logger.js';
|
|
|
10
10
|
import { puppeteer } from './third_party/index.js';
|
|
11
11
|
/** Cached browser instance for reuse across calls. */
|
|
12
12
|
let browser;
|
|
13
|
+
/**
|
|
14
|
+
* Get Chrome's default user data directory for the given platform and channel.
|
|
15
|
+
*
|
|
16
|
+
* @returns The platform-specific path to Chrome's user data directory.
|
|
17
|
+
*/
|
|
18
|
+
function getChromeDefaultUserDataDir(channel = 'stable') {
|
|
19
|
+
const platform = os.platform();
|
|
20
|
+
if (platform === 'darwin') {
|
|
21
|
+
const suffix = channel === 'stable'
|
|
22
|
+
? ''
|
|
23
|
+
: ` ${channel.charAt(0).toUpperCase() + channel.slice(1)}`;
|
|
24
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Google', `Chrome${suffix}`);
|
|
25
|
+
}
|
|
26
|
+
if (platform === 'win32') {
|
|
27
|
+
const appData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local');
|
|
28
|
+
const suffix = channel === 'stable'
|
|
29
|
+
? ''
|
|
30
|
+
: ` ${channel.charAt(0).toUpperCase() + channel.slice(1)}`;
|
|
31
|
+
return path.join(appData, 'Google', `Chrome${suffix}`, 'User Data');
|
|
32
|
+
}
|
|
33
|
+
// Linux
|
|
34
|
+
const channelSuffix = channel === 'stable'
|
|
35
|
+
? ''
|
|
36
|
+
: channel === 'beta'
|
|
37
|
+
? '-beta'
|
|
38
|
+
: '-unstable';
|
|
39
|
+
return path.join(os.homedir(), '.config', `google-chrome${channelSuffix}`);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Try to read a WebSocket endpoint from a DevToolsActivePort file.
|
|
43
|
+
*
|
|
44
|
+
* @param userDataDir - Directory containing the DevToolsActivePort file.
|
|
45
|
+
* @returns The WebSocket endpoint URL, or undefined if the file doesn't exist or is invalid.
|
|
46
|
+
*/
|
|
47
|
+
function readDevToolsActivePort(userDataDir) {
|
|
48
|
+
const portPath = path.join(userDataDir, 'DevToolsActivePort');
|
|
49
|
+
try {
|
|
50
|
+
const fileContent = fs.readFileSync(portPath, 'utf8');
|
|
51
|
+
const [rawPort, rawPath] = fileContent
|
|
52
|
+
.split('\n')
|
|
53
|
+
.map(line => line.trim())
|
|
54
|
+
.filter(line => !!line);
|
|
55
|
+
if (!rawPort || !rawPath) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const port = parseInt(rawPort, 10);
|
|
59
|
+
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return `ws://127.0.0.1:${port}${rawPath}`;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
13
68
|
/**
|
|
14
69
|
* Create a target filter for Puppeteer that excludes internal Chrome pages.
|
|
15
70
|
*
|
|
@@ -78,64 +133,59 @@ export async function ensureBrowserConnected(options) {
|
|
|
78
133
|
else if (channel || options.userDataDir) {
|
|
79
134
|
const userDataDir = options.userDataDir;
|
|
80
135
|
if (userDataDir) {
|
|
81
|
-
//
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const [rawPort, rawPath] = fileContent
|
|
86
|
-
.split('\n')
|
|
87
|
-
.map(line => {
|
|
88
|
-
return line.trim();
|
|
89
|
-
})
|
|
90
|
-
.filter(line => {
|
|
91
|
-
return !!line;
|
|
92
|
-
});
|
|
93
|
-
if (!rawPort || !rawPath) {
|
|
94
|
-
throw new Error(`Invalid DevToolsActivePort '${fileContent}' found`);
|
|
95
|
-
}
|
|
96
|
-
const port = parseInt(rawPort, 10);
|
|
97
|
-
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
98
|
-
throw new Error(`Invalid port '${rawPort}' found`);
|
|
99
|
-
}
|
|
100
|
-
const browserWSEndpoint = `ws://127.0.0.1:${port}${rawPath}`;
|
|
101
|
-
connectOptions.browserWSEndpoint = browserWSEndpoint;
|
|
136
|
+
// Explicit user data dir provided
|
|
137
|
+
const wsEndpoint = readDevToolsActivePort(userDataDir);
|
|
138
|
+
if (wsEndpoint) {
|
|
139
|
+
connectOptions.browserWSEndpoint = wsEndpoint;
|
|
102
140
|
}
|
|
103
|
-
|
|
104
|
-
throw new Error(`Could not connect to Chrome in ${userDataDir}. Check if Chrome is running and remote debugging is enabled
|
|
105
|
-
cause: error,
|
|
106
|
-
});
|
|
141
|
+
else {
|
|
142
|
+
throw new Error(`Could not connect to Chrome in ${userDataDir}. Check if Chrome is running and remote debugging is enabled.`);
|
|
107
143
|
}
|
|
108
144
|
}
|
|
109
145
|
else {
|
|
110
146
|
if (!channel) {
|
|
111
147
|
throw new Error('Channel must be provided if userDataDir is missing');
|
|
112
148
|
}
|
|
113
|
-
//
|
|
149
|
+
// Collect candidate WebSocket endpoints from multiple directories.
|
|
150
|
+
// Try each one in order — stale DevToolsActivePort files are common,
|
|
151
|
+
// so we attempt the actual connection before moving to the next.
|
|
114
152
|
const profileDirName = channel && channel !== 'stable'
|
|
115
153
|
? `chrome-profile-${channel}`
|
|
116
154
|
: 'chrome-profile';
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
155
|
+
const mcpUserDataDir = path.join(os.homedir(), '.cache', 'chrome-devtools-mcp', profileDirName);
|
|
156
|
+
const chromeUserDataDir = getChromeDefaultUserDataDir(channel);
|
|
157
|
+
// Chrome's default profile is checked first — this is the user's
|
|
158
|
+
// real browser with remote debugging enabled via chrome://inspect.
|
|
159
|
+
// The MCP cache dir is checked second as a fallback for instances
|
|
160
|
+
// launched by the MCP server itself.
|
|
161
|
+
const candidates = [];
|
|
162
|
+
const chromeWsEndpoint = readDevToolsActivePort(chromeUserDataDir);
|
|
163
|
+
if (chromeWsEndpoint) {
|
|
164
|
+
candidates.push({ dir: chromeUserDataDir, wsEndpoint: chromeWsEndpoint });
|
|
165
|
+
}
|
|
166
|
+
const mcpWsEndpoint = readDevToolsActivePort(mcpUserDataDir);
|
|
167
|
+
if (mcpWsEndpoint) {
|
|
168
|
+
candidates.push({ dir: mcpUserDataDir, wsEndpoint: mcpWsEndpoint });
|
|
169
|
+
}
|
|
170
|
+
if (candidates.length === 0) {
|
|
171
|
+
throw new Error(`Could not connect to Chrome ${channel} channel. Checked ${mcpUserDataDir} and ${chromeUserDataDir}. Ensure Chrome is running with remote debugging enabled (chrome://inspect/#remote-debugging).`);
|
|
172
|
+
}
|
|
173
|
+
// Try each candidate endpoint, returning the first that connects
|
|
174
|
+
for (const candidate of candidates) {
|
|
175
|
+
try {
|
|
176
|
+
logger(`Trying DevToolsActivePort from ${candidate.dir}: ${candidate.wsEndpoint}`);
|
|
177
|
+
browser = await puppeteer.connect({
|
|
178
|
+
...connectOptions,
|
|
179
|
+
browserWSEndpoint: candidate.wsEndpoint,
|
|
180
|
+
});
|
|
181
|
+
logger(`Connected via ${candidate.dir}`);
|
|
182
|
+
return browser;
|
|
128
183
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
throw new Error(`Invalid port '${rawPort}' found`);
|
|
184
|
+
catch (err) {
|
|
185
|
+
logger(`Failed to connect via ${candidate.dir}: ${err.message}`);
|
|
132
186
|
}
|
|
133
|
-
const browserWSEndpoint = `ws://127.0.0.1:${port}${rawPath}`;
|
|
134
|
-
connectOptions.browserWSEndpoint = browserWSEndpoint;
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
throw new Error(`Could not connect to Chrome ${channel} channel in ${derivedUserDataDir}. Check if Chrome is running and was launched with remote debugging enabled.`, { cause: error });
|
|
138
187
|
}
|
|
188
|
+
throw new Error(`Could not connect to Chrome ${channel} channel. Tried ${candidates.map(c => c.dir).join(' and ')}. Ensure Chrome is running with remote debugging enabled (chrome://inspect/#remote-debugging).`);
|
|
139
189
|
}
|
|
140
190
|
}
|
|
141
191
|
else {
|