chrome-devtools-mcp 0.8.1 → 0.10.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.
Files changed (118) hide show
  1. package/README.md +69 -9
  2. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Console.js +1 -8
  3. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Gzip.js +8 -6
  4. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ParsedURL.js +10 -20
  5. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/SegmentedRange.js +1 -2
  6. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Settings.js +4 -1
  7. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/StringOutputStream.js +1 -4
  8. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Worker.js +10 -2
  9. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/AidaClient.js +19 -0
  10. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/DispatchHttpRequestClient.js +54 -0
  11. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/GdpClient.js +6 -51
  12. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHost.js +2 -2
  13. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHostAPI.js +32 -29
  14. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +15 -6
  15. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/host.js +2 -1
  16. package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/ArrayUtilities.js +1 -1
  17. package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/CDPConnection.js +17 -0
  18. package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/ConnectionTransport.js +12 -0
  19. package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +81 -213
  20. package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/protocol_client.js +3 -8
  21. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/AnimationModel.js +1 -2
  22. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +45 -10
  23. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSModel.js +1 -1
  24. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSProperty.js +3 -6
  25. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParserMatchers.js +14 -10
  26. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSRule.js +34 -6
  27. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSStyleDeclaration.js +4 -4
  28. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ChildTargetManager.js +8 -33
  29. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Connections.js +10 -47
  30. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DOMModel.js +4 -0
  31. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +3 -3
  32. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/EnhancedTracesParser.js +17 -3
  33. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +371 -53
  34. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +5 -0
  35. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/PreloadingModel.js +56 -13
  36. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +133 -10
  37. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ResourceTreeModel.js +1 -1
  38. package/build/node_modules/chrome-devtools-frontend/front_end/{models/source_map_scopes → core/sdk}/ScopeTreeCache.js +9 -5
  39. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +50 -14
  40. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapManager.js +8 -2
  41. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapScopesInfo.js +131 -8
  42. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TargetManager.js +0 -21
  43. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TraceObject.js +9 -6
  44. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/sdk-meta.js +8 -1
  45. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/sdk.js +2 -1
  46. package/build/node_modules/chrome-devtools-frontend/front_end/generated/ARIAProperties.js +1301 -174
  47. package/build/node_modules/chrome-devtools-frontend/front_end/generated/Deprecation.js +7 -0
  48. package/build/node_modules/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +9 -45
  49. package/build/node_modules/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +74 -19
  50. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js +50 -34
  51. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js +46 -45
  52. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AICallTree.js +2 -3
  53. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIContext.js +10 -25
  54. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +45 -2
  55. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DebuggerWorkspaceBinding.js +1 -1
  56. package/build/node_modules/chrome-devtools-frontend/front_end/models/cpu_profile/ProfileTreeModel.js +6 -7
  57. package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/FormatterWorkerPool.js +14 -0
  58. package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/NamesResolver.js +5 -11
  59. package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/source_map_scopes.js +1 -2
  60. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceModel.js +1 -1
  61. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/Trie.js +8 -0
  62. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/ModelImpl.js +6 -3
  63. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/TraceTree.js +10 -3
  64. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.js +4 -1
  65. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/NetworkRequestsHandler.js +12 -3
  66. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/UserTimingsHandler.js +1 -1
  67. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/CLSCulprits.js +2 -1
  68. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Cache.js +2 -1
  69. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DOMSize.js +2 -1
  70. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DocumentLatency.js +2 -1
  71. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DuplicatedJavaScript.js +2 -1
  72. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/FontDisplay.js +2 -1
  73. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ForcedReflow.js +3 -2
  74. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/INPBreakdown.js +2 -1
  75. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ImageDelivery.js +2 -1
  76. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +2 -1
  77. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPDiscovery.js +2 -1
  78. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LegacyJavaScript.js +2 -1
  79. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ModernHTTP.js +2 -1
  80. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +2 -1
  81. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/RenderBlocking.js +2 -1
  82. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/SlowCSSSelector.js +2 -1
  83. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ThirdParties.js +2 -1
  84. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/Viewport.js +2 -1
  85. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +3 -0
  86. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver/SourceMapsResolver.js +1 -1
  87. package/build/node_modules/chrome-devtools-frontend/mcp/mcp.js +14 -0
  88. package/build/src/DevToolsConnectionAdapter.js +33 -0
  89. package/build/src/DevtoolsUtils.js +44 -0
  90. package/build/src/McpContext.js +182 -33
  91. package/build/src/McpResponse.js +169 -57
  92. package/build/src/PageCollector.js +123 -27
  93. package/build/src/WaitForHelper.js +5 -0
  94. package/build/src/browser.js +24 -12
  95. package/build/src/cli.js +87 -6
  96. package/build/src/formatters/consoleFormatter.js +29 -62
  97. package/build/src/formatters/networkFormatter.js +5 -6
  98. package/build/src/formatters/snapshotFormatter.js +28 -11
  99. package/build/src/logger.js +1 -1
  100. package/build/src/main.js +24 -6
  101. package/build/src/polyfill.js +2 -2
  102. package/build/src/third_party/THIRD_PARTY_NOTICES +1413 -0
  103. package/build/src/third_party/index.js +82791 -0
  104. package/build/src/tools/ToolDefinition.js +2 -2
  105. package/build/src/tools/categories.js +17 -9
  106. package/build/src/tools/console.js +71 -6
  107. package/build/src/tools/emulation.js +40 -48
  108. package/build/src/tools/input.js +57 -27
  109. package/build/src/tools/network.js +43 -13
  110. package/build/src/tools/pages.js +75 -49
  111. package/build/src/tools/performance.js +13 -10
  112. package/build/src/tools/screenshot.js +10 -9
  113. package/build/src/tools/script.js +29 -15
  114. package/build/src/tools/snapshot.js +27 -23
  115. package/build/src/trace-processing/parse.js +6 -16
  116. package/build/src/utils/keyboard.js +291 -0
  117. package/build/src/utils/types.js +6 -0
  118. package/package.json +16 -12
