@mcp-b/chrome-devtools-mcp 2.3.0 → 2.3.1-beta.20260528050333

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.
Files changed (67) hide show
  1. package/package.json +1 -1
  2. package/build/src/DevToolsConnectionAdapter.js +0 -70
  3. package/build/src/DevtoolsUtils.js +0 -290
  4. package/build/src/McpContext.js +0 -687
  5. package/build/src/McpPage.js +0 -95
  6. package/build/src/McpResponse.js +0 -588
  7. package/build/src/Mutex.js +0 -37
  8. package/build/src/PageCollector.js +0 -308
  9. package/build/src/SlimMcpResponse.js +0 -18
  10. package/build/src/WaitForHelper.js +0 -135
  11. package/build/src/bin/chrome-devtools-cli-options.js +0 -651
  12. package/build/src/bin/chrome-devtools-mcp-cli-options.js +0 -317
  13. package/build/src/bin/chrome-devtools-mcp-main.js +0 -35
  14. package/build/src/bin/chrome-devtools-mcp.js +0 -21
  15. package/build/src/bin/chrome-devtools.js +0 -185
  16. package/build/src/bin/cliDefinitions.js +0 -615
  17. package/build/src/browser.js +0 -198
  18. package/build/src/daemon/client.js +0 -152
  19. package/build/src/daemon/daemon.js +0 -206
  20. package/build/src/daemon/types.js +0 -6
  21. package/build/src/daemon/utils.js +0 -108
  22. package/build/src/formatters/ConsoleFormatter.js +0 -234
  23. package/build/src/formatters/IssueFormatter.js +0 -192
  24. package/build/src/formatters/NetworkFormatter.js +0 -215
  25. package/build/src/formatters/SnapshotFormatter.js +0 -131
  26. package/build/src/index.js +0 -202
  27. package/build/src/issue-descriptions.js +0 -39
  28. package/build/src/logger.js +0 -36
  29. package/build/src/polyfill.js +0 -7
  30. package/build/src/telemetry/ClearcutLogger.js +0 -102
  31. package/build/src/telemetry/WatchdogClient.js +0 -60
  32. package/build/src/telemetry/flagUtils.js +0 -45
  33. package/build/src/telemetry/metricUtils.js +0 -14
  34. package/build/src/telemetry/persistence.js +0 -53
  35. package/build/src/telemetry/types.js +0 -33
  36. package/build/src/telemetry/watchdog/ClearcutSender.js +0 -203
  37. package/build/src/telemetry/watchdog/main.js +0 -127
  38. package/build/src/third_party/devtools-formatter-worker.js +0 -7
  39. package/build/src/third_party/index.js +0 -26
  40. package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +0 -54183
  41. package/build/src/tools/ToolDefinition.js +0 -72
  42. package/build/src/tools/categories.js +0 -24
  43. package/build/src/tools/console.js +0 -85
  44. package/build/src/tools/emulation.js +0 -55
  45. package/build/src/tools/extensions.js +0 -96
  46. package/build/src/tools/input.js +0 -368
  47. package/build/src/tools/lighthouse.js +0 -123
  48. package/build/src/tools/memory.js +0 -28
  49. package/build/src/tools/network.js +0 -120
  50. package/build/src/tools/pages.js +0 -319
  51. package/build/src/tools/performance.js +0 -190
  52. package/build/src/tools/screencast.js +0 -79
  53. package/build/src/tools/screenshot.js +0 -84
  54. package/build/src/tools/script.js +0 -119
  55. package/build/src/tools/slim/tools.js +0 -81
  56. package/build/src/tools/snapshot.js +0 -56
  57. package/build/src/tools/tools.js +0 -52
  58. package/build/src/tools/webmcp.js +0 -416
  59. package/build/src/trace-processing/parse.js +0 -84
  60. package/build/src/types.js +0 -6
  61. package/build/src/utils/ExtensionRegistry.js +0 -35
  62. package/build/src/utils/files.js +0 -19
  63. package/build/src/utils/keyboard.js +0 -296
  64. package/build/src/utils/pagination.js +0 -49
  65. package/build/src/utils/string.js +0 -36
  66. package/build/src/utils/types.js +0 -6
  67. package/build/src/version.js +0 -9
