chrome-devtools-mcp-for-extension 0.15.0 → 0.15.1
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 +55 -8
- package/build/src/profile-resolver.js +24 -15
- package/package.json +1 -1
package/build/src/browser.js
CHANGED
|
@@ -444,6 +444,7 @@ export async function launch(options) {
|
|
|
444
444
|
console.error(`[profiles] Using: ${userDataDir}`);
|
|
445
445
|
console.error(` Reason: ${resolved.reason}`);
|
|
446
446
|
console.error(` Project: ${resolved.projectName} (${resolved.hash})`);
|
|
447
|
+
console.error(` Client: ${resolved.clientId}`);
|
|
447
448
|
if (resolved.reason === 'AUTO') {
|
|
448
449
|
console.error(` Root: ${process.cwd()}`);
|
|
449
450
|
}
|
|
@@ -546,15 +547,17 @@ export async function launch(options) {
|
|
|
546
547
|
console.error(` Headless: ${headless}`);
|
|
547
548
|
console.error(` Args: ${JSON.stringify(args, null, 2)}`);
|
|
548
549
|
console.error(` Ignored Default Args: ["--disable-extensions", "--enable-automation"]`);
|
|
550
|
+
// IMPORTANT: Chrome extensions (especially MV3 content scripts and service workers)
|
|
551
|
+
// DO NOT work in headless mode. Always use headless:false when loading extensions.
|
|
552
|
+
// Reference: https://groups.google.com/a/chromium.org/g/headless-dev/c/nEoeUkoNI0o/m/9KZ4Os46AQAJ
|
|
553
|
+
const effectiveHeadless = extensionPaths.length > 0 ? false : headless;
|
|
554
|
+
if (extensionPaths.length > 0 && headless) {
|
|
555
|
+
console.warn('⚠️ WARNING: Extensions require headful mode. Forcing headless:false');
|
|
556
|
+
}
|
|
557
|
+
let browser;
|
|
558
|
+
let finalUserDataDir = userDataDir;
|
|
549
559
|
try {
|
|
550
|
-
|
|
551
|
-
// DO NOT work in headless mode. Always use headless:false when loading extensions.
|
|
552
|
-
// Reference: https://groups.google.com/a/chromium.org/g/headless-dev/c/nEoeUkoNI0o/m/9KZ4Os46AQAJ
|
|
553
|
-
const effectiveHeadless = extensionPaths.length > 0 ? false : headless;
|
|
554
|
-
if (extensionPaths.length > 0 && headless) {
|
|
555
|
-
console.warn('⚠️ WARNING: Extensions require headful mode. Forcing headless:false');
|
|
556
|
-
}
|
|
557
|
-
const browser = await puppeteer.launch({
|
|
560
|
+
browser = await puppeteer.launch({
|
|
558
561
|
...connectOptions,
|
|
559
562
|
channel: puppeterChannel,
|
|
560
563
|
executablePath: effectiveExecutablePath,
|
|
@@ -565,6 +568,50 @@ export async function launch(options) {
|
|
|
565
568
|
args,
|
|
566
569
|
ignoreDefaultArgs: ['--disable-extensions', '--enable-automation'],
|
|
567
570
|
});
|
|
571
|
+
}
|
|
572
|
+
catch (e) {
|
|
573
|
+
// Profile lock collision fallback (v0.15.1)
|
|
574
|
+
const errorMsg = String(e.message || '').toLowerCase();
|
|
575
|
+
const isProfileLocked = errorMsg.includes('in use') ||
|
|
576
|
+
errorMsg.includes('lock') ||
|
|
577
|
+
errorMsg.includes('another chrome') ||
|
|
578
|
+
errorMsg.includes('profile appears to be');
|
|
579
|
+
if (isProfileLocked) {
|
|
580
|
+
// Fallback to ephemeral session
|
|
581
|
+
const sessionId = `${process.pid}-${Date.now()}`;
|
|
582
|
+
const tempPath = path.join(os.homedir(), '.cache', 'chrome-devtools-mcp', 'sessions', sessionId, channel || 'stable');
|
|
583
|
+
await fs.promises.mkdir(tempPath, { recursive: true });
|
|
584
|
+
console.error(`⚠️ Profile locked: ${userDataDir}`);
|
|
585
|
+
console.error(`📁 Falling back to ephemeral session: ${tempPath}`);
|
|
586
|
+
console.error(`💡 To avoid this, set MCP_CLIENT_ID (e.g., "claude-code", "codex")`);
|
|
587
|
+
// Clean up on exit
|
|
588
|
+
process.on('exit', () => {
|
|
589
|
+
try {
|
|
590
|
+
fs.rmSync(tempPath, { recursive: true, force: true });
|
|
591
|
+
}
|
|
592
|
+
catch {
|
|
593
|
+
/* ignore */
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
finalUserDataDir = tempPath;
|
|
597
|
+
// Retry with ephemeral profile
|
|
598
|
+
browser = await puppeteer.launch({
|
|
599
|
+
...connectOptions,
|
|
600
|
+
channel: puppeterChannel,
|
|
601
|
+
executablePath: effectiveExecutablePath,
|
|
602
|
+
defaultViewport: null,
|
|
603
|
+
userDataDir: tempPath,
|
|
604
|
+
pipe: true,
|
|
605
|
+
headless: effectiveHeadless,
|
|
606
|
+
args,
|
|
607
|
+
ignoreDefaultArgs: ['--disable-extensions', '--enable-automation'],
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
throw e;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
try {
|
|
568
615
|
// Log actual spawn args for debugging
|
|
569
616
|
const spawnArgs = browser.process()?.spawnargs;
|
|
570
617
|
if (spawnArgs) {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// src/profile-resolver.ts
|
|
2
|
-
// Phase 1 (v0.15.0)
|
|
2
|
+
// Phase 1 (v0.15.0) + v0.15.1 (MCP_CLIENT_ID support)
|
|
3
3
|
// - Hybrid priority (CLI > MCP_USER_DATA_DIR > MCP_PROJECT_ID > AUTO > DEFAULT)
|
|
4
4
|
// - Auto-detection (git root -> nearest package.json -> cwd)
|
|
5
5
|
// - Realpath normalization
|
|
6
6
|
// - Tilde (~) expansion
|
|
7
7
|
// - Short SHA-256 hash (8 chars)
|
|
8
8
|
// - CI detection => ephemeral session profile (unless MCP_PERSIST_PROFILES)
|
|
9
|
+
// - Client ID isolation (MCP_CLIENT_ID environment variable)
|
|
9
10
|
// - Minimal console.error() logging of decision
|
|
10
11
|
import fs from 'node:fs';
|
|
11
12
|
import os from 'node:os';
|
|
@@ -16,6 +17,7 @@ const CACHE_ROOT = path.join(os.homedir(), '.cache', 'chrome-devtools-mcp');
|
|
|
16
17
|
// --- Public API ---
|
|
17
18
|
export function resolveUserDataDir(opts) {
|
|
18
19
|
const channel = opts.channel || 'stable';
|
|
20
|
+
const clientId = sanitize(opts.env.MCP_CLIENT_ID || 'default');
|
|
19
21
|
// 0) CI detection → ephemeral session directory (unless MCP_PERSIST_PROFILES)
|
|
20
22
|
// - This happens before other priorities to keep CI clean by default.
|
|
21
23
|
if (isCI(opts.env) && !opts.env.MCP_PERSIST_PROFILES) {
|
|
@@ -33,13 +35,14 @@ export function resolveUserDataDir(opts) {
|
|
|
33
35
|
const result = {
|
|
34
36
|
path: tempPath,
|
|
35
37
|
reason: 'AUTO', // keep enum as specified (no EPHEMERAL type in Phase 1)
|
|
36
|
-
projectKey: `session-${sessionId}`,
|
|
38
|
+
projectKey: `session-${sessionId}_${clientId}`,
|
|
37
39
|
projectName: 'ci-session',
|
|
38
40
|
hash: sessionId,
|
|
41
|
+
clientId,
|
|
39
42
|
channel,
|
|
40
43
|
};
|
|
41
44
|
// concise decision log (resolver-side)
|
|
42
|
-
console.error(`[profiles] resolved(AUTO, ci-ephemeral): ${result.path} (session=${sessionId})`);
|
|
45
|
+
console.error(`[profiles] resolved(AUTO, ci-ephemeral): ${result.path} (session=${sessionId}, client=${clientId})`);
|
|
43
46
|
return result;
|
|
44
47
|
}
|
|
45
48
|
// 1) CLI explicit userDataDir
|
|
@@ -48,12 +51,13 @@ export function resolveUserDataDir(opts) {
|
|
|
48
51
|
const result = {
|
|
49
52
|
path: p,
|
|
50
53
|
reason: 'CLI',
|
|
51
|
-
projectKey: stripHomeForKey(p)
|
|
54
|
+
projectKey: `${stripHomeForKey(p)}_${clientId}`,
|
|
52
55
|
projectName: 'cli',
|
|
53
56
|
hash: shortHash(p),
|
|
57
|
+
clientId,
|
|
54
58
|
channel,
|
|
55
59
|
};
|
|
56
|
-
console.error(`[profiles] resolved(CLI): ${result.path}`);
|
|
60
|
+
console.error(`[profiles] resolved(CLI): ${result.path} (client=${clientId})`);
|
|
57
61
|
return result;
|
|
58
62
|
}
|
|
59
63
|
// 2) ENV: MCP_USER_DATA_DIR (full path)
|
|
@@ -63,27 +67,30 @@ export function resolveUserDataDir(opts) {
|
|
|
63
67
|
const result = {
|
|
64
68
|
path: p,
|
|
65
69
|
reason: 'MCP_USER_DATA_DIR',
|
|
66
|
-
projectKey: stripHomeForKey(p)
|
|
70
|
+
projectKey: `${stripHomeForKey(p)}_${clientId}`,
|
|
67
71
|
projectName: 'env',
|
|
68
72
|
hash: shortHash(p),
|
|
73
|
+
clientId,
|
|
69
74
|
channel,
|
|
70
75
|
};
|
|
71
|
-
console.error(`[profiles] resolved(MCP_USER_DATA_DIR): ${result.path}`);
|
|
76
|
+
console.error(`[profiles] resolved(MCP_USER_DATA_DIR): ${result.path} (client=${clientId})`);
|
|
72
77
|
return result;
|
|
73
78
|
}
|
|
74
79
|
// 3) ENV: MCP_PROJECT_ID (project-scoped persistent profile)
|
|
75
80
|
const projectId = sanitize(opts.env.MCP_PROJECT_ID || '');
|
|
76
81
|
if (projectId) {
|
|
77
|
-
const
|
|
82
|
+
const key = `${projectId}_${clientId}`;
|
|
83
|
+
const p = projectProfilePath(key, channel);
|
|
78
84
|
const result = {
|
|
79
85
|
path: p,
|
|
80
86
|
reason: 'MCP_PROJECT_ID',
|
|
81
|
-
projectKey:
|
|
87
|
+
projectKey: key,
|
|
82
88
|
projectName: projectId,
|
|
83
89
|
hash: shortHash(projectId),
|
|
90
|
+
clientId,
|
|
84
91
|
channel,
|
|
85
92
|
};
|
|
86
|
-
console.error(`[profiles] resolved(MCP_PROJECT_ID): ${result.path} (projectId=${projectId})`);
|
|
93
|
+
console.error(`[profiles] resolved(MCP_PROJECT_ID): ${result.path} (projectId=${projectId}, client=${clientId})`);
|
|
87
94
|
return result;
|
|
88
95
|
}
|
|
89
96
|
// 4) AUTO: detect by root -> name -> hash
|
|
@@ -92,7 +99,7 @@ export function resolveUserDataDir(opts) {
|
|
|
92
99
|
const name = detectProjectName(root);
|
|
93
100
|
const realRoot = realpathSafe(root);
|
|
94
101
|
const hash = shortHash(realRoot);
|
|
95
|
-
const key = `${sanitize(name)}_${hash}`;
|
|
102
|
+
const key = `${sanitize(name)}_${hash}_${clientId}`;
|
|
96
103
|
const p = projectProfilePath(key, channel);
|
|
97
104
|
const result = {
|
|
98
105
|
path: p,
|
|
@@ -100,24 +107,26 @@ export function resolveUserDataDir(opts) {
|
|
|
100
107
|
projectKey: key,
|
|
101
108
|
projectName: sanitize(name),
|
|
102
109
|
hash,
|
|
110
|
+
clientId,
|
|
103
111
|
channel,
|
|
104
112
|
};
|
|
105
|
-
console.error(`[profiles] resolved(AUTO): ${result.path} (root=${root}, name=${name}, hash=${hash})`);
|
|
113
|
+
console.error(`[profiles] resolved(AUTO): ${result.path} (root=${root}, name=${name}, hash=${hash}, client=${clientId})`);
|
|
106
114
|
return result;
|
|
107
115
|
}
|
|
108
116
|
catch (e) {
|
|
109
117
|
// 5) DEFAULT fallback
|
|
110
|
-
const key =
|
|
118
|
+
const key = `project-default_${clientId}`;
|
|
111
119
|
const p = projectProfilePath(key, channel);
|
|
112
120
|
const result = {
|
|
113
121
|
path: p,
|
|
114
122
|
reason: 'DEFAULT',
|
|
115
123
|
projectKey: key,
|
|
116
|
-
projectName:
|
|
124
|
+
projectName: 'project-default',
|
|
117
125
|
hash: '00000000',
|
|
126
|
+
clientId,
|
|
118
127
|
channel,
|
|
119
128
|
};
|
|
120
|
-
console.error(`[profiles] resolved(DEFAULT): ${result.path} (reason=${e?.message || 'fallback'})`);
|
|
129
|
+
console.error(`[profiles] resolved(DEFAULT): ${result.path} (reason=${e?.message || 'fallback'}, client=${clientId})`);
|
|
121
130
|
return result;
|
|
122
131
|
}
|
|
123
132
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-devtools-mcp-for-extension",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.1",
|
|
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",
|