@nimbus21.ai/chrome-devtools-mcp 0.12.3 → 0.17.4

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 (53) hide show
  1. package/README.md +119 -24
  2. package/build/src/DevtoolsUtils.js +174 -42
  3. package/build/src/McpContext.js +127 -29
  4. package/build/src/McpResponse.js +245 -159
  5. package/build/src/PageCollector.js +18 -2
  6. package/build/src/WaitForHelper.js +2 -2
  7. package/build/src/browser.js +8 -13
  8. package/build/src/cli.js +65 -3
  9. package/build/src/formatters/ConsoleFormatter.js +240 -0
  10. package/build/src/formatters/IssueFormatter.js +190 -0
  11. package/build/src/formatters/NetworkFormatter.js +218 -0
  12. package/build/src/formatters/SnapshotFormatter.js +134 -0
  13. package/build/src/logger.js +9 -0
  14. package/build/src/main.js +66 -8
  15. package/build/src/telemetry/ClearcutLogger.js +102 -0
  16. package/build/src/telemetry/WatchdogClient.js +60 -0
  17. package/build/src/telemetry/flagUtils.js +45 -0
  18. package/build/src/telemetry/metricUtils.js +14 -0
  19. package/build/src/telemetry/persistence.js +53 -0
  20. package/build/src/telemetry/types.js +33 -0
  21. package/build/src/telemetry/watchdog/ClearcutSender.js +201 -0
  22. package/build/src/telemetry/watchdog/main.js +127 -0
  23. package/build/src/third_party/THIRD_PARTY_NOTICES +10 -9
  24. package/build/src/third_party/bundled-packages.json +8 -0
  25. package/build/src/third_party/devtools-formatter-worker.js +15449 -0
  26. package/build/src/third_party/index.js +4093 -2367
  27. package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowlistItemType.md +12 -0
  28. package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidHeader.md +12 -0
  29. package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidUrlPattern.md +8 -0
  30. package/build/src/third_party/issue-descriptions/connectionAllowlistItemNotInnerList.md +12 -0
  31. package/build/src/third_party/issue-descriptions/connectionAllowlistMoreThanOneList.md +7 -0
  32. package/build/src/third_party/issue-descriptions/connectionAllowlistReportingEndpointNotToken.md +10 -0
  33. package/build/src/tools/categories.js +2 -0
  34. package/build/src/tools/emulation.js +83 -1
  35. package/build/src/tools/extensions.js +79 -0
  36. package/build/src/tools/input.js +93 -13
  37. package/build/src/tools/network.js +17 -3
  38. package/build/src/tools/pages.js +134 -54
  39. package/build/src/tools/performance.js +68 -27
  40. package/build/src/tools/script.js +2 -2
  41. package/build/src/tools/tools.js +2 -0
  42. package/build/src/utils/ExtensionRegistry.js +35 -0
  43. package/build/src/utils/string.js +36 -0
  44. package/package.json +17 -16
  45. package/build/src/formatters/consoleFormatter.js +0 -121
  46. package/build/src/formatters/networkFormatter.js +0 -77
  47. package/build/src/formatters/snapshotFormatter.js +0 -73
  48. package/build/src/third_party/devtools.js +0 -6
  49. package/build/src/third_party/issue-descriptions/SameSiteInvalidSameParty.md +0 -8
  50. package/build/src/third_party/issue-descriptions/SameSiteSamePartyCrossPartyContextSet.md +0 -10
  51. package/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataHttpNotFound.md +0 -1
  52. package/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataInvalidResponse.md +0 -1
  53. package/build/src/third_party/issue-descriptions/federatedAuthRequestClientMetadataNoResponse.md +0 -1
