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 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 profilePath = path.join(CACHE_ROOT, 'profiles', opts.rootsInfo.profileKey, opts.rootsInfo.clientName, channel);
27
- const normalized = pathNormalize(profilePath);
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
- // Sort URIs for consistent hashing across multi-root workspaces
39
- const sortedUris = [...rootsUris].sort();
40
- // Note: clientVersion is intentionally excluded from hash
41
- // to keep profiles stable across client updates
42
- const keyMaterial = JSON.stringify({
43
- roots: sortedUris,
44
- client: clientName,
45
- });
46
- // Use first 8 chars of SHA-256 for stable, collision-resistant key
47
- const hash = createHash('sha256').update(keyMaterial).digest('hex').slice(0, 8);
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
- const pathParts = url.pathname.split('/').filter(Boolean);
72
- const dirName = pathParts[pathParts.length - 1] || 'root';
73
- return sanitizeProjectName(dirName);
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
- const profileKey = generateProfileKey(rootsUris, clientName, clientVersion, projectName);
100
- console.error(`[roots] Resolved via roots/list: key=${profileKey}, project=${projectName}, client=${clientName}`);
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
- const profileKey = generateProfileKey([uri], clientName, clientVersion, projectName);
115
- console.error(`[roots] Resolved via --project-root: key=${profileKey}, project=${projectName}`);
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
- const profileKey = generateProfileKey([uri], clientName, clientVersion, projectName);
130
- console.error(`[roots] Resolved via MCP_PROJECT_ROOT: key=${profileKey}, project=${projectName}`);
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
- const profileKey = generateProfileKey([uri], clientName, clientVersion, projectName);
145
- console.error(`[roots] Resolved via AUTO (cwd): key=${profileKey}, project=${projectName}`);
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.4",
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",