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.
@@ -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
- if (!isDaemonRunning()) {
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, getDaemonPid, getPidFilePath, getSocketPath, INDEX_SCRIPT_PATH, IS_WINDOWS, isDaemonRunning, } from './utils.js';
15
- const pid = getDaemonPid();
16
- if (isDaemonRunning(pid)) {
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', APP_NAME, 'server.sock');
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, APP_NAME, 'server.sock');
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', `${APP_NAME}-${uid}.sock`);
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, APP_NAME);
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', `${APP_NAME}-${uid}`);
46
+ return path.join('/tmp', `${appName}-${uid}`);
43
47
  }
44
48
  // 3. Windows Fallback
45
- return path.join(os.tmpdir(), APP_NAME);
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(pid = getDaemonPid()) {
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
@@ -5,6 +5,7 @@
5
5
  "debug": "4.4.3",
6
6
  "lighthouse": "13.1.0",
7
7
  "semver": "^7.7.4",
8
+ "urlpattern-polyfill": "^10.1.0",
8
9
  "yargs": "18.0.0",
9
10
  "puppeteer-core": "24.42.0"
10
11
  }