chrome-devtools-mcp-for-extension 0.25.4 → 0.25.6
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/main.js +0 -5
- package/build/src/profile-resolver.js +36 -16
- package/build/src/roots-manager.js +97 -24
- package/package.json +1 -1
package/build/src/main.js
CHANGED
|
@@ -132,11 +132,6 @@ async function getContext() {
|
|
|
132
132
|
process.env.MCP_PROJECT_ROOT ||
|
|
133
133
|
rootFromRoots ||
|
|
134
134
|
process.cwd();
|
|
135
|
-
// Debug: Log the source of project root
|
|
136
|
-
logger(`[project-root] DEBUG: args.projectRoot=${args.projectRoot}`);
|
|
137
|
-
logger(`[project-root] DEBUG: env.MCP_PROJECT_ROOT=${process.env.MCP_PROJECT_ROOT}`);
|
|
138
|
-
logger(`[project-root] DEBUG: rootFromRoots=${rootFromRoots}`);
|
|
139
|
-
logger(`[project-root] DEBUG: process.cwd()=${process.cwd()}`);
|
|
140
135
|
setProjectRoot(projectRootToSet);
|
|
141
136
|
logger(`[project-root] Initialized: ${projectRootToSet}`);
|
|
142
137
|
const browserOptions = {
|
|
@@ -23,8 +23,42 @@ export function resolveUserDataDir(opts) {
|
|
|
23
23
|
const channel = opts.channel || 'stable';
|
|
24
24
|
// v0.18.0: PRIORITY 0 - Use Roots info if available
|
|
25
25
|
if (opts.rootsInfo) {
|
|
26
|
-
const
|
|
27
|
-
const normalized = pathNormalize(
|
|
26
|
+
const stableProfilePath = path.join(CACHE_ROOT, 'profiles', opts.rootsInfo.profileKey, opts.rootsInfo.clientName, channel);
|
|
27
|
+
const normalized = pathNormalize(stableProfilePath);
|
|
28
|
+
// v0.25.5: Migration from directory-based profile to stable identity profile
|
|
29
|
+
// Check if directory-based profile exists (for projects that added git later)
|
|
30
|
+
if (!fs.existsSync(stableProfilePath) && opts.rootsInfo.rootsUris.length > 0) {
|
|
31
|
+
try {
|
|
32
|
+
const firstUri = opts.rootsInfo.rootsUris[0];
|
|
33
|
+
const url = new URL(firstUri);
|
|
34
|
+
if (url.protocol === 'file:') {
|
|
35
|
+
const rootPath = url.pathname;
|
|
36
|
+
const realRoot = realpathSafe(rootPath);
|
|
37
|
+
const dirHash = shortHash(realRoot);
|
|
38
|
+
const dirBasedKey = `${opts.rootsInfo.projectName}_${dirHash}`;
|
|
39
|
+
const dirBasedPath = path.join(CACHE_ROOT, 'profiles', dirBasedKey, opts.rootsInfo.clientName, channel);
|
|
40
|
+
// If directory-based profile exists, create symlink for migration
|
|
41
|
+
if (fs.existsSync(dirBasedPath)) {
|
|
42
|
+
console.error(`[profiles] Migration: Found directory-based profile: ${dirBasedPath}`);
|
|
43
|
+
console.error(`[profiles] Migration: Creating symlink to stable profile: ${stableProfilePath}`);
|
|
44
|
+
try {
|
|
45
|
+
// Create parent directories
|
|
46
|
+
fs.mkdirSync(path.dirname(stableProfilePath), { recursive: true });
|
|
47
|
+
// Create symlink: stable -> directory-based
|
|
48
|
+
fs.symlinkSync(dirBasedPath, stableProfilePath, 'dir');
|
|
49
|
+
console.error(`[profiles] Migration: ✅ Symlink created successfully`);
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
console.error(`[profiles] Migration: ⚠️ Failed to create symlink: ${e}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
// Ignore migration errors, continue with normal flow
|
|
59
|
+
console.error(`[profiles] Migration check failed: ${e}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
28
62
|
const result = {
|
|
29
63
|
path: normalized,
|
|
30
64
|
reason: opts.rootsInfo.source,
|
|
@@ -223,20 +257,6 @@ export function resolveUserDataDir(opts) {
|
|
|
223
257
|
if (initializedRoot) {
|
|
224
258
|
console.error(`[profiles] Using initialized project root: "${initializedRoot}"`);
|
|
225
259
|
try {
|
|
226
|
-
// Debug: Check package.json existence
|
|
227
|
-
const pjPath = path.join(initializedRoot, 'package.json');
|
|
228
|
-
const pjExists = fs.existsSync(pjPath);
|
|
229
|
-
console.error(`[profiles] DEBUG: package.json at ${pjPath} exists=${pjExists}`);
|
|
230
|
-
if (pjExists) {
|
|
231
|
-
try {
|
|
232
|
-
const pkg = JSON.parse(fs.readFileSync(pjPath, 'utf8'));
|
|
233
|
-
console.error(`[profiles] DEBUG: package.json name="${pkg?.name}"`);
|
|
234
|
-
}
|
|
235
|
-
catch (e) {
|
|
236
|
-
console.error(`[profiles] DEBUG: package.json parse error: ${e}`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
console.error(`[profiles] DEBUG: basename="${path.basename(initializedRoot)}"`);
|
|
240
260
|
const name = detectProjectName(initializedRoot);
|
|
241
261
|
console.error(`[profiles] Initialized root name="${name}"`);
|
|
242
262
|
// v0.25.0: Check profiles in priority order
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* Implements the MCP Roots protocol for project-scoped Chrome profiles.
|
|
11
11
|
*/
|
|
12
12
|
import { createHash } from 'node:crypto';
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { detectProjectName } from './project-detector.js';
|
|
16
|
+
import { resolveStableIdentity } from './stable-identity.js';
|
|
13
17
|
/**
|
|
14
18
|
* Fetch roots from MCP client using roots/list protocol
|
|
15
19
|
*/
|
|
@@ -33,18 +37,28 @@ export async function fetchRootsFromClient(server) {
|
|
|
33
37
|
/**
|
|
34
38
|
* Generate stable profile key from roots and client info
|
|
35
39
|
* Format: projectName_hash (e.g., "my-app_2ca5dbf5")
|
|
40
|
+
*
|
|
41
|
+
* @param rootsUris - Array of root URIs (used for fallback hash if stableIdentityHash not provided)
|
|
42
|
+
* @param clientName - Client name (e.g., "claude-code")
|
|
43
|
+
* @param clientVersion - Client version (not used in hash for stability)
|
|
44
|
+
* @param projectName - Human-readable project name
|
|
45
|
+
* @param stableIdentityHash - Optional stable identity hash (from git remote, etc.)
|
|
36
46
|
*/
|
|
37
|
-
export function generateProfileKey(rootsUris, clientName, clientVersion, projectName) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
export function generateProfileKey(rootsUris, clientName, clientVersion, projectName, stableIdentityHash) {
|
|
48
|
+
let hash;
|
|
49
|
+
if (stableIdentityHash) {
|
|
50
|
+
// Use stable identity hash (preferred - survives directory moves)
|
|
51
|
+
hash = stableIdentityHash;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Fallback: URI-based hash
|
|
55
|
+
const sortedUris = [...rootsUris].sort();
|
|
56
|
+
const keyMaterial = JSON.stringify({
|
|
57
|
+
roots: sortedUris,
|
|
58
|
+
client: clientName,
|
|
59
|
+
});
|
|
60
|
+
hash = createHash('sha256').update(keyMaterial).digest('hex').slice(0, 8);
|
|
61
|
+
}
|
|
48
62
|
// Include project name for clarity (if available)
|
|
49
63
|
if (projectName) {
|
|
50
64
|
const sanitized = projectName.replace(/[^a-z0-9_-]/gi, '-').toLowerCase();
|
|
@@ -54,23 +68,35 @@ export function generateProfileKey(rootsUris, clientName, clientVersion, project
|
|
|
54
68
|
}
|
|
55
69
|
/**
|
|
56
70
|
* Extract project name from roots URIs
|
|
71
|
+
* Uses detectProjectName() to read package.json name (preferred)
|
|
72
|
+
* Falls back to directory name if package.json not found
|
|
57
73
|
*/
|
|
58
74
|
export function extractProjectName(roots) {
|
|
59
75
|
if (roots.length === 0) {
|
|
60
76
|
return 'unknown';
|
|
61
77
|
}
|
|
62
|
-
// Prefer explicit name if provided
|
|
78
|
+
// Prefer explicit name if provided by MCP client
|
|
63
79
|
const firstRoot = roots[0];
|
|
64
80
|
if (firstRoot.name) {
|
|
65
81
|
return sanitizeProjectName(firstRoot.name);
|
|
66
82
|
}
|
|
67
|
-
// Extract from file:// URI
|
|
83
|
+
// Extract from file:// URI using detectProjectName (reads package.json)
|
|
68
84
|
try {
|
|
69
85
|
const url = new URL(firstRoot.uri);
|
|
70
86
|
if (url.protocol === 'file:') {
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
87
|
+
// Convert URI to file path
|
|
88
|
+
const rootPath = fileURLToPath(url);
|
|
89
|
+
// Resolve realpath for symlink handling
|
|
90
|
+
let realPath;
|
|
91
|
+
try {
|
|
92
|
+
realPath = fs.realpathSync(rootPath);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
realPath = rootPath; // Use original if realpath fails
|
|
96
|
+
}
|
|
97
|
+
// Use detectProjectName to read package.json name
|
|
98
|
+
const name = detectProjectName(realPath);
|
|
99
|
+
return sanitizeProjectName(name);
|
|
74
100
|
}
|
|
75
101
|
}
|
|
76
102
|
catch {
|
|
@@ -84,6 +110,44 @@ export function extractProjectName(roots) {
|
|
|
84
110
|
function sanitizeProjectName(name) {
|
|
85
111
|
return name.toLowerCase().replace(/[^a-z0-9-_]/g, '-');
|
|
86
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Get stable identity hash from a root path
|
|
115
|
+
* Returns undefined if stable identity cannot be resolved
|
|
116
|
+
*/
|
|
117
|
+
function getStableIdentityHash(rootPath) {
|
|
118
|
+
try {
|
|
119
|
+
const identity = resolveStableIdentity(rootPath);
|
|
120
|
+
// Only use stable identity for high-confidence sources (git-based)
|
|
121
|
+
if (identity.confidence === 'high' || identity.source === 'MCP_PROFILE_ID') {
|
|
122
|
+
return identity.id;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Fall through to URI-based hash
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get root path from URI
|
|
132
|
+
*/
|
|
133
|
+
function getRootPathFromUri(uri) {
|
|
134
|
+
try {
|
|
135
|
+
const url = new URL(uri);
|
|
136
|
+
if (url.protocol === 'file:') {
|
|
137
|
+
const rootPath = fileURLToPath(url);
|
|
138
|
+
try {
|
|
139
|
+
return fs.realpathSync(rootPath);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return rootPath;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Ignore
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
87
151
|
/**
|
|
88
152
|
* Resolve roots information from MCP client or fallbacks
|
|
89
153
|
*/
|
|
@@ -96,8 +160,11 @@ export async function resolveRoots(server, fallbackOptions) {
|
|
|
96
160
|
if (rootsResult && rootsResult.roots.length > 0) {
|
|
97
161
|
const rootsUris = rootsResult.roots.map(r => r.uri);
|
|
98
162
|
const projectName = extractProjectName(rootsResult.roots);
|
|
99
|
-
|
|
100
|
-
|
|
163
|
+
// Get stable identity hash from first root
|
|
164
|
+
const rootPath = getRootPathFromUri(rootsUris[0]);
|
|
165
|
+
const stableHash = rootPath ? getStableIdentityHash(rootPath) : undefined;
|
|
166
|
+
const profileKey = generateProfileKey(rootsUris, clientName, clientVersion, projectName, stableHash);
|
|
167
|
+
console.error(`[roots] Resolved via roots/list: key=${profileKey}, project=${projectName}, client=${clientName}, stableId=${stableHash || 'none'}`);
|
|
101
168
|
return {
|
|
102
169
|
profileKey,
|
|
103
170
|
projectName,
|
|
@@ -111,8 +178,10 @@ export async function resolveRoots(server, fallbackOptions) {
|
|
|
111
178
|
if (fallbackOptions.cliProjectRoot) {
|
|
112
179
|
const uri = pathToFileUri(fallbackOptions.cliProjectRoot);
|
|
113
180
|
const projectName = extractProjectName([{ uri }]);
|
|
114
|
-
|
|
115
|
-
|
|
181
|
+
// Get stable identity hash
|
|
182
|
+
const stableHash = getStableIdentityHash(fallbackOptions.cliProjectRoot);
|
|
183
|
+
const profileKey = generateProfileKey([uri], clientName, clientVersion, projectName, stableHash);
|
|
184
|
+
console.error(`[roots] Resolved via --project-root: key=${profileKey}, project=${projectName}, stableId=${stableHash || 'none'}`);
|
|
116
185
|
return {
|
|
117
186
|
profileKey,
|
|
118
187
|
projectName,
|
|
@@ -126,8 +195,10 @@ export async function resolveRoots(server, fallbackOptions) {
|
|
|
126
195
|
if (fallbackOptions.envProjectRoot) {
|
|
127
196
|
const uri = pathToFileUri(fallbackOptions.envProjectRoot);
|
|
128
197
|
const projectName = extractProjectName([{ uri }]);
|
|
129
|
-
|
|
130
|
-
|
|
198
|
+
// Get stable identity hash
|
|
199
|
+
const stableHash = getStableIdentityHash(fallbackOptions.envProjectRoot);
|
|
200
|
+
const profileKey = generateProfileKey([uri], clientName, clientVersion, projectName, stableHash);
|
|
201
|
+
console.error(`[roots] Resolved via MCP_PROJECT_ROOT: key=${profileKey}, project=${projectName}, stableId=${stableHash || 'none'}`);
|
|
131
202
|
return {
|
|
132
203
|
profileKey,
|
|
133
204
|
projectName,
|
|
@@ -141,8 +212,10 @@ export async function resolveRoots(server, fallbackOptions) {
|
|
|
141
212
|
if (fallbackOptions.autoCwd) {
|
|
142
213
|
const uri = pathToFileUri(fallbackOptions.autoCwd);
|
|
143
214
|
const projectName = extractProjectName([{ uri }]);
|
|
144
|
-
|
|
145
|
-
|
|
215
|
+
// Get stable identity hash
|
|
216
|
+
const stableHash = getStableIdentityHash(fallbackOptions.autoCwd);
|
|
217
|
+
const profileKey = generateProfileKey([uri], clientName, clientVersion, projectName, stableHash);
|
|
218
|
+
console.error(`[roots] Resolved via AUTO (cwd): key=${profileKey}, project=${projectName}, stableId=${stableHash || 'none'}`);
|
|
146
219
|
return {
|
|
147
220
|
profileKey,
|
|
148
221
|
projectName,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-devtools-mcp-for-extension",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.6",
|
|
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": "./scripts/cli.mjs",
|