package/README.md CHANGED
@@ -25,7 +25,7 @@ Chrome DevTools for reliable automation, in-depth debugging, and performance ana
25
25
  DevTools](https://github.com/ChromeDevTools/devtools-frontend) to record
26
26
  traces and extract actionable performance insights.
27
27
  - **Advanced browser debugging**: Analyze network requests, take screenshots and
28
- check the browser console.
28
+ check browser console messages (with source-mapped stack traces).
29
29
  - **Reliable automation**. Uses
30
30
  [puppeteer](https://github.com/puppeteer/puppeteer) to automate actions in
31
31
  Chrome and automatically wait for action results.
@@ -37,6 +37,28 @@ allowing them to inspect, debug, and modify any data in the browser or DevTools.
37
37
  Avoid sharing sensitive or personal information that you don't want to share with
38
38
  MCP clients.
39
39
 
40
+ Performance tools may send trace URLs to the Google CrUX API to fetch real-user
41
+ experience data. This helps provide a holistic performance picture by
42
+ presenting field data alongside lab data. This data is collected by the [Chrome
43
+ User Experience Report (CrUX)](https://developer.chrome.com/docs/crux). To disable
44
+ this, run with the `--no-performance-crux` flag.
45
+
46
+ ## **Usage statistics**
47
+
48
+ Google collects usage statistics (such as tool invocation success rates, latency, and environment information) to improve the reliability and performance of Chrome DevTools MCP.
49
+
50
+ Data collection is **enabled by default**. You can opt-out by passing the `--no-usage-statistics` flag when starting the server:
51
+
52
+ ```json
53
+ "args": ["-y", "chrome-devtools-mcp@latest", "--no-usage-statistics"]
54
+ ```
55
+
56
+ Google handles this data in accordance with the [Google Privacy Policy](https://policies.google.com/privacy).
57
+
58
+ Google's collection of usage statistics for Chrome DevTools MCP is independent from the Chrome browser's usage statistics. Opting out of Chrome metrics does not automatically opt you out of this tool, and vice-versa.
59
+
60
+ Collection is disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set.
61
+
40
62
  ## Requirements
41
63
 
42
64
  - [Node.js](https://nodejs.org/) v20.19 or a newer [latest maintenance LTS](https://github.com/nodejs/Release#release-schedule) version.
@@ -76,7 +98,7 @@ amp mcp add chrome-devtools -- npx chrome-devtools-mcp@latest
76
98
  <details>
77
99
  <summary>Antigravity</summary>
78
100
 
79
- To use the Chrome DevTools MCP server follow the instructions from <a href="https://antigravity.google/docs/mcp">Antigravity's docs<a/> to install a custom MCP server. Add the following config to the MCP servers config:
101
+ To use the Chrome DevTools MCP server follow the instructions from <a href="https://antigravity.google/docs/mcp">Antigravity's docs</a> to install a custom MCP server. Add the following config to the MCP servers config:
80
102
 
81
103
  ```bash
82
104
  {
@@ -101,12 +123,34 @@ Chrome DevTools MCP will not start the browser instance automatically using this
101
123
 
102
124
  <details>
103
125
  <summary>Claude Code</summary>
104
- Use the Claude Code CLI to add the Chrome DevTools MCP server (<a href="https://docs.anthropic.com/en/docs/claude-code/mcp">guide</a>):
126
+
127
+ **Install via CLI (MCP only)**
128
+
129
+ Use the Claude Code CLI to add the Chrome DevTools MCP server (<a href="https://code.claude.com/docs/en/mcp">guide</a>):
105
130
 
106
131
  ```bash
107
- claude mcp add chrome-devtools npx @nimbus21.ai/chrome-devtools-mcp@latest
132
+ claude mcp add chrome-devtools --scope user npx @nimbus21.ai/chrome-devtools-mcp@latest
108
133
  ```
109
134
 
135
+ **Install as a Plugin (MCP + Skills)**
136
+
137
+ > [!NOTE]
138
+ > If you already had Chrome DevTools MCP installed previously for Claude Code, make sure to remove it first from your installation and configuration files.
139
+
140
+ To install Chrome DevTools MCP with skills, add the marketplace registry in Claude Code:
141
+
142
+ ```sh
143
+ /plugin marketplace add ChromeDevTools/chrome-devtools-mcp
144
+ ```
145
+
146
+ Then, install the plugin:
147
+
148
+ ```sh
149
+ /plugin install chrome-devtools-mcp
150
+ ```
151
+
152
+ Restart Claude Code to have the MCP server and skills load (check with `/skills`).
153
+
110
154
  </details>
111
155
 
112
156
  <details>
@@ -116,7 +160,7 @@ claude mcp add chrome-devtools npx @nimbus21.ai/chrome-devtools-mcp@latest
116
160
 
117
161
  <details>
118
162
  <summary>Codex</summary>
119
- Follow the <a href="https://github.com/openai/codex/blob/main/docs/advanced.md#model-context-protocol-mcp">configure MCP guide</a>
163
+ Follow the <a href="https://developers.openai.com/codex/mcp/#configure-with-the-cli">configure MCP guide</a>
120
164
  using the standard config from above. You can also install the Chrome DevTools MCP server using the Codex CLI:
121
165
 
122
166
  ```bash
@@ -208,7 +252,10 @@ Install the Chrome DevTools MCP server using the Gemini CLI.
208
252
  **Project wide:**
209
253
 
210
254
  ```bash
255
+ # Either MCP only:
211
256
  gemini mcp add chrome-devtools npx @nimbus21.ai/chrome-devtools-mcp@latest
257
+ # Or as a Gemini extension (MCP+Skills):
258
+ gemini extensions install --auto-update https://github.com/ChromeDevTools/chrome-devtools-mcp
212
259
  ```
213
260
 
214
261
  **Globally:**
@@ -244,6 +291,49 @@ Or, from the IDE **Activity Bar** > `Kiro` > `MCP Servers` > `Click Open MCP Con
244
291
 
245
292
  </details>
246
293
 
294
+ <details>
295
+ <summary>Katalon Studio</summary>
296
+
297
+ The Chrome DevTools MCP server can be used with <a href="https://docs.katalon.com/katalon-studio/studioassist/mcp-servers/setting-up-chrome-devtools-mcp-server-for-studioassist">Katalon StudioAssist</a> via an MCP proxy.
298
+
299
+ **Step 1:** Install the MCP proxy by following the <a href="https://docs.katalon.com/katalon-studio/studioassist/mcp-servers/setting-up-mcp-proxy-for-stdio-mcp-servers">MCP proxy setup guide</a>.
300
+
301
+ **Step 2:** Start the Chrome DevTools MCP server with the proxy:
302
+
303
+ ```bash
304
+ mcp-proxy --transport streamablehttp --port 8080 -- npx -y chrome-devtools-mcp@latest
305
+ ```
306
+
307
+ **Note:** You may need to pick another port if 8080 is already in use.
308
+
309
+ **Step 3:** In Katalon Studio, add the server to StudioAssist with the following settings:
310
+
311
+ - **Connection URL:** `http://127.0.0.1:8080/mcp`
312
+ - **Transport type:** `HTTP`
313
+
314
+ Once connected, the Chrome DevTools MCP tools will be available in StudioAssist.
315
+
316
+ </details>
317
+
318
+ <details>
319
+ <summary>OpenCode</summary>
320
+
321
+ Add the following configuration to your `opencode.json` file. If you don't have one, create it at `~/.config/opencode/opencode.json` (<a href="https://opencode.ai/docs/mcp-servers">guide</a>):
322
+
323
+ ```json
324
+ {
325
+ "$schema": "https://opencode.ai/config.json",
326
+ "mcp": {
327
+ "chrome-devtools": {
328
+ "type": "local",
329
+ "command": ["npx", "-y", "chrome-devtools-mcp@latest"]
330
+ }
331
+ }
332
+ }
333
+ ```
334
+
335
+ </details>
336
+
247
337
  <details>
248
338
  <summary>Qoder</summary>
249
339
 
@@ -354,7 +444,7 @@ The Chrome DevTools MCP server supports the following configuration option:
354
444
  <!-- BEGIN AUTO GENERATED OPTIONS -->
355
445
 
356
446
  - **`--autoConnect`/ `--auto-connect`**
357
- If specified, automatically connects to a browser (Chrome 145+) running in the user data directory identified by the channel param. Requires remote debugging being enabled in Chrome here: chrome://inspect/#remote-debugging.
447
+ If specified, automatically connects to a browser (Chrome 144+) running in the user data directory identified by the channel param. Requires the remoted debugging server to be started in the Chrome instance via chrome://inspect/#remote-debugging.
358
448
  - **Type:** boolean
359
449
  - **Default:** `false`
360
450
 
@@ -421,6 +511,10 @@ The Chrome DevTools MCP server supports the following configuration option:
421
511
  Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.
422
512
  - **Type:** array
423
513
 
514
+ - **`--ignoreDefaultChromeArg`/ `--ignore-default-chrome-arg`**
515
+ Explicitly disable default arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.
516
+ - **Type:** array
517
+
424
518
  - **`--categoryEmulation`/ `--category-emulation`**
425
519
  Set to false to exclude tools related to emulation.
426
520
  - **Type:** boolean
@@ -436,6 +530,16 @@ The Chrome DevTools MCP server supports the following configuration option:
436
530
  - **Type:** boolean
437
531
  - **Default:** `true`
438
532
 
533
+ - **`--performanceCrux`/ `--performance-crux`**
534
+ Set to false to disable sending URLs from performance traces to CrUX API to get field performance data.
535
+ - **Type:** boolean
536
+ - **Default:** `true`
537
+
538
+ - **`--usageStatistics`/ `--usage-statistics`**
539
+ Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set.
540
+ - **Type:** boolean
541
+ - **Default:** `true`
542
+
439
543
  <!-- END AUTO GENERATED OPTIONS -->
440
544
 
441
545
  Pass them via the `args` property in the JSON configuration. For example:
@@ -511,7 +615,7 @@ In these cases, start Chrome first and let the Chrome DevTools MCP server connec
511
615
 
512
616
  **Step 1:** Set up remote debugging in Chrome
513
617
 
514
- In Chrome, do the following to set up remote debugging:
618
+ In Chrome (\>= M144), do the following to set up remote debugging:
515
619
 
516
620
  1. Navigate to `chrome://inspect/#remote-debugging` to enable remote debugging.
517
621
  2. Follow the dialog UI to allow or disallow incoming debugging connections.
@@ -528,19 +632,12 @@ The following code snippet is an example configuration for gemini-cli:
528
632
  "mcpServers": {
529
633
  "chrome-devtools": {
530
634
  "command": "npx",
531
- "args": [
532
- "chrome-devtools-mcp@latest",
533
- "--autoConnect",
534
- "--channel=canary"
535
- ]
635
+ "args": ["chrome-devtools-mcp@latest", "--autoConnect"]
536
636
  }
537
637
  }
538
638
  }
539
639
  ```
540
640
 
541
- Note: you have to specify `--channel=canary` until Chrome M144 has reached the
542
- stable channel.
543
-
544
641
  **Step 3:** Test your setup
545
642
 
546
643
  Make sure your browser is running. Open gemini-cli and run the following prompt:
@@ -549,7 +646,8 @@ Make sure your browser is running. Open gemini-cli and run the following prompt:
549
646
  Check the performance of https://developers.chrome.com
550
647
  ```
551
648
 
552
- Note: The <code>autoConnect</code> option requires the user to start Chrome.
649
+ > [!NOTE]
650
+ > The <code>autoConnect</code> option requires the user to start Chrome. If the user has multiple active profiles, the MCP server will connect to the default profile (as determined by Chrome). The MCP server has access to all open windows for the selected profile.
553
651
 
554
652
  The Chrome DevTools MCP server will try to connect to your running Chrome
555
653
  instance. It shows a dialog asking for user permission.
@@ -623,13 +721,10 @@ If you hit VM-to-host port forwarding issues, see the “Remote debugging betwee
623
721
 
624
722
  For more details on remote debugging, see the [Chrome DevTools documentation](https://developer.chrome.com/docs/devtools/remote-debugging/).
625
723
 
626
- ## Known limitations
724
+ ### Debugging Chrome on Android
627
725
 
628
- ### Operating system sandboxes
726
+ Please consult [these instructions](./docs/debugging-android.md).
727
+
728
+ ## Known limitations
629
729
 
630
- Some MCP clients allow sandboxing the MCP server using macOS Seatbelt or Linux
631
- containers. If sandboxes are enabled, `chrome-devtools-mcp` is not able to start
632
- Chrome that requires permissions to create its own sandboxes. As a workaround,
633
- either disable sandboxing for `chrome-devtools-mcp` in your MCP client or use
634
- `--browser-url` to connect to a Chrome instance that you start manually outside
635
- of the MCP client sandbox.
730
+ See [Troubleshooting](./docs/troubleshooting.md).
@@ -4,8 +4,6 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import { PuppeteerDevToolsConnection } from './DevToolsConnectionAdapter.js';
7
- import { ISSUE_UTILS } from './issue-descriptions.js';
8
- import { logger } from './logger.js';
9
7
  import { Mutex } from './Mutex.js';
10
8
  import { DevTools } from './third_party/index.js';
11
9
  export function extractUrlLikeFromDevToolsTitle(title) {
@@ -64,46 +62,6 @@ export class FakeIssuesManager extends DevTools.Common.ObjectWrapper
64
62
  return [];
65
63
  }
66
64
  }
67
- export function mapIssueToMessageObject(issue) {
68
- const count = issue.getAggregatedIssuesCount();
69
- const markdownDescription = issue.getDescription();
70
- const filename = markdownDescription?.file;
71
- if (!markdownDescription) {
72
- logger(`no description found for issue:` + issue.code);
73
- return null;
74
- }
75
- const rawMarkdown = filename
76
- ? ISSUE_UTILS.getIssueDescription(filename)
77
- : null;
78
- if (!rawMarkdown) {
79
- logger(`no markdown ${filename} found for issue:` + issue.code);
80
- return null;
81
- }
82
- let processedMarkdown;
83
- let title;
84
- try {
85
- processedMarkdown =
86
- DevTools.MarkdownIssueDescription.substitutePlaceholders(rawMarkdown, markdownDescription.substitutions);
87
- const markdownAst = DevTools.Marked.Marked.lexer(processedMarkdown);
88
- title =
89
- DevTools.MarkdownIssueDescription.findTitleFromMarkdownAst(markdownAst);
90
- }
91
- catch {
92
- logger('error parsing markdown for issue ' + issue.code());
93
- return null;
94
- }
95
- if (!title) {
96
- logger('cannot read issue title from ' + filename);
97
- return null;
98
- }
99
- return {
100
- type: 'issue',
101
- item: issue,
102
- message: title,
103
- count,
104
- description: processedMarkdown,
105
- };
106
- }
107
65
  // DevTools CDP errors can get noisy.
108
66
  DevTools.ProtocolClient.InspectorBackend.test.suppressRequestErrors = true;
109
67
  DevTools.I18n.DevToolsLocale.DevToolsLocale.instance({
@@ -115,6 +73,11 @@ DevTools.I18n.DevToolsLocale.DevToolsLocale.instance({
115
73
  },
116
74
  });
117
75
  DevTools.I18n.i18n.registerLocaleDataForTest('en-US', {});
76
+ DevTools.Formatter.FormatterWorkerPool.FormatterWorkerPool.instance({
77
+ forceNew: true,
78
+ entrypointURL: import.meta
79
+ .resolve('./third_party/devtools-formatter-worker.js'),
80
+ });
118
81
  export class UniverseManager {
119
82
  #browser;
120
83
  #createUniverseFor;
@@ -206,3 +169,172 @@ const SKIP_ALL_PAUSES = {
206
169
  // Do nothing.
207
170
  },
208
171
  };
172
+ /**
173
+ * Constructed from Runtime.ExceptionDetails of an uncaught error.
174
+ *
175
+ * TODO: Also construct from a RemoteObject of subtype 'error'.
176
+ *
177
+ * Consists of the message, a fully resolved stack trace and a fully resolved 'cause' chain.
178
+ */
179
+ export class SymbolizedError {
180
+ message;
181
+ stackTrace;
182
+ cause;
183
+ constructor(message, stackTrace, cause) {
184
+ this.message = message;
185
+ this.stackTrace = stackTrace;
186
+ this.cause = cause;
187
+ }
188
+ static async fromDetails(opts) {
189
+ const message = SymbolizedError.#getMessage(opts.details);
190
+ if (!opts.includeStackAndCause || !opts.devTools) {
191
+ return new SymbolizedError(message, opts.resolvedStackTraceForTesting, opts.resolvedCauseForTesting);
192
+ }
193
+ let stackTrace;
194
+ if (opts.resolvedStackTraceForTesting) {
195
+ stackTrace = opts.resolvedStackTraceForTesting;
196
+ }
197
+ else if (opts.details.stackTrace) {
198
+ try {
199
+ stackTrace = await createStackTrace(opts.devTools, opts.details.stackTrace, opts.targetId);
200
+ }
201
+ catch {
202
+ // ignore
203
+ }
204
+ }
205
+ // TODO: Turn opts.details.exception into a JSHandle and retrieve the 'cause' property.
206
+ // If its an Error, recursively create a SymbolizedError.
207
+ let cause;
208
+ if (opts.resolvedCauseForTesting) {
209
+ cause = opts.resolvedCauseForTesting;
210
+ }
211
+ else if (opts.details.exception) {
212
+ try {
213
+ const causeRemoteObj = await SymbolizedError.#lookupCause(opts.devTools, opts.details.exception, opts.targetId);
214
+ if (causeRemoteObj) {
215
+ cause = await SymbolizedError.fromError({
216
+ devTools: opts.devTools,
217
+ error: causeRemoteObj,
218
+ targetId: opts.targetId,
219
+ });
220
+ }
221
+ }
222
+ catch {
223
+ // Ignore
224
+ }
225
+ }
226
+ return new SymbolizedError(message, stackTrace, cause);
227
+ }
228
+ static async fromError(opts) {
229
+ const details = await SymbolizedError.#getExceptionDetails(opts.devTools, opts.error, opts.targetId);
230
+ if (details) {
231
+ return SymbolizedError.fromDetails({
232
+ details,
233
+ devTools: opts.devTools,
234
+ targetId: opts.targetId,
235
+ includeStackAndCause: true,
236
+ });
237
+ }
238
+ return new SymbolizedError(SymbolizedError.#getMessageFromException(opts.error));
239
+ }
240
+ static #getMessage(details) {
241
+ // For Runtime.exceptionThrown with a present exception object, `details.text` will be "Uncaught" and
242
+ // we have to manually parse out the error text from the exception description.
243
+ // In the case of Runtime.getExceptionDetails, `details.text` has the Error.message.
244
+ if (details.text === 'Uncaught' && details.exception) {
245
+ return ('Uncaught ' +
246
+ SymbolizedError.#getMessageFromException(details.exception));
247
+ }
248
+ return details.text;
249
+ }
250
+ static #getMessageFromException(error) {
251
+ const messageWithRest = error.description?.split('\n at ', 2) ?? [];
252
+ return messageWithRest[0] ?? '';
253
+ }
254
+ static async #getExceptionDetails(devTools, error, targetId) {
255
+ if (!devTools || (error.type !== 'object' && error.subtype !== 'error')) {
256
+ return null;
257
+ }
258
+ const targetManager = devTools.universe.context.get(DevTools.TargetManager);
259
+ const target = targetId
260
+ ? targetManager.targetById(targetId) || devTools.target
261
+ : devTools.target;
262
+ const model = target.model(DevTools.RuntimeModel);
263
+ return ((await model.getExceptionDetails(error.objectId)) ?? null);
264
+ }
265
+ static async #lookupCause(devTools, error, targetId) {
266
+ if (!devTools || (error.type !== 'object' && error.subtype !== 'error')) {
267
+ return null;
268
+ }
269
+ const targetManager = devTools.universe.context.get(DevTools.TargetManager);
270
+ const target = targetId
271
+ ? targetManager.targetById(targetId) || devTools.target
272
+ : devTools.target;
273
+ const properties = await target.runtimeAgent().invoke_getProperties({
274
+ objectId: error.objectId,
275
+ });
276
+ if (properties.getError()) {
277
+ return null;
278
+ }
279
+ return properties.result.find(prop => prop.name === 'cause')?.value ?? null;
280
+ }
281
+ static createForTesting(message, stackTrace, cause) {
282
+ return new SymbolizedError(message, stackTrace, cause);
283
+ }
284
+ }
285
+ export async function createStackTraceForConsoleMessage(devTools, consoleMessage) {
286
+ const message = consoleMessage;
287
+ const rawStackTrace = message._rawStackTrace();
288
+ if (rawStackTrace) {
289
+ return createStackTrace(devTools, rawStackTrace, message._targetId());
290
+ }
291
+ return undefined;
292
+ }
293
+ export async function createStackTrace(devTools, rawStackTrace, targetId) {
294
+ const targetManager = devTools.universe.context.get(DevTools.TargetManager);
295
+ const target = targetId
296
+ ? targetManager.targetById(targetId) || devTools.target
297
+ : devTools.target;
298
+ const model = target.model(DevTools.DebuggerModel);
299
+ // DevTools doesn't wait for source maps to attach before building a stack trace, rather it'll send
300
+ // an update event once a source map was attached and the stack trace retranslated. This doesn't
301
+ // work in the MCP case, so we'll collect all script IDs upfront and wait for any pending source map
302
+ // loads before creating the stack trace. We might also have to wait for Debugger.ScriptParsed events if
303
+ // the stack trace is created particularly early.
304
+ const scriptIds = new Set();
305
+ for (const frame of rawStackTrace.callFrames) {
306
+ scriptIds.add(frame.scriptId);
307
+ }
308
+ for (let asyncStack = rawStackTrace.parent; asyncStack; asyncStack = asyncStack.parent) {
309
+ for (const frame of asyncStack.callFrames) {
310
+ scriptIds.add(frame.scriptId);
311
+ }
312
+ }
313
+ const signal = AbortSignal.timeout(1_000);
314
+ await Promise.all([...scriptIds].map(id => waitForScript(model, id, signal)
315
+ .then(script => model.sourceMapManager().sourceMapForClientPromise(script))
316
+ .catch()));
317
+ const binding = devTools.universe.context.get(DevTools.DebuggerWorkspaceBinding);
318
+ // DevTools uses branded types for ScriptId and others. Casting the puppeteer protocol type to the DevTools protocol type is safe.
319
+ return binding.createStackTraceFromProtocolRuntime(rawStackTrace, target);
320
+ }
321
+ // Waits indefinitely for the script so pair it with Promise.race.
322
+ async function waitForScript(model, scriptId, signal) {
323
+ while (true) {
324
+ if (signal.aborted) {
325
+ throw signal.reason;
326
+ }
327
+ const script = model.scriptForId(scriptId);
328
+ if (script) {
329
+ return script;
330
+ }
331
+ await new Promise((resolve, reject) => {
332
+ signal.addEventListener('abort', () => reject(signal.reason), {
333
+ once: true,
334
+ });
335
+ void model
336
+ .once('ParsedScriptSource')
337
+ .then(resolve);
338
+ });
339
+ }
340
+ }