chrome-devtools-mcp 0.22.0 → 0.23.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/README.md +4 -0
- package/build/src/McpContext.js +0 -125
- package/build/src/McpPage.js +198 -0
- package/build/src/McpResponse.js +6 -2
- package/build/src/TextSnapshot.js +230 -0
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +10 -0
- package/build/src/bin/chrome-devtools.js +22 -14
- package/build/src/daemon/client.js +10 -10
- package/build/src/daemon/daemon.js +6 -5
- package/build/src/daemon/utils.js +19 -14
- package/build/src/third_party/THIRD_PARTY_NOTICES +27 -0
- package/build/src/third_party/bundled-packages.json +1 -0
- package/build/src/third_party/index.js +1407 -1401
- package/build/src/tools/inPage.js +2 -33
- package/build/src/tools/network.js +2 -2
- package/build/src/tools/pages.js +209 -146
- package/build/src/tools/screencast.js +19 -8
- package/build/src/version.js +1 -1
- package/package.json +2 -1
|
@@ -15,9 +15,9 @@ import { VERSION } from '../version.js';
|
|
|
15
15
|
import { commands } from './chrome-devtools-cli-options.js';
|
|
16
16
|
import { cliOptions, parseArguments } from './chrome-devtools-mcp-cli-options.js';
|
|
17
17
|
await checkForUpdates('Run `npm install -g chrome-devtools-mcp@latest` and `chrome-devtools start` to update and restart the daemon.');
|
|
18
|
-
async function start(args) {
|
|
18
|
+
async function start(args, sessionId) {
|
|
19
19
|
const combinedArgs = [...args, ...defaultArgs];
|
|
20
|
-
await startDaemon(combinedArgs);
|
|
20
|
+
await startDaemon(combinedArgs, sessionId);
|
|
21
21
|
logDisclaimers(parseArguments(VERSION, combinedArgs));
|
|
22
22
|
}
|
|
23
23
|
const defaultArgs = ['--viaCli', '--experimentalStructuredContent'];
|
|
@@ -56,6 +56,12 @@ const y = yargs(hideBin(process.argv))
|
|
|
56
56
|
.showHelpOnFail(true)
|
|
57
57
|
.usage('chrome-devtools <command> [...args] --flags')
|
|
58
58
|
.usage(`Run 'chrome-devtools <command> --help' for help on the specific command.`)
|
|
59
|
+
.option('sessionId', {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: 'Session ID for daemon scoping',
|
|
62
|
+
default: '',
|
|
63
|
+
hidden: true,
|
|
64
|
+
})
|
|
59
65
|
.demandCommand()
|
|
60
66
|
.version(VERSION)
|
|
61
67
|
.strict()
|
|
@@ -65,8 +71,8 @@ y.command('start', 'Start or restart chrome-devtools-mcp', y => y
|
|
|
65
71
|
.options(startCliOptions)
|
|
66
72
|
.example('$0 start --browserUrl http://localhost:9222', 'Start the server connecting to an existing browser')
|
|
67
73
|
.strict(), async (argv) => {
|
|
68
|
-
if (isDaemonRunning()) {
|
|
69
|
-
await stopDaemon();
|
|
74
|
+
if (isDaemonRunning(argv.sessionId)) {
|
|
75
|
+
await stopDaemon(argv.sessionId);
|
|
70
76
|
}
|
|
71
77
|
// Defaults but we do not want to affect the yargs conflict resolution.
|
|
72
78
|
if (argv.isolated === undefined && argv.userDataDir === undefined) {
|
|
@@ -76,15 +82,15 @@ y.command('start', 'Start or restart chrome-devtools-mcp', y => y
|
|
|
76
82
|
argv.headless = true;
|
|
77
83
|
}
|
|
78
84
|
const args = serializeArgs(cliOptions, argv);
|
|
79
|
-
await start(args);
|
|
85
|
+
await start(args, argv.sessionId);
|
|
80
86
|
process.exit(0);
|
|
81
87
|
}).strict(); // Re-enable strict validation for other commands; this is applied to the yargs instance itself
|
|
82
|
-
y.command('status', 'Checks if chrome-devtools-mcp is running', async () => {
|
|
83
|
-
if (isDaemonRunning()) {
|
|
88
|
+
y.command('status', 'Checks if chrome-devtools-mcp is running', y => y, async (argv) => {
|
|
89
|
+
if (isDaemonRunning(argv.sessionId)) {
|
|
84
90
|
console.log('chrome-devtools-mcp daemon is running.');
|
|
85
91
|
const response = await sendCommand({
|
|
86
92
|
method: 'status',
|
|
87
|
-
});
|
|
93
|
+
}, argv.sessionId);
|
|
88
94
|
if (response.success) {
|
|
89
95
|
const data = JSON.parse(response.result);
|
|
90
96
|
console.log(`pid=${data.pid} socket=${data.socketPath} start-date=${data.startDate} version=${data.version}`);
|
|
@@ -100,11 +106,12 @@ y.command('status', 'Checks if chrome-devtools-mcp is running', async () => {
|
|
|
100
106
|
}
|
|
101
107
|
process.exit(0);
|
|
102
108
|
});
|
|
103
|
-
y.command('stop', 'Stop chrome-devtools-mcp if any', async () => {
|
|
104
|
-
|
|
109
|
+
y.command('stop', 'Stop chrome-devtools-mcp if any', y => y, async (argv) => {
|
|
110
|
+
const sessionId = argv.sessionId;
|
|
111
|
+
if (!isDaemonRunning(sessionId)) {
|
|
105
112
|
process.exit(0);
|
|
106
113
|
}
|
|
107
|
-
await stopDaemon();
|
|
114
|
+
await stopDaemon(sessionId);
|
|
108
115
|
process.exit(0);
|
|
109
116
|
});
|
|
110
117
|
for (const [commandName, commandDef] of Object.entries(commands)) {
|
|
@@ -159,9 +166,10 @@ for (const [commandName, commandDef] of Object.entries(commands)) {
|
|
|
159
166
|
}
|
|
160
167
|
}
|
|
161
168
|
}, async (argv) => {
|
|
169
|
+
const sessionId = argv.sessionId;
|
|
162
170
|
try {
|
|
163
|
-
if (!isDaemonRunning()) {
|
|
164
|
-
await start([]);
|
|
171
|
+
if (!isDaemonRunning(sessionId)) {
|
|
172
|
+
await start([], sessionId);
|
|
165
173
|
}
|
|
166
174
|
const commandArgs = {};
|
|
167
175
|
for (const argName of Object.keys(args)) {
|
|
@@ -173,7 +181,7 @@ for (const [commandName, commandDef] of Object.entries(commands)) {
|
|
|
173
181
|
method: 'invoke_tool',
|
|
174
182
|
tool: commandName,
|
|
175
183
|
args: commandArgs,
|
|
176
|
-
});
|
|
184
|
+
}, sessionId);
|
|
177
185
|
if (response.success) {
|
|
178
186
|
console.log(await handleResponse(JSON.parse(response.result), argv['output-format']));
|
|
179
187
|
}
|
|
@@ -48,12 +48,12 @@ function waitForFile(filePath, removed = false) {
|
|
|
48
48
|
});
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
|
-
export async function startDaemon(mcpArgs = []) {
|
|
52
|
-
if (isDaemonRunning()) {
|
|
51
|
+
export async function startDaemon(mcpArgs = [], sessionId) {
|
|
52
|
+
if (isDaemonRunning(sessionId)) {
|
|
53
53
|
logger('Daemon is already running');
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
|
-
const pidFilePath = getPidFilePath();
|
|
56
|
+
const pidFilePath = getPidFilePath(sessionId);
|
|
57
57
|
if (fs.existsSync(pidFilePath)) {
|
|
58
58
|
fs.unlinkSync(pidFilePath);
|
|
59
59
|
}
|
|
@@ -61,7 +61,7 @@ export async function startDaemon(mcpArgs = []) {
|
|
|
61
61
|
const child = spawn(process.execPath, [DAEMON_SCRIPT_PATH, ...mcpArgs], {
|
|
62
62
|
detached: true,
|
|
63
63
|
stdio: 'ignore',
|
|
64
|
-
env: process.env,
|
|
64
|
+
env: { ...process.env, CHROME_DEVTOOLS_MCP_SESSION_ID: sessionId },
|
|
65
65
|
cwd: process.cwd(),
|
|
66
66
|
windowsHide: true,
|
|
67
67
|
});
|
|
@@ -72,8 +72,8 @@ const SEND_COMMAND_TIMEOUT = 60_000; // ms
|
|
|
72
72
|
/**
|
|
73
73
|
* `sendCommand` opens a socket connection sends a single command and disconnects.
|
|
74
74
|
*/
|
|
75
|
-
export async function sendCommand(command) {
|
|
76
|
-
const socketPath = getSocketPath();
|
|
75
|
+
export async function sendCommand(command, sessionId) {
|
|
76
|
+
const socketPath = getSocketPath(sessionId);
|
|
77
77
|
const socket = net.createConnection({
|
|
78
78
|
path: socketPath,
|
|
79
79
|
});
|
|
@@ -102,13 +102,13 @@ export async function sendCommand(command) {
|
|
|
102
102
|
transport.send(JSON.stringify(command));
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
|
-
export async function stopDaemon() {
|
|
106
|
-
if (!isDaemonRunning()) {
|
|
105
|
+
export async function stopDaemon(sessionId) {
|
|
106
|
+
if (!isDaemonRunning(sessionId)) {
|
|
107
107
|
logger('Daemon is not running');
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
|
-
const pidFilePath = getPidFilePath();
|
|
111
|
-
await sendCommand({ method: 'stop' });
|
|
110
|
+
const pidFilePath = getPidFilePath(sessionId);
|
|
111
|
+
await sendCommand({ method: 'stop' }, sessionId);
|
|
112
112
|
await waitForFile(pidFilePath, /*removed=*/ true);
|
|
113
113
|
}
|
|
114
114
|
export async function handleResponse(response, format) {
|
|
@@ -11,19 +11,20 @@ import process from 'node:process';
|
|
|
11
11
|
import { logger } from '../logger.js';
|
|
12
12
|
import { Client, PipeTransport, StdioClientTransport, } from '../third_party/index.js';
|
|
13
13
|
import { VERSION } from '../version.js';
|
|
14
|
-
import { DAEMON_CLIENT_NAME,
|
|
15
|
-
const
|
|
16
|
-
|
|
14
|
+
import { DAEMON_CLIENT_NAME, getPidFilePath, getSocketPath, INDEX_SCRIPT_PATH, IS_WINDOWS, isDaemonRunning, } from './utils.js';
|
|
15
|
+
const sessionId = process.env.CHROME_DEVTOOLS_MCP_SESSION_ID || '';
|
|
16
|
+
logger(`Daemon sessionId: ${sessionId}`);
|
|
17
|
+
if (isDaemonRunning(sessionId)) {
|
|
17
18
|
logger('Another daemon process is running.');
|
|
18
19
|
process.exit(1);
|
|
19
20
|
}
|
|
20
|
-
const pidFilePath = getPidFilePath();
|
|
21
|
+
const pidFilePath = getPidFilePath(sessionId);
|
|
21
22
|
fs.mkdirSync(path.dirname(pidFilePath), {
|
|
22
23
|
recursive: true,
|
|
23
24
|
});
|
|
24
25
|
fs.writeFileSync(pidFilePath, process.pid.toString());
|
|
25
26
|
logger(`Writing ${process.pid.toString()} to ${pidFilePath}`);
|
|
26
|
-
const socketPath = getSocketPath();
|
|
27
|
+
const socketPath = getSocketPath(sessionId);
|
|
27
28
|
const startDate = new Date();
|
|
28
29
|
const mcpServerArgs = process.argv.slice(2);
|
|
29
30
|
let mcpClient = null;
|
|
@@ -13,46 +13,50 @@ export const INDEX_SCRIPT_PATH = path.join(import.meta.dirname, '..', 'bin', 'ch
|
|
|
13
13
|
const APP_NAME = 'chrome-devtools-mcp';
|
|
14
14
|
export const DAEMON_CLIENT_NAME = 'chrome-devtools-cli-daemon';
|
|
15
15
|
// Using these paths due to strict limits on the POSIX socket path length.
|
|
16
|
-
export function getSocketPath() {
|
|
16
|
+
export function getSocketPath(sessionId) {
|
|
17
17
|
const uid = os.userInfo().uid;
|
|
18
|
+
const suffix = sessionId ? `-${sessionId}` : '';
|
|
19
|
+
const appName = APP_NAME + suffix;
|
|
18
20
|
if (IS_WINDOWS) {
|
|
19
21
|
// Windows uses Named Pipes, not file paths.
|
|
20
22
|
// This format is required for server.listen()
|
|
21
|
-
return path.join('\\\\.\\pipe',
|
|
23
|
+
return path.join('\\\\.\\pipe', appName, 'server.sock');
|
|
22
24
|
}
|
|
23
25
|
// 1. Try XDG_RUNTIME_DIR (Linux standard, sometimes macOS)
|
|
24
26
|
if (process.env.XDG_RUNTIME_DIR) {
|
|
25
|
-
return path.join(process.env.XDG_RUNTIME_DIR,
|
|
27
|
+
return path.join(process.env.XDG_RUNTIME_DIR, appName, 'server.sock');
|
|
26
28
|
}
|
|
27
29
|
// 2. macOS/Unix Fallback: Use /tmp/
|
|
28
30
|
// We use /tmp/ because it is much shorter than ~/Library/Application Support/
|
|
29
31
|
// and keeps us well under the 104-character limit.
|
|
30
|
-
return path.join('/tmp', `${
|
|
32
|
+
return path.join('/tmp', `${appName}-${uid}.sock`);
|
|
31
33
|
}
|
|
32
|
-
export function getRuntimeHome() {
|
|
34
|
+
export function getRuntimeHome(sessionId) {
|
|
33
35
|
const platform = os.platform();
|
|
34
36
|
const uid = os.userInfo().uid;
|
|
37
|
+
const suffix = sessionId ? `-${sessionId}` : '';
|
|
38
|
+
const appName = APP_NAME + suffix;
|
|
35
39
|
// 1. Check for the modern Unix standard
|
|
36
40
|
if (process.env.XDG_RUNTIME_DIR) {
|
|
37
|
-
return path.join(process.env.XDG_RUNTIME_DIR,
|
|
41
|
+
return path.join(process.env.XDG_RUNTIME_DIR, appName);
|
|
38
42
|
}
|
|
39
43
|
// 2. Fallback for macOS and older Linux
|
|
40
44
|
if (platform === 'darwin' || platform === 'linux') {
|
|
41
45
|
// /tmp is cleared on boot, making it perfect for PIDs
|
|
42
|
-
return path.join('/tmp', `${
|
|
46
|
+
return path.join('/tmp', `${appName}-${uid}`);
|
|
43
47
|
}
|
|
44
48
|
// 3. Windows Fallback
|
|
45
|
-
return path.join(os.tmpdir(),
|
|
49
|
+
return path.join(os.tmpdir(), appName);
|
|
46
50
|
}
|
|
47
51
|
export const IS_WINDOWS = os.platform() === 'win32';
|
|
48
|
-
export function getPidFilePath() {
|
|
49
|
-
const runtimeDir = getRuntimeHome();
|
|
52
|
+
export function getPidFilePath(sessionId) {
|
|
53
|
+
const runtimeDir = getRuntimeHome(sessionId);
|
|
50
54
|
return path.join(runtimeDir, 'daemon.pid');
|
|
51
55
|
}
|
|
52
|
-
export function getDaemonPid() {
|
|
56
|
+
export function getDaemonPid(sessionId) {
|
|
53
57
|
try {
|
|
54
|
-
const pidFile = getPidFilePath();
|
|
55
|
-
logger(`Daemon pid file ${pidFile}`);
|
|
58
|
+
const pidFile = getPidFilePath(sessionId);
|
|
59
|
+
logger(`Daemon pid file ${pidFile} sessionId=${sessionId}`);
|
|
56
60
|
if (!fs.existsSync(pidFile)) {
|
|
57
61
|
return null;
|
|
58
62
|
}
|
|
@@ -68,7 +72,8 @@ export function getDaemonPid() {
|
|
|
68
72
|
return null;
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
|
-
export function isDaemonRunning(
|
|
75
|
+
export function isDaemonRunning(sessionId) {
|
|
76
|
+
const pid = getDaemonPid(sessionId);
|
|
72
77
|
if (pid) {
|
|
73
78
|
try {
|
|
74
79
|
process.kill(pid, 0); // Throws if process doesn't exist
|
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
Name: urlpattern-polyfill
|
|
2
|
+
URL: https://github.com/kenchris/urlpattern-polyfill
|
|
3
|
+
Version: 10.1.0
|
|
4
|
+
License: MIT
|
|
5
|
+
|
|
6
|
+
Copyright 2020 Intel Corporation
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in
|
|
16
|
+
all copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
24
|
+
THE SOFTWARE.
|
|
25
|
+
|
|
26
|
+
-------------------- DEPENDENCY DIVIDER --------------------
|
|
27
|
+
|
|
1
28
|
Name: core-js
|
|
2
29
|
URL: https://core-js.io
|
|
3
30
|
Version: 3.49.0
|