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.
- package/build/src/browser.js +3 -0
- package/build/src/main.js +12 -0
- package/build/src/tools/extensions.js +104 -12
- package/package.json +1 -1
package/build/src/browser.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
+
"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",
|