chrome-devtools-mcp-for-extension 0.18.10 → 0.19.0

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/README.md CHANGED
@@ -299,13 +299,17 @@ git push && git push --tags
299
299
 
300
300
  | Tool | Purpose | Example |
301
301
  |------|---------|---------|
302
- | `list_extensions` | View all extensions | "List my extensions" |
303
- | `reload_extension` | Hot-reload | "Reload my-extension" |
304
- | `inspect_service_worker` | Debug background | "Debug service worker" |
302
+ | `open_extension_popup` | Select popup window | "Open my extension popup" |
303
+ | `reload_iframe_extension` | Hot-reload via CDP | "Reload extension" |
304
+ | `patch_iframe_popup` | Edit & reload | "Patch popup.html" |
305
305
  | `ask_chatgpt_web` | ChatGPT research | "Ask ChatGPT about..." |
306
306
  | `take_snapshot` | Page analysis | "Snapshot current page" |
307
307
  | `list_pages` | Browser tabs | "List open pages" |
308
308
 
309
+ **Note:** Extension tools use CDP (Chrome DevTools Protocol) for reliable operation.
310
+ Shadow DOM-based tools (`list_extensions`, `reload_extension`, etc.) were removed in v0.19.0
311
+ due to Chrome security restrictions.
312
+
309
313
  **See also:** [Full Tool Documentation](docs/tools-reference.md)
310
314
 
311
315
  ---
@@ -315,7 +319,7 @@ git push && git push --tags
315
319
  ### Extension Not Loading
316
320
 
317
321
  ```
318
- "List extensions and show any errors"
322
+ "Check extension popup for errors"
319
323
  ```
320
324
 
321
325
  **Common fixes:**
@@ -8,444 +8,21 @@ import z from 'zod';
8
8
  import { ToolCategories } from './categories.js';
9
9
  import { defineTool } from './ToolDefinition.js';
10
10
  // ========================================
11
- // Essential User-Facing Tools (3 tools only)
11
+ // Chrome Extension Development Tools
12
+ // ========================================
13
+ //
14
+ // These tools use CDP (Chrome DevTools Protocol) and direct URL access
15
+ // to interact with extensions. Tools that relied on chrome://extensions
16
+ // Shadow DOM scraping have been removed as they are unreliable due to
17
+ // Chrome's security restrictions.
18
+ //
19
+ // Working tools:
20
+ // - openExtensionPopup: Uses page.goto('chrome-extension://ID/popup.html')
21
+ // - closeExtensionPopup: URL validation only
22
+ // - inspectIframePopup: CDP frame attachment
23
+ // - patchIframePopup: File I/O + CDP reload
24
+ // - reloadIframeExtension: CDP + chrome.runtime.reload()
12
25
  // ========================================