@@ -1,37 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Google Inc.
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- export class Mutex {
7
- static Guard = class Guard {
8
- #mutex;
9
- constructor(mutex) {
10
- this.#mutex = mutex;
11
- }
12
- dispose() {
13
- return this.#mutex.release();
14
- }
15
- };
16
- #locked = false;
17
- #acquirers = [];
18
- // This is FIFO.
19
- async acquire() {
20
- if (!this.#locked) {
21
- this.#locked = true;
22
- return new Mutex.Guard(this);
23
- }
24
- const { resolve, promise } = Promise.withResolvers();
25
- this.#acquirers.push(resolve);
26
- await promise;
27
- return new Mutex.Guard(this);
28
- }
29
- release() {
30
- const resolve = this.#acquirers.shift();
31
- if (!resolve) {
32
- this.#locked = false;
33
- return;
34
- }
35
- resolve();
36
- }
37
- }
@@ -1,308 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- import { FakeIssuesManager } from './DevtoolsUtils.js';
7
- import { logger } from './logger.js';
8
- import { DevTools } from './third_party/index.js';
9
- export class UncaughtError {
10
- details;
11
- targetId;
12
- constructor(details, targetId) {
13
- this.details = details;
14
- this.targetId = targetId;
15
- }
16
- }
17
- function createIdGenerator() {
18
- let i = 1;
19
- return () => {
20
- if (i === Number.MAX_SAFE_INTEGER) {
21
- i = 0;
22
- }
23
- return i++;
24
- };
25
- }
26
- export const stableIdSymbol = Symbol('stableIdSymbol');
27
- export class PageCollector {
28
- #browser;
29
- #listenersInitializer;
30
- #listeners = new WeakMap();
31
- maxNavigationSaved = 3;
32
- /**
33
- * This maps a Page to a list of navigations with a sub-list
34
- * of all collected resources.
35
- * The newer navigations come first.
36
- */
37
- storage = new WeakMap();
38
- constructor(browser, listeners) {
39
- this.#browser = browser;
40
- this.#listenersInitializer = listeners;
41
- }
42
- async init(pages) {
43
- for (const page of pages) {
44
- this.addPage(page);
45
- }
46
- this.#browser.on('targetcreated', this.#onTargetCreated);
47
- this.#browser.on('targetdestroyed', this.#onTargetDestroyed);
48
- }
49
- dispose() {
50
- this.#browser.off('targetcreated', this.#onTargetCreated);
51
- this.#browser.off('targetdestroyed', this.#onTargetDestroyed);
52
- }
53
- #onTargetCreated = async (target) => {
54
- try {
55
- const page = await target.page();
56
- if (!page) {
57
- return;
58
- }
59
- this.addPage(page);
60
- }
61
- catch (err) {
62
- logger('Error getting a page for a target onTargetCreated', err);
63
- }
64
- };
65
- #onTargetDestroyed = async (target) => {
66
- try {
67
- const page = await target.page();
68
- if (!page) {
69
- return;
70
- }
71
- this.cleanupPageDestroyed(page);
72
- }
73
- catch (err) {
74
- logger('Error getting a page for a target onTargetDestroyed', err);
75
- }
76
- };
77
- addPage(page) {
78
- this.#initializePage(page);
79
- }
80
- #initializePage(page) {
81
- if (this.storage.has(page)) {
82
- return;
83
- }
84
- const idGenerator = createIdGenerator();
85
- const storedLists = [[]];
86
- this.storage.set(page, storedLists);
87
- const listeners = this.#listenersInitializer((value) => {
88
- const withId = value;
89
- withId[stableIdSymbol] = idGenerator();
90
- const navigations = this.storage.get(page) ?? [[]];
91
- navigations[0].push(withId);
92
- });
93
- listeners['framenavigated'] = (frame) => {
94
- // Only split the storage on main frame navigation
95
- if (frame !== page.mainFrame()) {
96
- return;
97
- }
98
- this.splitAfterNavigation(page);
99
- };
100
- for (const [name, listener] of Object.entries(listeners)) {
101
- page.on(name, listener);
102
- }
103
- this.#listeners.set(page, listeners);
104
- }
105
- splitAfterNavigation(page) {
106
- const navigations = this.storage.get(page);
107
- if (!navigations) {
108
- return;
109
- }
110
- // Add the latest navigation first
111
- navigations.unshift([]);
112
- navigations.splice(this.maxNavigationSaved);
113
- }
114
- cleanupPageDestroyed(page) {
115
- const listeners = this.#listeners.get(page);
116
- if (listeners) {
117
- for (const [name, listener] of Object.entries(listeners)) {
118
- page.off(name, listener);
119
- }
120
- }
121
- this.storage.delete(page);
122
- }
123
- getData(page, includePreservedData) {
124
- const navigations = this.storage.get(page);
125
- if (!navigations) {
126
- return [];
127
- }
128
- if (!includePreservedData) {
129
- return navigations[0];
130
- }
131
- const data = [];
132
- for (let index = this.maxNavigationSaved; index >= 0; index--) {
133
- if (navigations[index]) {
134
- data.push(...navigations[index]);
135
- }
136
- }
137
- return data;
138
- }
139
- getIdForResource(resource) {
140
- return resource[stableIdSymbol] ?? -1;
141
- }
142
- getById(page, stableId) {
143
- const navigations = this.storage.get(page);
144
- if (!navigations) {
145
- throw new Error('No requests found for selected page');
146
- }
147
- const item = this.find(page, (item) => item[stableIdSymbol] === stableId);
148
- if (item) {
149
- return item;
150
- }
151
- throw new Error('Request not found for selected page');
152
- }
153
- find(page, filter) {
154
- const navigations = this.storage.get(page);
155
- if (!navigations) {
156
- return;
157
- }
158
- for (const navigation of navigations) {
159
- const item = navigation.find(filter);
160
- if (item) {
161
- return item;
162
- }
163
- }
164
- return;
165
- }
166
- }
167
- export class ConsoleCollector extends PageCollector {
168
- #subscribedPages = new WeakMap();
169
- addPage(page) {
170
- super.addPage(page);
171
- if (!this.#subscribedPages.has(page)) {
172
- const subscriber = new PageEventSubscriber(page);
173
- this.#subscribedPages.set(page, subscriber);
174
- void subscriber.subscribe();
175
- }
176
- }
177
- cleanupPageDestroyed(page) {
178
- super.cleanupPageDestroyed(page);
179
- this.#subscribedPages.get(page)?.unsubscribe();
180
- this.#subscribedPages.delete(page);
181
- }
182
- }
183
- class PageEventSubscriber {
184
- #issueManager = new FakeIssuesManager();
185
- #issueAggregator = new DevTools.IssueAggregator(this.#issueManager);
186
- #seenKeys = new Set();
187
- #seenIssues = new Set();
188
- #page;
189
- #session;
190
- #targetId;
191
- constructor(page) {
192
- this.#page = page;
193
- // @ts-expect-error use existing CDP client (internal Puppeteer API).
194
- this.#session = this.#page._client();
195
- // @ts-expect-error use internal Puppeteer API to get target ID
196
- this.#targetId = this.#session.target()._targetId;
197
- }
198
- #resetIssueAggregator() {
199
- this.#issueManager = new FakeIssuesManager();
200
- if (this.#issueAggregator) {
201
- this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedissue);
202
- }
203
- this.#issueAggregator = new DevTools.IssueAggregator(this.#issueManager);
204
- this.#issueAggregator.addEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedissue);
205
- }
206
- async subscribe() {
207
- this.#resetIssueAggregator();
208
- this.#page.on('framenavigated', this.#onFrameNavigated);
209
- this.#session.on('Audits.issueAdded', this.#onIssueAdded);
210
- this.#session.on('Runtime.exceptionThrown', this.#onExceptionThrown);
211
- try {
212
- await this.#session.send('Audits.enable');
213
- }
214
- catch (error) {
215
- logger('Error subscribing to issues', error);
216
- }
217
- }
218
- unsubscribe() {
219
- this.#seenKeys.clear();
220
- this.#seenIssues.clear();
221
- this.#page.off('framenavigated', this.#onFrameNavigated);
222
- this.#session.off('Audits.issueAdded', this.#onIssueAdded);
223
- this.#session.off('Runtime.exceptionThrown', this.#onExceptionThrown);
224
- if (this.#issueAggregator) {
225
- this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedissue);
226
- }
227
- void this.#session.send('Audits.disable').catch(() => {
228
- // might fail.
229
- });
230
- }
231
- #onAggregatedissue = (event) => {
232
- if (this.#seenIssues.has(event.data)) {
233
- return;
234
- }
235
- this.#seenIssues.add(event.data);
236
- this.#page.emit('issue', event.data);
237
- };
238
- #onExceptionThrown = (event) => {
239
- this.#page.emit('uncaughtError', new UncaughtError(event.exceptionDetails, this.#targetId));
240
- };
241
- // On navigation, we reset issue aggregation.
242
- #onFrameNavigated = (frame) => {
243
- // Only split the storage on main frame navigation
244
- if (frame !== frame.page().mainFrame()) {
245
- return;
246
- }
247
- this.#seenKeys.clear();
248
- this.#seenIssues.clear();
249
- this.#resetIssueAggregator();
250
- };
251
- #onIssueAdded = (data) => {
252
- try {
253
- const inspectorIssue = data.issue;
254
- const issue = DevTools.createIssuesFromProtocolIssue(null,
255
- // @ts-expect-error Protocol types diverge.
256
- inspectorIssue)[0];
257
- if (!issue) {
258
- logger('No issue mapping for for the issue: ', inspectorIssue.code);
259
- return;
260
- }
261
- const primaryKey = issue.primaryKey();
262
- if (this.#seenKeys.has(primaryKey)) {
263
- return;
264
- }
265
- this.#seenKeys.add(primaryKey);
266
- this.#issueManager.dispatchEventToListeners("IssueAdded" /* DevTools.IssuesManagerEvents.ISSUE_ADDED */, {
267
- issue,
268
- // @ts-expect-error We don't care that issues model is null
269
- issuesModel: null,
270
- });
271
- }
272
- catch (error) {
273
- logger('Error creating a new issue', error);
274
- }
275
- };
276
- }
277
- export class NetworkCollector extends PageCollector {
278
- constructor(browser, listeners = (collect) => {
279
- return {
280
- request: (req) => {
281
- collect(req);
282
- },
283
- };
284
- }) {
285
- super(browser, listeners);
286
- }
287
- splitAfterNavigation(page) {
288
- const navigations = this.storage.get(page) ?? [];
289
- if (!navigations) {
290
- return;
291
- }
292
- const requests = navigations[0];
293
- const lastRequestIdx = requests.findLastIndex((request) => {
294
- return request.frame() === page.mainFrame() ? request.isNavigationRequest() : false;
295
- });
296
- // Keep all requests since the last navigation request including that
297
- // navigation request itself.
298
- // Keep the reference
299
- if (lastRequestIdx !== -1) {
300
- const fromCurrentNavigation = requests.splice(lastRequestIdx);
301
- navigations.unshift(fromCurrentNavigation);
302
- }
303
- else {
304
- navigations.unshift([]);
305
- }
306
- navigations.splice(this.maxNavigationSaved);
307
- }
308
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2026 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- import { McpResponse } from './McpResponse.js';
7
- export class SlimMcpResponse extends McpResponse {
8
- async handle(_toolName, _context) {
9
- const text = {
10
- type: 'text',
11
- text: this.responseLines.join('\n'),
12
- };
13
- return {
14
- content: [text],
15
- structuredContent: text,
16
- };
17
- }
18
- }
@@ -1,135 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2025 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- import { logger } from './logger.js';
7
- export class WaitForHelper {
8
- #abortController = new AbortController();
9
- #page;
10
- #stableDomTimeout;
11
- #stableDomFor;
12
- #expectNavigationIn;
13
- #navigationTimeout;
14
- constructor(page, cpuTimeoutMultiplier, networkTimeoutMultiplier) {
15
- this.#stableDomTimeout = 3000 * cpuTimeoutMultiplier;
16
- this.#stableDomFor = 100 * cpuTimeoutMultiplier;
17
- this.#expectNavigationIn = 100 * cpuTimeoutMultiplier;
18
- this.#navigationTimeout = 3000 * networkTimeoutMultiplier;
19
- this.#page = page;
20
- }
21
- /**
22
- * A wrapper that executes a action and waits for
23
- * a potential navigation, after which it waits
24
- * for the DOM to be stable before returning.
25
- */
26
- async waitForStableDom() {
27
- const stableDomObserver = await this.#page.evaluateHandle((timeout) => {
28
- let timeoutId;
29
- function callback() {
30
- clearTimeout(timeoutId);
31
- timeoutId = setTimeout(() => {
32
- domObserver.resolver.resolve();
33
- domObserver.observer.disconnect();
34
- }, timeout);
35
- }
36
- const domObserver = {
37
- resolver: Promise.withResolvers(),
38
- observer: new MutationObserver(callback),
39
- };
40
- // It's possible that the DOM is not gonna change so we
41
- // need to start the timeout initially.
42
- callback();
43
- domObserver.observer.observe(document.body, {
44
- childList: true,
45
- subtree: true,
46
- attributes: true,
47
- });
48
- return domObserver;
49
- }, this.#stableDomFor);
50
- this.#abortController.signal.addEventListener('abort', async () => {
51
- try {
52
- await stableDomObserver.evaluate((observer) => {
53
- observer.observer.disconnect();
54
- observer.resolver.resolve();
55
- });
56
- await stableDomObserver.dispose();
57
- }
58
- catch {
59
- // Ignored cleanup errors
60
- }
61
- });
62
- return Promise.race([
63
- stableDomObserver.evaluate(async (observer) => {
64
- return await observer.resolver.promise;
65
- }),
66
- this.timeout(this.#stableDomTimeout).then(() => {
67
- throw new Error('Timeout');
68
- }),
69
- ]);
70
- }
71
- async waitForNavigationStarted() {
72
- // Currently Puppeteer does not have API
73
- // For when a navigation is about to start
74
- const navigationStartedPromise = new Promise((resolve) => {
75
- const listener = (event) => {
76
- if (['historySameDocument', 'historyDifferentDocument', 'sameDocument'].includes(event.navigationType)) {
77
- resolve(false);
78
- return;
79
- }
80
- resolve(true);
81
- };
82
- this.#page._client().on('Page.frameStartedNavigating', listener);
83
- this.#abortController.signal.addEventListener('abort', () => {
84
- resolve(false);
85
- this.#page._client().off('Page.frameStartedNavigating', listener);
86
- });
87
- });
88
- return await Promise.race([
89
- navigationStartedPromise,
90
- this.timeout(this.#expectNavigationIn).then(() => false),
91
- ]);
92
- }
93
- timeout(time) {
94
- return new Promise((res) => {
95
- const id = setTimeout(res, time);
96
- this.#abortController.signal.addEventListener('abort', () => {
97
- res();
98
- clearTimeout(id);
99
- });
100
- });
101
- }
102
- async waitForEventsAfterAction(action, options) {
103
- const navigationFinished = this.waitForNavigationStarted()
104
- .then((navigationStated) => {
105
- if (navigationStated) {
106
- return this.#page.waitForNavigation({
107
- timeout: options?.timeout ?? this.#navigationTimeout,
108
- signal: this.#abortController.signal,
109
- });
110
- }
111
- return;
112
- })
113
- .catch((error) => logger(error));
114
- try {
115
- await action();
116
- }
117
- catch (error) {
118
- // Clear up pending promises
119
- this.#abortController.abort();
120
- throw error;
121
- }
122
- try {
123
- await navigationFinished;
124
- // Wait for stable dom after navigation so we execute in
125
- // the correct context
126
- await this.waitForStableDom();
127
- }
128
- catch (error) {
129
- logger(error);
130
- }
131
- finally {
132
- this.#abortController.abort();
133
- }
134
- }
135
- }