@@ -3,21 +3,35 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
+ function createIdGenerator() {
7
+ let i = 1;
8
+ return () => {
9
+ if (i === Number.MAX_SAFE_INTEGER) {
10
+ i = 0;
11
+ }
12
+ return i++;
13
+ };
14
+ }
15
+ export const stableIdSymbol = Symbol('stableIdSymbol');
6
16
  export class PageCollector {
7
17
  #browser;
8
- #initializer;
18
+ #listenersInitializer;
19
+ #listeners = new WeakMap();
20
+ #maxNavigationSaved = 3;
21
+ #includeAllPages;
9
22
  /**
10
- * The Array in this map should only be set once
11
- * As we use the reference to it.
12
- * Use methods that manipulate the array in place.
23
+ * This maps a Page to a list of navigations with a sub-list
24
+ * of all collected resources.
25
+ * The newer navigations come first.
13
26
  */
14
27
  storage = new WeakMap();
15
- constructor(browser, initializer) {
28
+ constructor(browser, listeners, includeAllPages) {
16
29
  this.#browser = browser;
17
- this.#initializer = initializer;
30
+ this.#listenersInitializer = listeners;
31
+ this.#includeAllPages = includeAllPages;
18
32
  }
19
33
  async init() {
20
- const pages = await this.#browser.pages();
34
+ const pages = await this.#browser.pages(this.#includeAllPages);
21
35
  for (const page of pages) {
22
36
  this.#initializePage(page);
23
37
  }
@@ -28,6 +42,13 @@ export class PageCollector {
28
42
  }
29
43
  this.#initializePage(page);
30
44
  });
45
+ this.#browser.on('targetdestroyed', async (target) => {
46
+ const page = await target.page();
47
+ if (!page) {
48
+ return;
49
+ }
50
+ this.#cleanupPageDestroyed(page);
51
+ });
31
52
  }