13
- export const listExtensions = defineTool({
14
- name: 'list_extensions',
15
- description: `List all installed Chrome extensions with their status, version, and ability to reload or debug them.`,
16
- annotations: {
17
- category: ToolCategories.EXTENSION_DEVELOPMENT,
18
- readOnlyHint: true,
19
- },
20
- schema: {},
21
- handler: async (_request, response, context) => {
22
- const page = context.getSelectedPage();
23
- await context.waitForEventsAfterAction(async () => {
24
- await page.goto('chrome://extensions/', { waitUntil: 'networkidle0' });
25
- const extensions = await page.evaluate(() => {
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');
33
- const results = [];
34
- Array.from(extensionCards).forEach(card => {
35
- const shadowRoot = card.shadowRoot;
36
- if (shadowRoot) {
37
- const name = shadowRoot.querySelector('#name')?.textContent?.trim() ||
38
- 'Unknown';
39
- const description = shadowRoot.querySelector('#description')?.textContent?.trim() ||
40
- '';
41
- const version = shadowRoot.querySelector('#version')?.textContent?.trim() || '';
42
- let enableToggle = shadowRoot.querySelector('#enable-toggle');
43
- if (!enableToggle) {
44
- enableToggle = shadowRoot.querySelector('cr-toggle');
45
- }
46
- const enabled = enableToggle?.getAttribute('checked') === '';
47
- const id = card.getAttribute('id') || 'unknown';
48
- const errorsBadge = shadowRoot.querySelector('#errors-button .badge');
49
- const hasErrors = errorsBadge ? parseInt(errorsBadge.textContent?.trim() || '0') > 0 : false;
50
- results.push({
51
- id,
52
- name,
53
- enabled,
54
- version,
55
- description,
56
- hasErrors,
57
- });
58
- }
59
- });
60
- return results;
61
- });
62
- if (!extensions) {
63
- response.appendResponseLine('❌ Failed to query extensions page');
64
- return;
65
- }
66
- response.appendResponseLine('Installed Chrome Extensions:');
67
- response.appendResponseLine('');
68
- if (extensions.length === 0) {
69
- response.appendResponseLine('No extensions found.');
70
- }
71
- else {
72
- extensions.forEach((ext, index) => {
73
- response.appendResponseLine(`${index + 1}. **${ext.name}** v${ext.version}`);
74
- response.appendResponseLine(` ID: ${ext.id}`);
75
- response.appendResponseLine(` Status: ${ext.enabled ? '✅ Enabled' : '❌ Disabled'}${ext.hasErrors ? ' ⚠️ Has errors' : ''}`);
76
- if (ext.description) {
77
- response.appendResponseLine(` ${ext.description}`);
78
- }
79
- response.appendResponseLine('');
80
- });
81
- response.appendResponseLine('💡 Use `reload_extension` to reload or `inspect_service_worker` to debug');
82
- }
83
- });
84
- },
85
- });
86
- export const getExtensionInfo = defineTool({
87
- name: 'get_extension_info',
88
- description: `Get detailed information about a specific Chrome extension including its current state, version, and any errors.`,
89
- annotations: {
90
- category: ToolCategories.EXTENSION_DEVELOPMENT,
91
- readOnlyHint: true,
92
- },
93
- schema: {
94
- extensionName: z
95
- .string()
96
- .describe('The name or partial name of the extension to get info about'),
97
- },
98
- handler: async (request, response, context) => {
99
- const page = context.getSelectedPage();
100
- const { extensionName } = request.params;
101
- await context.waitForEventsAfterAction(async () => {
102
- await page.goto('chrome://extensions/', { waitUntil: 'networkidle0' });
103
- const extensionInfo = await page.evaluate((searchName) => {
104
- const manager = document.querySelector('extensions-manager');
105
- if (!manager?.shadowRoot)
106
- return null;
107
- const itemList = manager.shadowRoot.querySelector('extensions-item-list');
108
- if (!itemList?.shadowRoot)
109
- return null;
110
- const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
111
- for (const card of Array.from(extensionCards)) {
112
- const shadowRoot = card.shadowRoot;
113
- if (shadowRoot) {
114
- const name = shadowRoot.querySelector('#name')?.textContent?.trim() || '';
115
- if (name.toLowerCase().includes(searchName.toLowerCase())) {
116
- const description = shadowRoot.querySelector('#description')?.textContent?.trim() ||
117
- '';
118
- const version = shadowRoot.querySelector('#version')?.textContent?.trim() || '';
119
- let enableToggle = shadowRoot.querySelector('#enable-toggle');
120
- if (!enableToggle) {
121
- enableToggle = shadowRoot.querySelector('cr-toggle');
122
- }
123
- const enabled = enableToggle?.getAttribute('checked') === '';
124
- const id = card.getAttribute('id') || 'unknown';
125
- const errorsBadge = shadowRoot.querySelector('#errors-button .badge');
126
- const hasErrors = errorsBadge
127
- ? parseInt(errorsBadge.textContent?.trim() || '0') > 0
128
- : false;
129
- // Get error details if available
130
- const errors = [];
131
- if (hasErrors) {
132
- const errorsButton = shadowRoot.querySelector('#errors-button');
133
- if (errorsButton) {
134
- // Try to get error text from the errors section
135
- const errorsList = shadowRoot.querySelectorAll('.error-list .error-message');
136
- errorsList.forEach(err => {
137
- const errorText = err.textContent?.trim();
138
- if (errorText) {
139
- errors.push(errorText);
140
- }
141
- });
142
- }
143
- }
144
- // Check if this is a development extension
145
- const detailsView = shadowRoot.querySelector('extensions-detail-view');
146
- const isDevelopment = detailsView ?
147
- detailsView.shadowRoot?.querySelector('#load-path')?.textContent?.trim() : undefined;
148
- return {
149
- found: true,
150
- id,
151
- name,
152
- version,
153
- description,
154
- enabled,
155
- hasErrors,
156
- errors,
157
- path: isDevelopment || 'Not a development extension',
158
- };
159
- }
160
- }
161
- }
162
- return { found: false };
163
- }, extensionName);
164
- if (!extensionInfo) {
165
- response.appendResponseLine('❌ Failed to query extensions page');
166
- return;
167
- }
168
- if (extensionInfo.found) {
169
- response.appendResponseLine(`## Extension: ${extensionInfo.name}`);
170
- response.appendResponseLine('');
171
- response.appendResponseLine(`**ID:** ${extensionInfo.id}`);
172
- response.appendResponseLine(`**Version:** ${extensionInfo.version}`);
173
- response.appendResponseLine(`**Status:** ${extensionInfo.enabled ? '✅ Enabled' : '❌ Disabled'}`);
174
- if (extensionInfo.description) {
175
- response.appendResponseLine(`**Description:** ${extensionInfo.description}`);
176
- }
177
- if (extensionInfo.path !== 'Not a development extension') {
178
- response.appendResponseLine(`**Path:** ${extensionInfo.path}`);
179
- }
180
- response.appendResponseLine('');
181
- if (extensionInfo.hasErrors) {
182
- response.appendResponseLine('⚠️ **Errors:**');
183
- if (extensionInfo.errors.length > 0) {
184
- extensionInfo.errors.forEach(err => {
185
- response.appendResponseLine(` - ${err}`);
186
- });
187
- }
188
- else {
189
- response.appendResponseLine(' Extension has errors (details not available)');
190
- }
191
- }
192
- else {
193
- response.appendResponseLine('✅ No errors detected');
194
- }
195
- }
196
- else {
197
- response.appendResponseLine(`❌ Extension not found: "${extensionName}"`);
198
- response.appendResponseLine('');
199
- response.appendResponseLine('💡 Use `list_extensions` to see all installed extensions');
200
- }
201
- });
202
- },
203
- });
204
- export const reloadExtension = defineTool({
205
- name: 'reload_extension',
206
- description: `Reload a Chrome extension to apply changes during development. Checks extension state before and after reload.`,
207
- annotations: {
208
- category: ToolCategories.EXTENSION_DEVELOPMENT,
209
- readOnlyHint: false,
210
- },
211
- schema: {
212
- extensionName: z
213
- .string()
214
- .describe('The name or partial name of the extension to reload'),
215
- },
216
- handler: async (request, response, context) => {
217
- const page = context.getSelectedPage();
218
- const { extensionName } = request.params;
219
- await context.waitForEventsAfterAction(async () => {
220
- await page.goto('chrome://extensions/', { waitUntil: 'networkidle0' });
221
- // Get extension info before reload
222
- const beforeState = await page.evaluate((searchName) => {
223
- const manager = document.querySelector('extensions-manager');
224
- if (!manager?.shadowRoot)
225
- return null;
226
- const itemList = manager.shadowRoot.querySelector('extensions-item-list');
227
- if (!itemList?.shadowRoot)
228
- return null;
229
- const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
230
- for (const card of Array.from(extensionCards)) {
231
- const shadowRoot = card.shadowRoot;
232
- if (shadowRoot) {
233
- const name = shadowRoot.querySelector('#name')?.textContent?.trim() || '';
234
- if (name.toLowerCase().includes(searchName.toLowerCase())) {
235
- const version = shadowRoot.querySelector('#version')?.textContent?.trim() || '';
236
- let enableToggle = shadowRoot.querySelector('#enable-toggle');
237
- if (!enableToggle) {
238
- enableToggle = shadowRoot.querySelector('cr-toggle');
239
- }
240
- const enabled = enableToggle?.getAttribute('checked') === '';
241
- const id = card.getAttribute('id') || 'unknown';
242
- return {
243
- found: true,
244
- id,
245
- name,
246
- version,
247
- enabled,
248
- };
249
- }
250
- }
251
- }
252
- return { found: false };
253
- }, extensionName);
254
- if (!beforeState) {
255
- response.appendResponseLine('❌ Failed to query extensions page');
256
- return;
257
- }
258
- if (!beforeState.found) {
259
- response.appendResponseLine(`❌ Extension not found: "${extensionName}"`);
260
- return;
261
- }
262
- if (!beforeState.enabled) {
263
- response.appendResponseLine(`⚠️ Warning: Extension "${beforeState.name}" is currently disabled`);
264
- }
265
- response.appendResponseLine(`🔄 Reloading: ${beforeState.name} v${beforeState.version}`);
266
- // Perform reload
267
- const reloadResult = await page.evaluate((searchName) => {
268
- const manager = document.querySelector('extensions-manager');
269
- if (!manager?.shadowRoot)
270
- return null;
271
- const itemList = manager.shadowRoot.querySelector('extensions-item-list');
272
- if (!itemList?.shadowRoot)
273
- return null;
274
- const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
275
- for (const card of Array.from(extensionCards)) {
276
- const shadowRoot = card.shadowRoot;
277
- if (shadowRoot) {
278
- const name = shadowRoot.querySelector('#name')?.textContent?.trim() || '';
279
- if (name.toLowerCase().includes(searchName.toLowerCase())) {
280
- // Try multiple selectors for reload button (dev-reload-button in developer mode)
281
- let reloadButton = shadowRoot.querySelector('#dev-reload-button');
282
- if (!reloadButton) {
283
- reloadButton = shadowRoot.querySelector('#reload-button');
284
- }
285
- if (!reloadButton) {
286
- // Try finding by aria-label (supports both English and Japanese)
287
- reloadButton = shadowRoot.querySelector('[aria-label*="再読み込み"]');
288
- }
289
- if (!reloadButton) {
290
- reloadButton = shadowRoot.querySelector('[aria-label*="Reload"]');
291
- }
292
- if (reloadButton && !reloadButton.hasAttribute('hidden')) {
293
- reloadButton.click();
294
- return { success: true };
295
- }
296
- else {
297
- return {
298
- success: false,
299
- reason: 'Reload button not available (extension not in developer mode or button hidden)',
300
- };
301
- }
302
- }
303
- }
304
- }
305
- return { success: false, reason: 'Extension not found' };
306
- }, extensionName);
307
- if (!reloadResult) {
308
- response.appendResponseLine('❌ Failed to execute reload');
309
- return;
310
- }
311
- if (!reloadResult.success) {
312
- response.appendResponseLine(`❌ Failed: ${reloadResult.reason}`);
313
- return;
314
- }
315
- // Wait for reload to complete
316
- await new Promise(resolve => setTimeout(resolve, 1000));
317
- // Check for errors after reload
318
- const afterState = await page.evaluate((searchName) => {
319
- const manager = document.querySelector('extensions-manager');
320
- if (!manager?.shadowRoot)
321
- return null;
322
- const itemList = manager.shadowRoot.querySelector('extensions-item-list');
323
- if (!itemList?.shadowRoot)
324
- return null;
325
- const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
326
- for (const card of Array.from(extensionCards)) {
327
- const shadowRoot = card.shadowRoot;
328
- if (shadowRoot) {
329
- const name = shadowRoot.querySelector('#name')?.textContent?.trim() || '';
330
- if (name.toLowerCase().includes(searchName.toLowerCase())) {
331
- const version = shadowRoot.querySelector('#version')?.textContent?.trim() || '';
332
- const errorsBadge = shadowRoot.querySelector('#errors-button .badge');
333
- const hasErrors = errorsBadge
334
- ? parseInt(errorsBadge.textContent?.trim() || '0') > 0
335
- : false;
336
- return {
337
- found: true,
338
- version,
339
- hasErrors,
340
- };
341
- }
342
- }
343
- }
344
- return { found: false, hasErrors: false };
345
- }, extensionName);
346
- if (!afterState) {
347
- response.appendResponseLine('⚠️ Warning: Could not verify reload status');
348
- return;
349
- }
350
- response.appendResponseLine('');
351
- if (afterState.hasErrors) {
352
- response.appendResponseLine(`⚠️ Extension reloaded but has errors (v${afterState.version})`);
353
- response.appendResponseLine('💡 Use `get_extension_info` to see error details');
354
- }
355
- else {
356
- response.appendResponseLine(`✅ Successfully reloaded: ${beforeState.name} v${afterState.version}`);
357
- }
358
- });
359
- },
360
- });
361
- export const toggleExtensionState = defineTool({
362
- name: 'toggle_extension_state',
363
- description: `Safely enable or disable a Chrome extension. Always checks current state before toggling to prevent accidental changes.`,
364
- annotations: {
365
- category: ToolCategories.EXTENSION_DEVELOPMENT,
366
- readOnlyHint: false,
367
- },
368
- schema: {
369
- extensionName: z
370
- .string()
371
- .describe('The name or partial name of the extension'),
372
- state: z
373
- .enum(['enable', 'disable'])
374
- .describe('Desired state: "enable" or "disable"'),
375
- },
376
- handler: async (request, response, context) => {
377
- const page = context.getSelectedPage();
378
- const { extensionName, state } = request.params;
379
- const desiredEnabled = state === 'enable';
380
- await context.waitForEventsAfterAction(async () => {
381
- await page.goto('chrome://extensions/', { waitUntil: 'networkidle0' });
382
- const result = await page.evaluate((searchName, targetEnabled) => {
383
- const manager = document.querySelector('extensions-manager');
384
- if (!manager?.shadowRoot)
385
- return null;
386
- const itemList = manager.shadowRoot.querySelector('extensions-item-list');
387
- if (!itemList?.shadowRoot)
388
- return null;
389
- const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
390
- for (const card of Array.from(extensionCards)) {
391
- const shadowRoot = card.shadowRoot;
392
- if (shadowRoot) {
393
- const name = shadowRoot.querySelector('#name')?.textContent?.trim() || '';
394
- if (name.toLowerCase().includes(searchName.toLowerCase())) {
395
- // Try both selectors: #enable-toggle (older Chrome) and cr-toggle (newer Chrome)
396
- let enableToggle = shadowRoot.querySelector('#enable-toggle');
397
- if (!enableToggle) {
398
- enableToggle = shadowRoot.querySelector('cr-toggle');
399
- }
400
- const currentEnabled = enableToggle?.getAttribute('checked') === '';
401
- // Check if already in desired state
402
- if (currentEnabled === targetEnabled) {
403
- return {
404
- success: true,
405
- alreadyInState: true,
406
- extensionName: name,
407
- currentState: currentEnabled,
408
- };
409
- }
410
- // Toggle the state
411
- if (enableToggle) {
412
- enableToggle.click();
413
- return {
414
- success: true,
415
- alreadyInState: false,
416
- extensionName: name,
417
- previousState: currentEnabled,
418
- newState: targetEnabled,
419
- };
420
- }
421
- else {
422
- return {
423
- success: false,
424
- reason: 'Enable/disable toggle not found (tried #enable-toggle and cr-toggle)',
425
- };
426
- }
427
- }
428
- }
429
- }
430
- return { success: false, reason: 'Extension not found' };
431
- }, extensionName, desiredEnabled);
432
- if (!result) {
433
- response.appendResponseLine('❌ Failed to query extensions page');
434
- return;
435
- }
436
- if (!result.success) {
437
- response.appendResponseLine(`❌ Failed: ${result.reason}`);
438
- return;
439
- }
440
- if (result.alreadyInState) {
441
- response.appendResponseLine(`ℹ️ Extension "${result.extensionName}" is already ${result.currentState ? 'enabled' : 'disabled'}`);
442
- }
443
- else {
444
- response.appendResponseLine(`✅ ${result.extensionName}: ${result.previousState ? 'Enabled' : 'Disabled'} → ${result.newState ? 'Enabled' : 'Disabled'}`);
445
- }
446
- });
447
- },
448
- });
449
26
  export const openExtensionPopup = defineTool({
450
27
  name: 'open_extension_popup',
451
28
  description: `Select an already-opened Chrome extension popup window for testing. If no extension name is provided, it will automatically detect and select the currently active popup window. If an extension name is provided, it will search for that specific extension's popup. After selection, you can use take_snapshot, click, evaluate_script, etc. on the popup.`,
@@ -518,100 +95,38 @@ export const openExtensionPopup = defineTool({
518
95
  response.appendResponseLine('💡 Please manually click the extension icon to open the popup first.');
519
96
  return;
520
97
  }
521
- // First, get extension ID
522
- await page.goto('chrome://extensions/', { waitUntil: 'networkidle0' });
523
- const extensionInfo = await page.evaluate((searchName) => {
524
- const manager = document.querySelector('extensions-manager');
525
- if (!manager?.shadowRoot)
526
- return null;
527
- const itemList = manager.shadowRoot.querySelector('extensions-item-list');
528
- if (!itemList?.shadowRoot)
529
- return null;
530
- const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
531
- for (const card of Array.from(extensionCards)) {
532
- const shadowRoot = card.shadowRoot;
533
- if (shadowRoot) {
534
- const name = shadowRoot.querySelector('#name')?.textContent?.trim() || '';
535
- if (name.toLowerCase().includes(searchName.toLowerCase())) {
536
- const id = card.getAttribute('id') || '';
537
- return { found: true, id, name };
538
- }
539
- }
540
- }
541
- return { found: false };
542
- }, extensionName);
543
- if (!extensionInfo) {
544
- response.appendResponseLine('❌ Failed to query extensions page');
545
- return;
546
- }
547
- if (!extensionInfo.found) {
548
- response.appendResponseLine(`❌ Extension not found: "${extensionName}"`);
549
- return;
550
- }
551
- response.appendResponseLine(`🔍 Found extension: ${extensionInfo.name} (${extensionInfo.id})`);
552
- try {
553
- const browser = page.browser();
554
- if (!browser) {
555
- response.appendResponseLine('❌ Failed to get browser instance.');
98
+ // If extensionName is provided, search for popup containing that name in URL
99
+ // This uses URL-based detection, not chrome://extensions Shadow DOM
100
+ response.appendResponseLine(`🔍 Searching for popup matching: "${extensionName}"`);
101
+ const pages = await browser.pages();
102
+ for (let i = 0; i < pages.length; i++) {
103
+ const p = pages[i];
104
+ const url = p.url();
105
+ if (url.startsWith('chrome-extension://') &&
106
+ url.toLowerCase().includes(extensionName.toLowerCase())) {
107
+ context.setSelectedPageIdx(i);
108
+ response.appendResponseLine('✅ Found and selected matching popup window');
109
+ response.appendResponseLine(`📄 Popup URL: ${url}`);
110
+ response.appendResponseLine('');
111
+ response.appendResponseLine('💡 You can now use take_snapshot, click, evaluate_script, etc. on the popup');
556
112
  return;
557
113
  }
558
- response.appendResponseLine('🔧 Looking for open popup window...');
559
- // Get all pages
560
- const pages = await browser.pages();
561
- // Find popup page by URL (contains extension ID)
562
- let popupPage = null;
563
- let popupIndex = -1;
564
- for (let i = 0; i < pages.length; i++) {
565
- const p = pages[i];
566
- const url = p.url();
567
- if (url && url.includes(extensionInfo.id)) {
568
- popupPage = p;
569
- popupIndex = i;
570
- break;
571
- }
572
- }
573
- if (!popupPage) {
574
- // Check for iframe-embedded popup with this extension ID
575
- response.appendResponseLine('🔧 Checking for iframe-embedded popup...');
576
- // Go back to the original page to check for iframes
577
- await page.goBack();
578
- const iframePopups = await page.evaluate((extId) => {
579
- return Array.from(document.querySelectorAll('iframe'))
580
- .filter((iframe) => iframe.src.includes(extId))
581
- .map((iframe) => ({
582
- src: iframe.src,
583
- id: iframe.id,
584
- className: iframe.className,
585
- }));
586
- }, extensionInfo.id);
587
- if (iframePopups.length > 0) {
588
- response.appendResponseLine('');
589
- response.appendResponseLine(`✅ Extension popup found (embedded as iframe): ${extensionInfo.name}`);
590
- response.appendResponseLine(`📄 Popup URL: ${iframePopups[0].src}`);
591
- response.appendResponseLine('');
592
- response.appendResponseLine('💡 This popup is embedded in the current page as an iframe.');
593
- response.appendResponseLine(' You can interact with it using regular page tools:');
594
- response.appendResponseLine(' - take_snapshot (includes iframe content)');
595
- response.appendResponseLine(' - click on elements');
596
- response.appendResponseLine(' - fill forms');
597
- response.appendResponseLine(' - evaluate_script');
598
- return;
599
- }
600
- response.appendResponseLine('❌ Popup window not found.');
601
- response.appendResponseLine('💡 Please manually click the extension icon to open the popup first.');
114
+ }
115
+ // Check for any extension popup if exact match not found
116
+ for (let i = 0; i < pages.length; i++) {
117
+ const p = pages[i];
118
+ const url = p.url();
119
+ if (url.startsWith('chrome-extension://')) {
120
+ context.setSelectedPageIdx(i);
121
+ response.appendResponseLine(`⚠️ No popup matching "${extensionName}" found, but found another extension popup`);
122
+ response.appendResponseLine(`📄 Popup URL: ${url}`);
123
+ response.appendResponseLine('');
124
+ response.appendResponseLine('💡 You can now use take_snapshot, click, evaluate_script, etc. on the popup');
602
125
  return;
603
126
  }
604
- // Select the popup page
605
- context.setSelectedPageIdx(popupIndex);
606
- response.appendResponseLine('');
607
- response.appendResponseLine(`✅ Popup window selected: ${extensionInfo.name}`);
608
- response.appendResponseLine(`📄 Popup URL: ${popupPage.url()}`);
609
- response.appendResponseLine('');
610
- response.appendResponseLine('💡 You can now use take_snapshot, click, evaluate_script, etc. on the popup');
611
- }
612
- catch (error) {
613
- response.appendResponseLine(`❌ Error: ${error instanceof Error ? error.message : String(error)}`);
614
127
  }
128
+ response.appendResponseLine(`❌ No extension popup found matching: "${extensionName}"`);
129
+ response.appendResponseLine('💡 Please manually click the extension icon to open the popup first.');
615
130
  });
616
131
  },
617
132
  });
@@ -640,78 +155,6 @@ export const closeExtensionPopup = defineTool({
640
155
  }
641
156
  },
642
157
  });
