chrome-devtools-mcp-for-extension 0.15.2 → 0.16.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.
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
/**
|
|
9
|
+
* Detect MCP client type from parent process
|
|
10
|
+
* Returns client ID like "claude-code", "codex", "cursor", or "default"
|
|
11
|
+
*/
|
|
12
|
+
export function detectClientType() {
|
|
13
|
+
const ppid = process.ppid;
|
|
14
|
+
if (!ppid) {
|
|
15
|
+
return 'default';
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const platform = os.platform();
|
|
19
|
+
if (platform === 'darwin' || platform === 'linux') {
|
|
20
|
+
// macOS and Linux: use ps command
|
|
21
|
+
const result = spawnSync('ps', ['-p', String(ppid), '-o', 'comm='], {
|
|
22
|
+
encoding: 'utf8',
|
|
23
|
+
timeout: 500,
|
|
24
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
25
|
+
});
|
|
26
|
+
if (result.status === 0 && result.stdout) {
|
|
27
|
+
const processName = result.stdout.trim().toLowerCase();
|
|
28
|
+
return mapProcessNameToClient(processName);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else if (platform === 'win32') {
|
|
32
|
+
// Windows: use wmic command
|
|
33
|
+
const result = spawnSync('wmic', ['process', 'where', `ProcessId=${ppid}`, 'get', 'Name', '/value'], {
|
|
34
|
+
encoding: 'utf8',
|
|
35
|
+
timeout: 500,
|
|
36
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
37
|
+
});
|
|
38
|
+
if (result.status === 0 && result.stdout) {
|
|
39
|
+
const match = result.stdout.match(/Name=(.+)/i);
|
|
40
|
+
if (match) {
|
|
41
|
+
const processName = match[1].trim().toLowerCase();
|
|
42
|
+
return mapProcessNameToClient(processName);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error(`[client-detector] Failed to detect client: ${error}`);
|
|
49
|
+
}
|
|
50
|
+
return 'default';
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Map process name to MCP client ID
|
|
54
|
+
*/
|
|
55
|
+
function mapProcessNameToClient(processName) {
|
|
56
|
+
// Remove .exe extension (Windows)
|
|
57
|
+
const name = processName.replace(/\.exe$/i, '');
|
|
58
|
+
// Known MCP clients
|
|
59
|
+
if (name.includes('claude')) {
|
|
60
|
+
return 'claude-code';
|
|
61
|
+
}
|
|
62
|
+
if (name.includes('codex')) {
|
|
63
|
+
return 'codex';
|
|
64
|
+
}
|
|
65
|
+
if (name.includes('cursor')) {
|
|
66
|
+
return 'cursor';
|
|
67
|
+
}
|
|
68
|
+
if (name.includes('copilot')) {
|
|
69
|
+
return 'copilot';
|
|
70
|
+
}
|
|
71
|
+
if (name.includes('cline')) {
|
|
72
|
+
return 'cline';
|
|
73
|
+
}
|
|
74
|
+
if (name.includes('continue')) {
|
|
75
|
+
return 'continue';
|
|
76
|
+
}
|
|
77
|
+
if (name.includes('aider')) {
|
|
78
|
+
return 'aider';
|
|
79
|
+
}
|
|
80
|
+
if (name.includes('windsurf')) {
|
|
81
|
+
return 'windsurf';
|
|
82
|
+
}
|
|
83
|
+
// VSCode (could be any MCP client extension)
|
|
84
|
+
if (name.includes('code') || name.includes('vscode')) {
|
|
85
|
+
return 'vscode';
|
|
86
|
+
}
|
|
87
|
+
// Generic node process (fallback to default)
|
|
88
|
+
if (name.includes('node')) {
|
|
89
|
+
return 'default';
|
|
90
|
+
}
|
|
91
|
+
// Unknown process - use sanitized process name
|
|
92
|
+
return name.slice(0, 20); // Max 20 chars
|
|
93
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Check if the page requires login
|
|
8
|
+
* More robust than just checking URL
|
|
9
|
+
*/
|
|
10
|
+
export async function isLoginRequired(page) {
|
|
11
|
+
const currentUrl = page.url();
|
|
12
|
+
// Method 1: Check URL
|
|
13
|
+
if (currentUrl.includes('auth') ||
|
|
14
|
+
currentUrl.includes('login') ||
|
|
15
|
+
currentUrl.includes('signin')) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
// Method 2: Check for login UI elements
|
|
19
|
+
try {
|
|
20
|
+
// ChatGPT login page has specific elements
|
|
21
|
+
const loginButton = await page.$('button[data-testid="login-button"]');
|
|
22
|
+
const signUpButton = await page.$('a[href*="signup"]');
|
|
23
|
+
const authContainer = await page.$('[class*="auth"]');
|
|
24
|
+
if (loginButton || signUpButton || authContainer) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Element check failed, continue to next method
|
|
30
|
+
}
|
|
31
|
+
// Method 3: Check for main content absence
|
|
32
|
+
try {
|
|
33
|
+
// If we can't find the main composer, likely not logged in
|
|
34
|
+
const composer = await page.$('textarea, .ProseMirror[contenteditable="true"]');
|
|
35
|
+
if (!composer) {
|
|
36
|
+
// No composer found - might be login page
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Element check failed
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Wait for user to complete login
|
|
47
|
+
* Shows visual guidance and polls for login completion
|
|
48
|
+
*/
|
|
49
|
+
export async function waitForLogin(page, options = {}) {
|
|
50
|
+
const maxWaitTime = options.maxWaitTime || 300000; // 5 minutes default
|
|
51
|
+
const pollInterval = options.pollInterval || 2000; // 2 seconds default
|
|
52
|
+
const onStatusUpdate = options.onStatusUpdate || console.error;
|
|
53
|
+
const startTime = Date.now();
|
|
54
|
+
onStatusUpdate('\n🔐 ログインが必要です');
|
|
55
|
+
onStatusUpdate('📱 ブラウザウィンドウでChatGPTにログインしてください');
|
|
56
|
+
onStatusUpdate(`⏰ 最大待機時間: ${Math.floor(maxWaitTime / 1000)}秒`);
|
|
57
|
+
onStatusUpdate('\n💡 ログイン方法:');
|
|
58
|
+
onStatusUpdate(' 1. ブラウザウィンドウでChatGPTのログインボタンをクリック');
|
|
59
|
+
onStatusUpdate(' 2. メールアドレスまたはGoogleアカウントでログイン');
|
|
60
|
+
onStatusUpdate(' 3. ログイン完了後、自動的に処理が続行されます\n');
|
|
61
|
+
// Poll for login completion
|
|
62
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
63
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
64
|
+
onStatusUpdate(`⏳ ログイン待機中... (${elapsed}秒経過)`);
|
|
65
|
+
// Check if login is completed
|
|
66
|
+
const stillNeedsLogin = await isLoginRequired(page);
|
|
67
|
+
if (!stillNeedsLogin) {
|
|
68
|
+
onStatusUpdate('\n✅ ログイン完了!処理を続行します...\n');
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
// Wait before next poll
|
|
72
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
73
|
+
}
|
|
74
|
+
// Timeout
|
|
75
|
+
onStatusUpdate('\n⏱️ ログイン待機がタイムアウトしました');
|
|
76
|
+
onStatusUpdate('💡 再度実行してください\n');
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Ensure user is logged in to ChatGPT
|
|
81
|
+
* If not, guide them through login process
|
|
82
|
+
*/
|
|
83
|
+
export async function ensureLoggedIn(page, options = {}) {
|
|
84
|
+
const needsLogin = await isLoginRequired(page);
|
|
85
|
+
if (!needsLogin) {
|
|
86
|
+
return true; // Already logged in
|
|
87
|
+
}
|
|
88
|
+
// Wait for user to login
|
|
89
|
+
return await waitForLogin(page, options);
|
|
90
|
+
}
|
|
@@ -13,11 +13,22 @@ import os from 'node:os';
|
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import crypto from 'node:crypto';
|
|
15
15
|
import { detectProjectName, detectProjectRoot } from './project-detector.js';
|
|
16
|
+
import { detectClientType } from './client-detector.js';
|
|
16
17
|
const CACHE_ROOT = path.join(os.homedir(), '.cache', 'chrome-devtools-mcp');
|
|
17
18
|
// --- Public API ---
|
|
18
19
|
export function resolveUserDataDir(opts) {
|
|
19
20
|
const channel = opts.channel || 'stable';
|
|
20
|
-
|
|
21
|
+
// Auto-detect client type from parent process if MCP_CLIENT_ID not set
|
|
22
|
+
let clientId;
|
|
23
|
+
if (opts.env.MCP_CLIENT_ID) {
|
|
24
|
+
clientId = sanitize(opts.env.MCP_CLIENT_ID);
|
|
25
|
+
console.error(`[profiles] Using explicit MCP_CLIENT_ID: ${clientId}`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const detected = detectClientType();
|
|
29
|
+
clientId = sanitize(detected);
|
|
30
|
+
console.error(`[profiles] Auto-detected client from parent process: ${clientId}`);
|
|
31
|
+
}
|
|
21
32
|
// 0) CI detection → ephemeral session directory (unless MCP_PERSIST_PROFILES)
|
|
22
33
|
// - This happens before other priorities to keep CI clean by default.
|
|
23
34
|
if (isCI(opts.env) && !opts.env.MCP_PERSIST_PROFILES) {
|
|
@@ -10,6 +10,7 @@ import { ToolCategories } from './categories.js';
|
|
|
10
10
|
import { defineTool } from './ToolDefinition.js';
|
|
11
11
|
import { loadSelectors, getSelector } from '../selectors/loader.js';
|
|
12
12
|
import { CHATGPT_CONFIG } from '../config.js';
|
|
13
|
+
import { ensureLoggedIn } from '../login-helper.js';
|
|
13
14
|
/**
|
|
14
15
|
* Path to store chat session data
|
|
15
16
|
*/
|
|
@@ -188,11 +189,13 @@ export const askChatGPTWeb = defineTool({
|
|
|
188
189
|
// Step 1: Navigate to ChatGPT
|
|
189
190
|
response.appendResponseLine('ChatGPTに接続中...');
|
|
190
191
|
await page.goto(CHATGPT_CONFIG.DEFAULT_URL, { waitUntil: 'networkidle2' });
|
|
191
|
-
//
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
response.appendResponseLine(
|
|
195
|
-
|
|
192
|
+
// Step 2: Ensure logged in (with user guidance if needed)
|
|
193
|
+
const isLoggedIn = await ensureLoggedIn(page, {
|
|
194
|
+
maxWaitTime: 300000, // 5 minutes
|
|
195
|
+
onStatusUpdate: (msg) => response.appendResponseLine(msg),
|
|
196
|
+
});
|
|
197
|
+
if (!isLoggedIn) {
|
|
198
|
+
response.appendResponseLine('❌ ログインがタイムアウトしました。再度実行してください。');
|
|
196
199
|
return;
|
|
197
200
|
}
|
|
198
201
|
response.appendResponseLine('✅ ログイン確認完了');
|
|
@@ -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 { CHATGPT_CONFIG } from '../config.js';
|
|
12
|
+
import { ensureLoggedIn } from '../login-helper.js';
|
|
12
13
|
/**
|
|
13
14
|
* Path to store chat session data
|
|
14
15
|
*/
|
|
@@ -672,11 +673,13 @@ export const deepResearchChatGPT = defineTool({
|
|
|
672
673
|
if (needsNewChat) {
|
|
673
674
|
await page.goto(CHATGPT_CONFIG.DEFAULT_URL, { waitUntil: 'networkidle2' });
|
|
674
675
|
}
|
|
675
|
-
//
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
response.appendResponseLine(
|
|
679
|
-
|
|
676
|
+
// Ensure logged in (with user guidance if needed)
|
|
677
|
+
const isLoggedIn = await ensureLoggedIn(page, {
|
|
678
|
+
maxWaitTime: 300000, // 5 minutes
|
|
679
|
+
onStatusUpdate: (msg) => response.appendResponseLine(msg),
|
|
680
|
+
});
|
|
681
|
+
if (!isLoggedIn) {
|
|
682
|
+
response.appendResponseLine('❌ ログインがタイムアウトしました。再度実行してください。');
|
|
680
683
|
return;
|
|
681
684
|
}
|
|
682
685
|
response.appendResponseLine('✅ ログイン確認完了');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-devtools-mcp-for-extension",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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",
|