32
53
  addPage(page) {
33
54
  this.#initializePage(page);
@@ -36,36 +57,105 @@ export class PageCollector {
36
57
  if (this.storage.has(page)) {
37
58
  return;
38
59
  }
39
- const stored = [];
40
- this.storage.set(page, stored);
41
- page.on('framenavigated', frame => {
42
- // Only reset the storage on main frame navigation
60
+ const idGenerator = createIdGenerator();
61
+ const storedLists = [[]];
62
+ this.storage.set(page, storedLists);
63
+ const listeners = this.#listenersInitializer(value => {
64
+ const withId = value;
65
+ withId[stableIdSymbol] = idGenerator();
66
+ const navigations = this.storage.get(page) ?? [[]];
67
+ navigations[0].push(withId);
68
+ });
69
+ listeners['framenavigated'] = (frame) => {
70
+ // Only split the storage on main frame navigation
43
71
  if (frame !== page.mainFrame()) {
44
72
  return;
45
73
  }
46
- this.cleanup(page);
47
- });
48
- this.#initializer(page, value => {
49
- stored.push(value);
50
- });
74
+ this.splitAfterNavigation(page);
75
+ };
76
+ for (const [name, listener] of Object.entries(listeners)) {
77
+ page.on(name, listener);
78
+ }
79
+ this.#listeners.set(page, listeners);
51
80
  }
