chrome-devtools-mcp-for-extension 0.15.0 → 0.15.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 +55 -8
- package/build/src/config.js +25 -0
- package/build/src/profile-resolver.js +24 -15
- package/build/src/startup-check.js +2 -1
- package/build/src/tools/bookmarks.js +2 -1
- package/build/src/tools/chatgpt-web.js +2 -1
- package/build/src/tools/deep_research_chatgpt.js +2 -1
- package/build/src/tools/diagnose-ui.js +3 -2
- 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) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Global configuration for chrome-devtools-mcp-for-extension
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* ChatGPT configuration
|
|
11
|
+
*/
|
|
12
|
+
export const CHATGPT_CONFIG = {
|
|
13
|
+
/**
|
|
14
|
+
* Default ChatGPT URL with gpt-5-thinking model
|
|
15
|
+
*/
|
|
16
|
+
DEFAULT_URL: 'https://chatgpt.com/?model=gpt-5-thinking',
|
|
17
|
+
/**
|
|
18
|
+
* Base URL for ChatGPT (without query params)
|
|
19
|
+
*/
|
|
20
|
+
BASE_URL: 'https://chatgpt.com/',
|
|
21
|
+
/**
|
|
22
|
+
* Default model parameter
|
|
23
|
+
*/
|
|
24
|
+
DEFAULT_MODEL: 'gpt-5-thinking',
|
|
25
|
+
};
|
|
@@ -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
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
+
import { CHATGPT_CONFIG } from './config.js';
|
|
6
7
|
/**
|
|
7
8
|
* UI elements to check during health verification
|
|
8
9
|
*/
|
|
@@ -70,7 +71,7 @@ export async function verifyUIHealth(browser) {
|
|
|
70
71
|
page = pages[0] || (await browser.newPage());
|
|
71
72
|
// Navigate to ChatGPT with short timeout
|
|
72
73
|
console.error(' Navigating to ChatGPT...');
|
|
73
|
-
await page.goto(
|
|
74
|
+
await page.goto(CHATGPT_CONFIG.DEFAULT_URL, {
|
|
74
75
|
waitUntil: 'networkidle2',
|
|
75
76
|
timeout: 30000,
|
|
76
77
|
});
|
|
@@ -9,6 +9,7 @@ import * as path from 'path';
|
|
|
9
9
|
import * as os from 'os';
|
|
10
10
|
import { ToolCategories } from './categories.js';
|
|
11
11
|
import { defineTool } from './ToolDefinition.js';
|
|
12
|
+
import { CHATGPT_CONFIG } from '../config.js';
|
|
12
13
|
// Default hardcoded bookmarks - fallback when Chrome bookmarks cannot be loaded
|
|
13
14
|
function getDefaultBookmarks() {
|
|
14
15
|
return {
|
|
@@ -24,7 +25,7 @@ function getDefaultBookmarks() {
|
|
|
24
25
|
'localhost': 'http://localhost:3000',
|
|
25
26
|
'localhost8080': 'http://localhost:8080',
|
|
26
27
|
'suno': 'https://suno.com/create',
|
|
27
|
-
'chatgpt':
|
|
28
|
+
'chatgpt': CHATGPT_CONFIG.DEFAULT_URL
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
31
|
/**
|
|
@@ -9,6 +9,7 @@ import z from 'zod';
|
|
|
9
9
|
import { ToolCategories } from './categories.js';
|
|
10
10
|
import { defineTool } from './ToolDefinition.js';
|
|
11
11
|
import { loadSelectors, getSelector } from '../selectors/loader.js';
|
|
12
|
+
import { CHATGPT_CONFIG } from '../config.js';
|
|
12
13
|
/**
|
|
13
14
|
* Path to store chat session data
|
|
14
15
|
*/
|
|
@@ -186,7 +187,7 @@ export const askChatGPTWeb = defineTool({
|
|
|
186
187
|
try {
|
|
187
188
|
// Step 1: Navigate to ChatGPT
|
|
188
189
|
response.appendResponseLine('ChatGPTに接続中...');
|
|
189
|
-
await page.goto(
|
|
190
|
+
await page.goto(CHATGPT_CONFIG.DEFAULT_URL, { waitUntil: 'networkidle2' });
|
|
190
191
|
// Check if logged in
|
|
191
192
|
const currentUrl = page.url();
|
|
192
193
|
if (currentUrl.includes('auth') || currentUrl.includes('login')) {
|
|
@@ -8,6 +8,7 @@ import path from 'node:path';
|
|
|
8
8
|
import z from 'zod';
|
|
9
9
|
import { ToolCategories } from './categories.js';
|
|
10
10
|
import { defineTool } from './ToolDefinition.js';
|
|
11
|
+
import { CHATGPT_CONFIG } from '../config.js';
|
|
11
12
|
/**
|
|
12
13
|
* Path to store chat session data
|
|
13
14
|
*/
|
|
@@ -669,7 +670,7 @@ export const deepResearchChatGPT = defineTool({
|
|
|
669
670
|
}
|
|
670
671
|
}
|
|
671
672
|
if (needsNewChat) {
|
|
672
|
-
await page.goto(
|
|
673
|
+
await page.goto(CHATGPT_CONFIG.DEFAULT_URL, { waitUntil: 'networkidle2' });
|
|
673
674
|
}
|
|
674
675
|
// Check if logged in
|
|
675
676
|
const currentUrl = page.url();
|
|
@@ -8,6 +8,7 @@ import path from 'node:path';
|
|
|
8
8
|
import z from 'zod';
|
|
9
9
|
import { ToolCategories } from './categories.js';
|
|
10
10
|
import { defineTool } from './ToolDefinition.js';
|
|
11
|
+
import { CHATGPT_CONFIG } from '../config.js';
|
|
11
12
|
/**
|
|
12
13
|
* Known important elements in ChatGPT UI
|
|
13
14
|
*/
|
|
@@ -200,8 +201,8 @@ export const diagnoseChatgptUi = defineTool({
|
|
|
200
201
|
},
|
|
201
202
|
schema: {
|
|
202
203
|
url: z.string()
|
|
203
|
-
.default(
|
|
204
|
-
.describe(
|
|
204
|
+
.default(CHATGPT_CONFIG.DEFAULT_URL)
|
|
205
|
+
.describe(`ChatGPT URL to diagnose (default: ${CHATGPT_CONFIG.DEFAULT_URL})`),
|
|
205
206
|
waitForLoad: z.number()
|
|
206
207
|
.default(5000)
|
|
207
208
|
.describe('Time to wait for page to load in milliseconds (default: 5000)'),
|
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.2",
|
|
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",
|