chrome-devtools-mcp-for-extension 0.8.3 → 0.8.5

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.
@@ -443,9 +443,12 @@ export async function launch(options) {
443
443
  }
444
444
  }
445
445
  async function ensureBrowserLaunched(options) {
446
+ console.error(`[ensureBrowserLaunched] browser exists: ${!!browser}, connected: ${browser?.connected}`);
446
447
  if (browser?.connected) {
448
+ console.error(`[ensureBrowserLaunched] Reusing existing browser`);
447
449
  return browser;
448
450
  }
451
+ console.error(`[ensureBrowserLaunched] Launching new browser`);
449
452
  browser = await launch(options);
450
453
  return browser;
451
454
  }
package/build/src/main.js CHANGED
@@ -102,6 +102,18 @@ function registerTool(tool) {
102
102
  }
103
103
  catch (error) {
104
104
  const errorText = error instanceof Error ? error.message : String(error);
105
+ // Detect browser closed error and provide helpful message
106
+ if (errorText.includes('Target closed') || errorText.includes('Session closed')) {
107
+ return {
108
+ content: [
109
+ {
110
+ type: 'text',
111
+ text: `Browser connection lost. The Chrome instance was closed or disconnected.\n\nPlease restart the MCP server to reconnect.`,
112
+ },
113
+ ],
114
+ isError: true,
115
+ };
116
+ }
105
117
  return {
106
118
  content: [
107
119
  {
@@ -23,7 +23,13 @@ export const listExtensions = defineTool({
23
23
  await context.waitForEventsAfterAction(async () => {
24
24
  await page.goto('chrome://extensions/', { waitUntil: 'networkidle0' });
25
25
  const extensions = await page.evaluate(() => {
26
- const extensionCards = document.querySelectorAll('extensions-item');
26
+ const manager = document.querySelector('extensions-manager');
27
+ if (!manager?.shadowRoot)
28
+ return null;
29
+ const itemList = manager.shadowRoot.querySelector('extensions-item-list');
30
+ if (!itemList?.shadowRoot)
31
+ return null;
32
+ const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
27
33
  const results = [];
28
34
  Array.from(extensionCards).forEach(card => {
29
35
  const shadowRoot = card.shadowRoot;
@@ -50,6 +56,10 @@ export const listExtensions = defineTool({
50
56
  });
51
57
  return results;
52
58
  });
59
+ if (!extensions) {
60
+ response.appendResponseLine('❌ Failed to query extensions page');
61
+ return;
62
+ }
53
63
  response.appendResponseLine('Installed Chrome Extensions:');
54
64
  response.appendResponseLine('');
55
65
  if (extensions.length === 0) {
@@ -88,7 +98,13 @@ export const getExtensionInfo = defineTool({
88
98
  await context.waitForEventsAfterAction(async () => {
89
99
  await page.goto('chrome://extensions/', { waitUntil: 'networkidle0' });
90
100
  const extensionInfo = await page.evaluate((searchName) => {
91
- const extensionCards = document.querySelectorAll('extensions-item');
101
+ const manager = document.querySelector('extensions-manager');
102
+ if (!manager?.shadowRoot)
103
+ return null;
104
+ const itemList = manager.shadowRoot.querySelector('extensions-item-list');
105
+ if (!itemList?.shadowRoot)
106
+ return null;
107
+ const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
92
108
  for (const card of Array.from(extensionCards)) {
93
109
  const shadowRoot = card.shadowRoot;
94
110
  if (shadowRoot) {
@@ -139,6 +155,10 @@ export const getExtensionInfo = defineTool({
139
155
  }
140
156
  return { found: false };
141
157
  }, extensionName);
158
+ if (!extensionInfo) {
159
+ response.appendResponseLine('❌ Failed to query extensions page');
160
+ return;
161
+ }
142
162
  if (extensionInfo.found) {
143
163
  response.appendResponseLine(`## Extension: ${extensionInfo.name}`);
144
164
  response.appendResponseLine('');
@@ -194,7 +214,13 @@ export const reloadExtension = defineTool({
194
214
  await page.goto('chrome://extensions/', { waitUntil: 'networkidle0' });
195
215
  // Get extension info before reload
196
216
  const beforeState = await page.evaluate((searchName) => {
197
- const extensionCards = document.querySelectorAll('extensions-item');
217
+ const manager = document.querySelector('extensions-manager');
218
+ if (!manager?.shadowRoot)
219
+ return null;
220
+ const itemList = manager.shadowRoot.querySelector('extensions-item-list');
221
+ if (!itemList?.shadowRoot)
222
+ return null;
223
+ const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
198
224
  for (const card of Array.from(extensionCards)) {
199
225
  const shadowRoot = card.shadowRoot;
200
226
  if (shadowRoot) {
@@ -216,6 +242,10 @@ export const reloadExtension = defineTool({
216
242
  }
217
243
  return { found: false };
218
244
  }, extensionName);
245
+ if (!beforeState) {
246
+ response.appendResponseLine('❌ Failed to query extensions page');
247
+ return;
248
+ }
219
249
  if (!beforeState.found) {
220
250
  response.appendResponseLine(`❌ Extension not found: "${extensionName}"`);
221
251
  return;
@@ -226,13 +256,27 @@ export const reloadExtension = defineTool({
226
256
  response.appendResponseLine(`🔄 Reloading: ${beforeState.name} v${beforeState.version}`);
227
257
  // Perform reload
228
258
  const reloadResult = await page.evaluate((searchName) => {
229
- const extensionCards = document.querySelectorAll('extensions-item');
259
+ const manager = document.querySelector('extensions-manager');
260
+ if (!manager?.shadowRoot)
261
+ return null;
262
+ const itemList = manager.shadowRoot.querySelector('extensions-item-list');
263
+ if (!itemList?.shadowRoot)
264
+ return null;
265
+ const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
230
266
  for (const card of Array.from(extensionCards)) {
231
267
  const shadowRoot = card.shadowRoot;
232
268
  if (shadowRoot) {
233
269
  const name = shadowRoot.querySelector('#name')?.textContent?.trim() || '';
234
270
  if (name.toLowerCase().includes(searchName.toLowerCase())) {
235
- const reloadButton = shadowRoot.querySelector('#reload-button');
271
+ // Try multiple selectors for reload button
272
+ let reloadButton = shadowRoot.querySelector('#reload-button');
273
+ if (!reloadButton) {
274
+ reloadButton = shadowRoot.querySelector('cr-icon-button[id="reload-button"]');
275
+ }
276
+ if (!reloadButton) {
277
+ // Try finding by aria-label or title
278
+ reloadButton = shadowRoot.querySelector('[aria-label*="Reload"]');
279
+ }
236
280
  if (reloadButton && !reloadButton.hasAttribute('hidden')) {
237
281
  reloadButton.click();
238
282
  return { success: true };
@@ -240,7 +284,7 @@ export const reloadExtension = defineTool({
240
284
  else {
241
285
  return {
242
286
  success: false,
243
- reason: 'Reload button not available (extension not in developer mode)',
287
+ reason: 'Reload button not available (extension not in developer mode or button hidden)',
244
288
  };
245
289
  }
246
290
  }
@@ -248,6 +292,10 @@ export const reloadExtension = defineTool({
248
292
  }
249
293
  return { success: false, reason: 'Extension not found' };
250
294
  }, extensionName);
295
+ if (!reloadResult) {
296
+ response.appendResponseLine('❌ Failed to execute reload');
297
+ return;
298
+ }
251
299
  if (!reloadResult.success) {
252
300
  response.appendResponseLine(`❌ Failed: ${reloadResult.reason}`);
253
301
  return;
@@ -256,7 +304,13 @@ export const reloadExtension = defineTool({
256
304
  await new Promise(resolve => setTimeout(resolve, 1000));
257
305
  // Check for errors after reload
258
306
  const afterState = await page.evaluate((searchName) => {
259
- const extensionCards = document.querySelectorAll('extensions-item');
307
+ const manager = document.querySelector('extensions-manager');
308
+ if (!manager?.shadowRoot)
309
+ return null;
310
+ const itemList = manager.shadowRoot.querySelector('extensions-item-list');
311
+ if (!itemList?.shadowRoot)
312
+ return null;
313
+ const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
260
314
  for (const card of Array.from(extensionCards)) {
261
315
  const shadowRoot = card.shadowRoot;
262
316
  if (shadowRoot) {
@@ -277,6 +331,10 @@ export const reloadExtension = defineTool({
277
331
  }
278
332
  return { found: false, hasErrors: false };
279
333
  }, extensionName);
334
+ if (!afterState) {
335
+ response.appendResponseLine('⚠️ Warning: Could not verify reload status');
336
+ return;
337
+ }
280
338
  response.appendResponseLine('');
281
339
  if (afterState.hasErrors) {
282
340
  response.appendResponseLine(`⚠️ Extension reloaded but has errors (v${afterState.version})`);
@@ -310,13 +368,23 @@ export const toggleExtensionState = defineTool({
310
368
  await context.waitForEventsAfterAction(async () => {
311
369
  await page.goto('chrome://extensions/', { waitUntil: 'networkidle0' });
312
370
  const result = await page.evaluate((searchName, targetEnabled) => {
313
- const extensionCards = document.querySelectorAll('extensions-item');
371
+ const manager = document.querySelector('extensions-manager');
372
+ if (!manager?.shadowRoot)
373
+ return null;
374
+ const itemList = manager.shadowRoot.querySelector('extensions-item-list');
375
+ if (!itemList?.shadowRoot)
376
+ return null;
377
+ const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
314
378
  for (const card of Array.from(extensionCards)) {
315
379
  const shadowRoot = card.shadowRoot;
316
380
  if (shadowRoot) {
317
381
  const name = shadowRoot.querySelector('#name')?.textContent?.trim() || '';
318
382
  if (name.toLowerCase().includes(searchName.toLowerCase())) {
319
- const enableToggle = shadowRoot.querySelector('#enable-toggle');
383
+ // Try both selectors: #enable-toggle (older Chrome) and cr-toggle (newer Chrome)
384
+ let enableToggle = shadowRoot.querySelector('#enable-toggle');
385
+ if (!enableToggle) {
386
+ enableToggle = shadowRoot.querySelector('cr-toggle');
387
+ }
320
388
  const currentEnabled = enableToggle?.getAttribute('checked') === '';
321
389
  // Check if already in desired state
322
390
  if (currentEnabled === targetEnabled) {
@@ -341,7 +409,7 @@ export const toggleExtensionState = defineTool({
341
409
  else {
342
410
  return {
343
411
  success: false,
344
- reason: 'Enable/disable toggle not found',
412
+ reason: 'Enable/disable toggle not found (tried #enable-toggle and cr-toggle)',
345
413
  };
346
414
  }
347
415
  }
@@ -349,6 +417,10 @@ export const toggleExtensionState = defineTool({
349
417
  }
350
418
  return { success: false, reason: 'Extension not found' };
351
419
  }, extensionName, desiredEnabled);
420
+ if (!result) {
421
+ response.appendResponseLine('❌ Failed to query extensions page');
422
+ return;
423
+ }
352
424
  if (!result.success) {
353
425
  response.appendResponseLine(`❌ Failed: ${result.reason}`);
354
426
  return;
@@ -381,7 +453,13 @@ export const openExtensionPopup = defineTool({
381
453
  // First, get extension ID
382
454
  await page.goto('chrome://extensions/', { waitUntil: 'networkidle0' });
383
455
  const extensionInfo = await page.evaluate((searchName) => {
384
- const extensionCards = document.querySelectorAll('extensions-item');
456
+ const manager = document.querySelector('extensions-manager');
457
+ if (!manager?.shadowRoot)
458
+ return null;
459
+ const itemList = manager.shadowRoot.querySelector('extensions-item-list');
460
+ if (!itemList?.shadowRoot)
461
+ return null;
462
+ const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
385
463
  for (const card of Array.from(extensionCards)) {
386
464
  const shadowRoot = card.shadowRoot;
387
465
  if (shadowRoot) {
@@ -394,6 +472,10 @@ export const openExtensionPopup = defineTool({
394
472
  }
395
473
  return { found: false };
396
474
  }, extensionName);
475
+ if (!extensionInfo) {
476
+ response.appendResponseLine('❌ Failed to query extensions page');
477
+ return;
478
+ }
397
479
  if (!extensionInfo.found) {
398
480
  response.appendResponseLine(`❌ Extension not found: "${extensionName}"`);
399
481
  return;
@@ -498,7 +580,13 @@ export const inspectServiceWorker = defineTool({
498
580
  await context.waitForEventsAfterAction(async () => {
499
581
  await page.goto('chrome://extensions/', { waitUntil: 'networkidle0' });
500
582
  const inspectResult = await page.evaluate((searchName) => {
501
- const extensionCards = document.querySelectorAll('extensions-item');
583
+ const manager = document.querySelector('extensions-manager');
584
+ if (!manager?.shadowRoot)
585
+ return null;
586
+ const itemList = manager.shadowRoot.querySelector('extensions-item-list');
587
+ if (!itemList?.shadowRoot)
588
+ return null;
589
+ const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
502
590
  for (const card of Array.from(extensionCards)) {
503
591
  const shadowRoot = card.shadowRoot;
504
592
  if (shadowRoot) {
@@ -533,6 +621,10 @@ export const inspectServiceWorker = defineTool({
533
621
  }
534
622
  return { success: false, reason: 'Extension not found' };
535
623
  }, extensionName);
624
+ if (!inspectResult) {
625
+ response.appendResponseLine('❌ Failed to find extension');
626
+ return;
627
+ }
536
628
  if (inspectResult.success) {
537
629
  response.appendResponseLine(`✅ Opened DevTools for ${inspectResult.type} of: ${inspectResult.extensionName}`);
538
630
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-devtools-mcp-for-extension",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
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",