643
- export const inspectServiceWorker = defineTool({
644
- name: 'inspect_service_worker',
645
- description: `Open DevTools for an extension's service worker to debug background scripts.`,
646
- annotations: {
647
- category: ToolCategories.EXTENSION_DEVELOPMENT,
648
- readOnlyHint: false,
649
- },
650
- schema: {
651
- extensionName: z
652
- .string()
653
- .describe('The name or partial name of the extension to debug'),
654
- },
655
- handler: async (request, response, context) => {
656
- const page = context.getSelectedPage();
657
- const { extensionName } = request.params;
658
- await context.waitForEventsAfterAction(async () => {
659
- await page.goto('chrome://extensions/', { waitUntil: 'networkidle0' });
660
- const inspectResult = await page.evaluate((searchName) => {
661
- const manager = document.querySelector('extensions-manager');
662
- if (!manager?.shadowRoot)
663
- return null;
664
- const itemList = manager.shadowRoot.querySelector('extensions-item-list');
665
- if (!itemList?.shadowRoot)
666
- return null;
667
- const extensionCards = itemList.shadowRoot.querySelectorAll('extensions-item');
668
- for (const card of Array.from(extensionCards)) {
669
- const shadowRoot = card.shadowRoot;
670
- if (shadowRoot) {
671
- const name = shadowRoot.querySelector('#name')?.textContent?.trim() || '';
672
- if (name.toLowerCase().includes(searchName.toLowerCase())) {
673
- // Look for service worker link
674
- const serviceWorkerLink = shadowRoot.querySelector('a[href*="service_worker"]');
675
- if (serviceWorkerLink) {
676
- serviceWorkerLink.click();
677
- return {
678
- success: true,
679
- extensionName: name,
680
- type: 'service_worker',
681
- };
682
- }
683
- // Look for background page link
684
- const backgroundLink = shadowRoot.querySelector('a[href*="background"]');
685
- if (backgroundLink) {
686
- backgroundLink.click();
687
- return {
688
- success: true,
689
- extensionName: name,
690
- type: 'background_page',
691
- };
692
- }
693
- return {
694
- success: false,
695
- reason: 'No service worker or background page found',
696
- };
697
- }
698
- }
699
- }
700
- return { success: false, reason: 'Extension not found' };
701
- }, extensionName);
702
- if (!inspectResult) {
703
- response.appendResponseLine('❌ Failed to find extension');
704
- return;
705
- }
706
- if (inspectResult.success) {
707
- response.appendResponseLine(`✅ Opened DevTools for ${inspectResult.type} of: ${inspectResult.extensionName}`);
708
- }
709
- else {
710
- response.appendResponseLine(`❌ Failed: ${inspectResult.reason}`);
711
- }
712
- });
713
- },
714
- });
715
158
  // Import iframe popup tools
716
159
  import * as iframePopupTools from './iframe-popup-tools.js';
717
160
  export const inspectIframePopup = defineTool({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-devtools-mcp-for-extension",
3
- "version": "0.18.10",
3
+ "version": "0.19.0",
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",