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.
@@ -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
- // 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
- 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 p = projectProfilePath(projectId, channel);
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: projectId,
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 = 'project-default';
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: key,
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('https://chatgpt.com/', {
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': 'https://chatgpt.com/?model=gpt-5-thinking'
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('https://chatgpt.com/?model=gpt-5-thinking', { waitUntil: 'networkidle2' });
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('https://chatgpt.com/?model=gpt-5-thinking', { waitUntil: 'networkidle2' });
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('https://chatgpt.com/')
204
- .describe('ChatGPT URL to diagnose (default: https://chatgpt.com/)'),
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.0",
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",