52
- cleanup(page) {
53
- const collection = this.storage.get(page);
54
- if (collection) {
55
- // Keep the reference alive
56
- collection.length = 0;
81
+ splitAfterNavigation(page) {
82
+ const navigations = this.storage.get(page);
83
+ if (!navigations) {
84
+ return;
57
85
  }
86
+ // Add the latest navigation first
87
+ navigations.unshift([]);
88
+ navigations.splice(this.#maxNavigationSaved);
58
89
  }
59
- getData(page) {
60
- return this.storage.get(page) ?? [];
90
+ #cleanupPageDestroyed(page) {
91
+ const listeners = this.#listeners.get(page);
92
+ if (listeners) {
93
+ for (const [name, listener] of Object.entries(listeners)) {
94
+ page.off(name, listener);
95
+ }
96
+ }
97
+ this.storage.delete(page);
98
+ }
99
+ getData(page, includePreservedData) {
100
+ const navigations = this.storage.get(page);
101
+ if (!navigations) {
102
+ return [];
103
+ }
104
+ if (!includePreservedData) {
105
+ return navigations[0];
106
+ }
107
+ const data = [];
108
+ for (let index = this.#maxNavigationSaved; index >= 0; index--) {
109
+ if (navigations[index]) {
110
+ data.push(...navigations[index]);
111
+ }
112
+ }
113
+ return data;
114
+ }
115
+ getIdForResource(resource) {
116
+ return resource[stableIdSymbol] ?? -1;
117
+ }
118
+ getById(page, stableId) {
119
+ const navigations = this.storage.get(page);
120
+ if (!navigations) {
121
+ throw new Error('No requests found for selected page');
122
+ }
123
+ const item = this.find(page, item => item[stableIdSymbol] === stableId);
124
+ if (item) {
125
+ return item;
126
+ }
127
+ throw new Error('Request not found for selected page');
128
+ }
129
+ find(page, filter) {
130
+ const navigations = this.storage.get(page);
131
+ if (!navigations) {
132
+ return;
133
+ }
134
+ for (const navigation of navigations) {
135
+ const item = navigation.find(filter);
136
+ if (item) {
137
+ return item;
138
+ }
139
+ }
140
+ return;
61
141
  }
62
142
  }
63
143
  export class NetworkCollector extends PageCollector {
64
- cleanup(page) {
65
- const requests = this.storage.get(page) ?? [];
66
- if (!requests) {
144
+ constructor(browser, listeners = collect => {
145
+ return {
146
+ request: req => {
147
+ collect(req);
148
+ },
149
+ };
150
+ }, includeAllPages) {
151
+ super(browser, listeners, includeAllPages);
152
+ }
153
+ splitAfterNavigation(page) {
154
+ const navigations = this.storage.get(page) ?? [];
155
+ if (!navigations) {
67
156
  return;
68
157
  }
158
+ const requests = navigations[0];
69
159
  const lastRequestIdx = requests.findLastIndex(request => {
70
160
  return request.frame() === page.mainFrame()
71
161
  ? request.isNavigationRequest()
@@ -74,6 +164,12 @@ export class NetworkCollector extends PageCollector {
74
164
  // Keep all requests since the last navigation request including that
75
165
  // navigation request itself.
76
166
  // Keep the reference
77
- requests.splice(0, Math.max(lastRequestIdx, 0));
167
+ if (lastRequestIdx !== -1) {
168
+ const fromCurrentNavigation = requests.splice(lastRequestIdx);
169
+ navigations.unshift(fromCurrentNavigation);
170
+ }
171
+ else {
172
+ navigations.unshift([]);
173
+ }
78
174
  }
79
175
  }
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
1
6
  import { logger } from './logger.js';
2
7
  export class WaitForHelper {
3
8
  #abortController = new AbortController();
@@ -6,17 +6,15 @@
6
6
  import fs from 'node:fs';
7
7
  import os from 'node:os';
8
8
  import path from 'node:path';
9
- import puppeteer from 'puppeteer-core';
9
+ import { logger } from './logger.js';
10
+ import { puppeteer } from './third_party/index.js';
10
11
  let browser;
11
- function makeTargetFilter(devtools) {
12
+ function makeTargetFilter() {
12
13
  const ignoredPrefixes = new Set([
13
14
  'chrome://',
14
15
  'chrome-extension://',
15
16
  'chrome-untrusted://',
16
17
  ]);
17
- if (!devtools) {
18
- ignoredPrefixes.add('devtools://');
19
- }
20
18
  return function targetFilter(target) {
21
19
  if (target.url() === 'chrome://newtab/') {
22
20
  return true;
@@ -33,12 +31,26 @@ export async function ensureBrowserConnected(options) {
33
31
  if (browser?.connected) {
34
32
  return browser;
35
33
  }
36
- browser = await puppeteer.connect({
37
- targetFilter: makeTargetFilter(options.devtools),
38
- browserURL: options.browserURL,
34
+ const connectOptions = {
35
+ targetFilter: makeTargetFilter(),
39
36
  defaultViewport: null,
40
- handleDevToolsAsPage: options.devtools,
41
- });
37
+ handleDevToolsAsPage: true,
38
+ };
39
+ if (options.wsEndpoint) {
40
+ connectOptions.browserWSEndpoint = options.wsEndpoint;
41
+ if (options.wsHeaders) {
42
+ connectOptions.headers = options.wsHeaders;
43
+ }
44
+ }
45
+ else if (options.browserURL) {
46
+ connectOptions.browserURL = options.browserURL;
47
+ }
48
+ else {
49
+ throw new Error('Either browserURL or wsEndpoint must be provided');
50
+ }
51
+ logger('Connecting Puppeteer to ', JSON.stringify(connectOptions));
52
+ browser = await puppeteer.connect(connectOptions);
53
+ logger('Connected Puppeteer');
42
54
  return browser;
43
55
  }
44
56
  export async function launch(options) {
@@ -73,7 +85,7 @@ export async function launch(options) {
73
85
  try {
74
86
  const browser = await puppeteer.launch({
75
87
  channel: puppeteerChannel,
76
- targetFilter: makeTargetFilter(options.devtools),
88
+ targetFilter: makeTargetFilter(),
77
89
  executablePath,
78
90
  defaultViewport: null,
79
91
  userDataDir,
@@ -81,7 +93,7 @@ export async function launch(options) {
81
93
  headless,
82
94
  args,
83
95
  acceptInsecureCerts: options.acceptInsecureCerts,
84
- handleDevToolsAsPage: options.devtools,
96
+ handleDevToolsAsPage: true,
85
97
  });
86
98
  if (options.logFile) {
87
99
  // FIXME: we are probably subscribing too late to catch startup logs. We
package/build/src/cli.js CHANGED
@@ -3,13 +3,13 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import yargs from 'yargs';
7
- import { hideBin } from 'yargs/helpers';
6
+ import { yargs, hideBin } from './third_party/index.js';
8
7
  export const cliOptions = {
9
8
  browserUrl: {
10
9
  type: 'string',
11
10
  description: 'Connect to a running Chrome instance using port forwarding. For more details see: https://developer.chrome.com/docs/devtools/remote-debugging/local-server.',
12
11
  alias: 'u',
12
+ conflicts: 'wsEndpoint',
13
13
  coerce: (url) => {
14
14
  if (!url) {
15
15
  return;
@@ -23,6 +23,50 @@ export const cliOptions = {
23
23
  return url;
24
24
  },
25
25
  },
26
+ wsEndpoint: {
27
+ type: 'string',
28
+ description: 'WebSocket endpoint to connect to a running Chrome instance (e.g., ws://127.0.0.1:9222/devtools/browser/<id>). Alternative to --browserUrl.',
29
+ alias: 'w',
30
+ conflicts: 'browserUrl',
31
+ coerce: (url) => {
32
+ if (!url) {
33
+ return;
34
+ }
35
+ try {
36
+ const parsed = new URL(url);
37
+ if (parsed.protocol !== 'ws:' && parsed.protocol !== 'wss:') {
38
+ throw new Error(`Provided wsEndpoint ${url} must use ws:// or wss:// protocol.`);
39
+ }
40
+ return url;
41
+ }
42
+ catch (error) {
43
+ if (error.message.includes('ws://')) {
44
+ throw error;
45
+ }
46
+ throw new Error(`Provided wsEndpoint ${url} is not valid URL.`);
47
+ }
48
+ },
49
+ },
50
+ wsHeaders: {
51
+ type: 'string',
52
+ description: 'Custom headers for WebSocket connection in JSON format (e.g., \'{"Authorization":"Bearer token"}\'). Only works with --wsEndpoint.',
53
+ implies: 'wsEndpoint',
54
+ coerce: (val) => {
55
+ if (!val) {
56
+ return;
57
+ }
58
+ try {
59
+ const parsed = JSON.parse(val);
60
+ if (typeof parsed !== 'object' || Array.isArray(parsed)) {
61
+ throw new Error('Headers must be a JSON object');
62
+ }
63
+ return parsed;
64
+ }
65
+ catch (error) {
66
+ throw new Error(`Invalid JSON for wsHeaders: ${error.message}`);
67
+ }
68
+ },
69
+ },
26
70
  headless: {
27
71
  type: 'boolean',
28
72
  description: 'Whether to run in headless (no UI) mode.',
@@ -31,7 +75,7 @@ export const cliOptions = {
31
75
  executablePath: {
32
76
  type: 'string',
33
77
  description: 'Path to custom Chrome executable.',
34
- conflicts: 'browserUrl',
78
+ conflicts: ['browserUrl', 'wsEndpoint'],
35
79
  alias: 'e',
36
80
  },
37
81
  isolated: {
@@ -43,7 +87,7 @@ export const cliOptions = {
43
87
  type: 'string',
44
88
  description: 'Specify a different Chrome channel that should be used. The default is the stable channel version.',
45
89
  choices: ['stable', 'canary', 'beta', 'dev'],
46
- conflicts: ['browserUrl', 'executablePath'],
90
+ conflicts: ['browserUrl', 'wsEndpoint', 'executablePath'],
47
91
  },
48
92
  logFile: {
49
93
  type: 'string',
@@ -79,10 +123,30 @@ export const cliOptions = {
79
123
  describe: 'Whether to enable automation over DevTools targets',
80
124
  hidden: true,
81
125
  },
126
+ experimentalIncludeAllPages: {
127
+ type: 'boolean',
128
+ describe: 'Whether to include all kinds of pages such as webviews or background pages as pages.',
129
+ hidden: true,
130
+ },
82
131
  chromeArg: {
83
132
  type: 'array',
84
133
  describe: 'Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.',
85
134
  },
135
+ categoryEmulation: {
136
+ type: 'boolean',
137
+ default: true,
138
+ describe: 'Set to false to exclude tools related to emulation.',
139
+ },
140
+ categoryPerformance: {
141
+ type: 'boolean',
142
+ default: true,
143
+ describe: 'Set to false to exclude tools related to performance.',
144
+ },
145
+ categoryNetwork: {
146
+ type: 'boolean',
147
+ default: true,
148
+ describe: 'Set to false to exclude tools related to network.',
149
+ },
86
150
  };
87
151
  export function parseArguments(version, argv = process.argv) {
88
152
  const yargsInstance = yargs(hideBin(argv))
@@ -91,7 +155,10 @@ export function parseArguments(version, argv = process.argv) {
91
155
  .check(args => {
92
156
  // We can't set default in the options else
93
157
  // Yargs will complain
94
- if (!args.channel && !args.browserUrl && !args.executablePath) {
158
+ if (!args.channel &&
159
+ !args.browserUrl &&
160
+ !args.wsEndpoint &&
161
+ !args.executablePath) {
95
162
  args.channel = 'stable';
96
163
  }
97
164
  return true;
@@ -99,7 +166,15 @@ export function parseArguments(version, argv = process.argv) {
99
166
  .example([
100
167
  [
101
168
  '$0 --browserUrl http://127.0.0.1:9222',
102
- 'Connect to an existing browser instance',
169
+ 'Connect to an existing browser instance via HTTP',
170
+ ],
171
+ [
172
+ '$0 --wsEndpoint ws://127.0.0.1:9222/devtools/browser/abc123',
173
+ 'Connect to an existing browser instance via WebSocket',
174
+ ],
175
+ [
176
+ `$0 --wsEndpoint ws://127.0.0.1:9222/devtools/browser/abc123 --wsHeaders '{"Authorization":"Bearer token"}'`,
177
+ 'Connect via WebSocket with custom headers',
103
178
  ],
104
179
  ['$0 --channel beta', 'Use Chrome Beta installed on this system'],
105
180
  ['$0 --channel canary', 'Use Chrome Canary installed on this system'],
@@ -115,6 +190,12 @@ export function parseArguments(version, argv = process.argv) {
115
190
  `$0 --chrome-arg='--no-sandbox' --chrome-arg='--disable-setuid-sandbox'`,
116
191
  'Launch Chrome without sandboxes. Use with caution.',
117
192
  ],
193
+ ['$0 --no-category-emulation', 'Disable tools in the emulation category'],
194
+ [
195
+ '$0 --no-category-performance',
196
+ 'Disable tools in the performance category',
197
+ ],
198
+ ['$0 --no-category-network', 'Disable tools in the network category'],
118
199
  ]);
119
200
  return yargsInstance
120
201
  .wrap(Math.min(120, yargsInstance.terminalWidth()))
@@ -3,71 +3,38 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- const logLevels = {
7
- log: 'Log',
8
- info: 'Info',
9
- warning: 'Warning',
10
- error: 'Error',
11
- exception: 'Exception',
12
- assert: 'Assert',
13
- };
14
- export async function formatConsoleEvent(event) {
15
- // Check if the event object has the .type() method, which is unique to ConsoleMessage
16
- if ('type' in event) {
17
- return await formatConsoleMessage(event);
18
- }
19
- return `Error: ${event.message}`;
6
+ // The short format for a console message, based on a previous format.
7
+ export function formatConsoleEventShort(msg) {
8
+ return `msgid=${msg.consoleMessageStableId} [${msg.type}] ${msg.message} (${msg.args?.length ?? 0} args)`;
20
9
  }
21
- async function formatConsoleMessage(msg) {
22
- const logLevel = logLevels[msg.type()];
23
- const args = msg.args();
24
- if (logLevel === 'Error') {
25
- let message = `${logLevel}> `;
26
- if (msg.text() === 'JSHandle@error') {
27
- const errorHandle = args[0];
28
- message += await errorHandle
29
- .evaluate(error => {
30
- return error.toString();
31
- })
32
- .catch(() => {
33
- return 'Error occurred';
34
- });
35
- void errorHandle.dispose().catch();
36
- const formattedArgs = await formatArgs(args.slice(1));
37
- if (formattedArgs) {
38
- message += ` ${formattedArgs}`;
39
- }
40
- }
41
- else {
42
- message += msg.text();
43
- const formattedArgs = await formatArgs(args);
44
- if (formattedArgs) {
45
- message += ` ${formattedArgs}`;
46
- }
47
- for (const frame of msg.stackTrace()) {
48
- message += '\n' + formatStackFrame(frame);
49
- }
50
- }
51
- return message;
10
+ function getArgs(msg) {
11
+ const args = [...(msg.args ?? [])];
12
+ // If there is no text, the first argument serves as text (see formatMessage).
13
+ if (!msg.message) {
14
+ args.shift();
52
15
  }
53
- const formattedArgs = await formatArgs(args);
54
- const text = msg.text();
55
- return `${logLevel}> ${formatStackFrame(msg.location())}: ${text} ${formattedArgs}`.trim();
16
+ return args;
17
+ }
18
+ // The verbose format for a console message, including all details.
19
+ export function formatConsoleEventVerbose(msg) {
20
+ const result = [
21
+ `ID: ${msg.consoleMessageStableId}`,
22
+ `Message: ${msg.type}> ${msg.message}`,
23
+ formatArgs(msg),
24
+ ].filter(line => !!line);
25
+ return result.join('\n');
56
26
  }
57
- async function formatArgs(args) {
58
- const argValues = await Promise.all(args.map(arg => arg.jsonValue().catch(() => {
59
- // Ignore errors
60
- })));
61
- return argValues
62
- .map(value => {
63
- return typeof value === 'object' ? JSON.stringify(value) : String(value);
64
- })
65
- .join(' ');
27
+ function formatArg(arg) {
28
+ return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
66
29
  }
67
- function formatStackFrame(stackFrame) {
68
- if (!stackFrame?.url) {
69
- return '<unknown>';
30
+ function formatArgs(consoleData) {
31
+ const args = getArgs(consoleData);
32
+ if (!args.length) {
33
+ return '';
34
+ }
35
+ const result = ['### Arguments'];
36
+ for (const [key, arg] of args.entries()) {
37
+ result.push(`Arg #${key}: ${formatArg(arg)}`);
70
38
  }
71
- const filename = stackFrame.url.replace(/^.*\//, '');
72
- return `${filename}:${stackFrame.lineNumber}:${stackFrame.columnNumber}`;
39
+ return result.join('\n');
73
40
  }
@@ -5,8 +5,9 @@
5
5
  */
6
6
  import { isUtf8 } from 'node:buffer';
7
7
  const BODY_CONTEXT_SIZE_LIMIT = 10000;
8
- export function getShortDescriptionForRequest(request) {
9
- return `${request.url()} ${request.method()} ${getStatusFromRequest(request)}`;
8
+ export function getShortDescriptionForRequest(request, id, selectedInDevToolsUI = false) {
9
+ // TODO truncate the URL
10
+ return `reqid=${id} ${request.method()} ${request.url()} ${getStatusFromRequest(request)}${selectedInDevToolsUI ? ` [selected in the DevTools Network panel]` : ''}`;
10
11
  }
11
12
  export function getStatusFromRequest(request) {
12
13
  const httpResponse = request.response();
@@ -47,8 +48,7 @@ export async function getFormattedResponseBody(httpResponse, sizeLimit = BODY_CO
47
48
  return `<binary data>`;
48
49
  }
49
50
  catch {
50
- // buffer() call might fail with CDP exception, in this case we don't print anything in the context
51
- return;
51
+ return `<not available anymore>`;
52
52
  }
53
53
  }
54
54
  export async function getFormattedRequestBody(httpRequest, sizeLimit = BODY_CONTEXT_SIZE_LIMIT) {
@@ -64,8 +64,7 @@ export async function getFormattedRequestBody(httpRequest, sizeLimit = BODY_CONT
64
64
  }
65
65
  }
66
66
  catch {
67
- // fetchPostData() call might fail with CDP exception, in this case we don't print anything in the context
68
- return;
67
+ return `<not available anymore>`;
69
68
  }
70
69
  }
71
70
  return;
@@ -1,20 +1,37 @@
1
- export function formatA11ySnapshot(serializedAXNodeRoot, depth = 0) {
1
+ export function formatSnapshotNode(root, snapshot, depth = 0) {
2
2
  let result = '';
3
- const attributes = getAttributes(serializedAXNodeRoot);
4
- const line = ' '.repeat(depth * 2) + attributes.join(' ') + '\n';
3
+ const attributes = getAttributes(root);
4
+ const line = ' '.repeat(depth * 2) +
5
+ attributes.join(' ') +
6
+ (root.id === snapshot?.selectedElementUid
7
+ ? ' [selected in the DevTools Elements panel]'
8
+ : '') +
9
+ '\n';
5
10
  result += line;
6
- for (const child of serializedAXNodeRoot.children) {
7
- result += formatA11ySnapshot(child, depth + 1);
11
+ for (const child of root.children) {
12
+ result += formatSnapshotNode(child, snapshot, depth + 1);
8
13
  }
9
14
  return result;
10
15
  }
11
16
  function getAttributes(serializedAXNodeRoot) {
12
- const attributes = [
13
- `uid=${serializedAXNodeRoot.id}`,
14
- serializedAXNodeRoot.role,
15
- `"${serializedAXNodeRoot.name || ''}"`, // Corrected: Added quotes around name
16
- ];
17
- const excluded = new Set(['id', 'role', 'name', 'elementHandle', 'children']);
17
+ const attributes = [`uid=${serializedAXNodeRoot.id}`];
18
+ if (serializedAXNodeRoot.role) {
19
+ // To match representation in DevTools.
20
+ attributes.push(serializedAXNodeRoot.role === 'none'
21
+ ? 'ignored'
22
+ : serializedAXNodeRoot.role);
23
+ }
24
+ if (serializedAXNodeRoot.name) {
25
+ attributes.push(`"${serializedAXNodeRoot.name}"`);
26
+ }
27
+ const excluded = new Set([
28
+ 'id',
29
+ 'role',
30
+ 'name',
31
+ 'elementHandle',
32
+ 'children',
33
+ 'backendNodeId',
34
+ ]);
18
35
  const booleanPropertyMap = {
19
36
  disabled: 'disableable',
20
37
  expanded: 'expandable',
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import fs from 'node:fs';
7
- import debug from 'debug';
7
+ import { debug } from './third_party/index.js';
8
8
  const mcpDebugNamespace = 'mcp:log';
9
9
  const namespacesToEnable = [
10
10
  mcpDebugNamespace,