brave-mcp 0.4.0 → 1.2.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 (120) hide show
  1. package/README.md +97 -29
  2. package/build/src/HeapSnapshotManager.js +55 -18
  3. package/build/src/McpContext.js +94 -32
  4. package/build/src/McpPage.js +9 -9
  5. package/build/src/McpResponse.js +167 -63
  6. package/build/src/PageCollector.js +8 -8
  7. package/build/src/ServiceWorkerCollector.js +171 -0
  8. package/build/src/TextSnapshot.js +1 -1
  9. package/build/src/ToolHandler.js +223 -0
  10. package/build/src/WaitForHelper.js +20 -6
  11. package/build/src/bin/brave-devtools-cli-options.js +136 -40
  12. package/build/src/bin/brave-devtools-mcp-cli-options.js +22 -11
  13. package/build/src/bin/brave-devtools-mcp-main.js +45 -6
  14. package/build/src/bin/brave-devtools.js +22 -7
  15. package/build/src/bin/check-latest-version.js +25 -1
  16. package/build/src/browser.js +61 -7
  17. package/build/src/daemon/client.js +19 -13
  18. package/build/src/daemon/daemon.js +74 -17
  19. package/build/src/daemon/utils.js +2 -2
  20. package/build/src/{DevToolsConnectionAdapter.js → devtools/DevToolsConnectionAdapter.js} +1 -1
  21. package/build/src/{DevtoolsUtils.js → devtools/DevtoolsUtils.js} +94 -19
  22. package/build/src/formatters/ConsoleFormatter.js +1 -1
  23. package/build/src/formatters/HeapSnapshotFormatter.js +44 -9
  24. package/build/src/formatters/IssueFormatter.js +4 -4
  25. package/build/src/index.js +24 -165
  26. package/build/src/telemetry/ClearcutLogger.js +35 -119
  27. package/build/src/telemetry/WatchdogClient.js +4 -4
  28. package/build/src/telemetry/errors.js +18 -0
  29. package/build/src/telemetry/flagUtils.js +4 -3
  30. package/build/src/telemetry/{toolMetricsUtils.js → metricsRegistry.js} +3 -3
  31. package/build/src/telemetry/persistence.js +21 -3
  32. package/build/src/telemetry/transformation.js +134 -0
  33. package/build/src/telemetry/types.js +0 -8
  34. package/build/src/telemetry/watchdog/ClearcutSender.js +10 -10
  35. package/build/src/telemetry/watchdog/main.js +5 -5
  36. package/build/src/third_party/THIRD_PARTY_NOTICES +3637 -0
  37. package/build/src/third_party/bundled-packages.json +12 -0
  38. package/build/src/third_party/devtools-formatter-worker.js +15300 -7
  39. package/build/src/third_party/devtools-heap-snapshot-worker.js +9817 -7
  40. package/build/src/third_party/index.js +159331 -27
  41. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsEmptyList.md +1 -0
  42. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsHttpNotFound.md +1 -0
  43. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidContentType.md +1 -0
  44. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidResponse.md +1 -0
  45. package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsNoResponse.md +1 -0
  46. package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsFetchFailed.md +1 -0
  47. package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsInvalidRecord.md +1 -0
  48. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownHttpNotFound.md +1 -0
  49. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidContentType.md +1 -0
  50. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidResponse.md +1 -0
  51. package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownNoResponse.md +1 -0
  52. package/build/src/third_party/issue-descriptions/emailVerificationRequestInvalidEmail.md +1 -0
  53. package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksHttpNotFound.md +1 -0
  54. package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksInvalidResponse.md +1 -0
  55. package/build/src/third_party/issue-descriptions/emailVerificationRequestKeyBindingSigningFailed.md +1 -0
  56. package/build/src/third_party/issue-descriptions/emailVerificationRequestRpOriginIsOpaque.md +1 -0
  57. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenHttpNotFound.md +1 -0
  58. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidContentType.md +1 -0
  59. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidResponse.md +1 -0
  60. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidSdJwt.md +1 -0
  61. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenMalformedSdJwt.md +1 -0
  62. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenNoResponse.md +1 -0
  63. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidAudience.md +1 -0
  64. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidIssuedAt.md +1 -0
  65. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidNonce.md +1 -0
  66. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidSdHash.md +1 -0
  67. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidTyp.md +1 -0
  68. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingAud.md +1 -0
  69. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingCnf.md +1 -0
  70. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingIat.md +1 -0
  71. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingNonce.md +1 -0
  72. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingSdHash.md +1 -0
  73. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbSignatureFailed.md +1 -0
  74. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmail.md +1 -0
  75. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmailVerified.md +1 -0
  76. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidHolderKey.md +1 -0
  77. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuedAt.md +1 -0
  78. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuer.md +1 -0
  79. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtJwksMissingKeys.md +1 -0
  80. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingCnf.md +1 -0
  81. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingEmail.md +1 -0
  82. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIat.md +1 -0
  83. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIss.md +1 -0
  84. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtSignatureFailed.md +1 -0
  85. package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtUnsupportedHeaderAlg.md +1 -0
  86. package/build/src/third_party/issue-descriptions/emailVerificationRequestUserLoggedOut.md +1 -0
  87. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownAccountsEndpointCrossOrigin.md +1 -0
  88. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownHttpNotFound.md +1 -0
  89. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidContentType.md +1 -0
  90. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidResponse.md +1 -0
  91. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownIssuanceEndpointCrossOrigin.md +1 -0
  92. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownListEmpty.md +1 -0
  93. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingAccountsEndpoint.md +1 -0
  94. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingIssuanceEndpoint.md +1 -0
  95. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownNoResponse.md +1 -0
  96. package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownUnsupportedSigningAlgorithm.md +1 -0
  97. package/build/src/third_party/issue-descriptions/genericBackUINavigationWouldSkipAd.md +4 -0
  98. package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +4236 -4219
  99. package/build/src/tools/ToolDefinition.js +2 -2
  100. package/build/src/tools/console.js +7 -0
  101. package/build/src/tools/emulation.js +29 -2
  102. package/build/src/tools/extensions.js +5 -0
  103. package/build/src/tools/input.js +57 -19
  104. package/build/src/tools/lighthouse.js +18 -13
  105. package/build/src/tools/memory.js +97 -21
  106. package/build/src/tools/network.js +2 -2
  107. package/build/src/tools/pages.js +13 -5
  108. package/build/src/tools/performance.js +15 -11
  109. package/build/src/tools/screencast.js +2 -1
  110. package/build/src/tools/screenshot.js +1 -1
  111. package/build/src/tools/script.js +33 -11
  112. package/build/src/tools/slim/tools.js +3 -0
  113. package/build/src/tools/snapshot.js +3 -2
  114. package/build/src/tools/thirdPartyDeveloper.js +12 -2
  115. package/build/src/tools/webmcp.js +2 -0
  116. package/build/src/trace-processing/parse.js +7 -7
  117. package/build/src/utils/files.js +43 -0
  118. package/build/src/version.js +4 -1
  119. package/package.json +21 -17
  120. package/build/src/telemetry/metricUtils.js +0 -15
@@ -4,17 +4,19 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import fs from 'node:fs/promises';
7
+ import fsPromises from 'node:fs/promises';
7
8
  import os from 'node:os';
8
9
  import path from 'node:path';
9
10
  import { fileURLToPath, pathToFileURL } from 'node:url';
10
- import { UniverseManager } from './DevtoolsUtils.js';
11
+ import { overrideDevToolsGlobals, UniverseManager, } from './devtools/DevtoolsUtils.js';
11
12
  import { HeapSnapshotManager } from './HeapSnapshotManager.js';
12
13
  import { McpPage } from './McpPage.js';
13
14
  import { NetworkCollector, ConsoleCollector, } from './PageCollector.js';
15
+ import { ServiceWorkerConsoleCollector } from './ServiceWorkerCollector.js';
14
16
  import { Locator, PredefinedNetworkConditions, } from './third_party/index.js';
15
17
  import { listPages } from './tools/pages.js';
16
18
  import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js';
17
- import { ensureExtension, getTempFilePath } from './utils/files.js';
19
+ import { ensureExtension, getTempFilePath, resolveCanonicalPath, } from './utils/files.js';
18
20
  import { getNetworkMultiplierFromString } from './WaitForHelper.js';
19
21
  const DEFAULT_TIMEOUT = 5_000;
20
22
  const NAVIGATION_TIMEOUT = 10_000;
@@ -32,6 +34,7 @@ export class McpContext {
32
34
  #networkCollector;
33
35
  #consoleCollector;
34
36
  #devtoolsUniverseManager;
37
+ #serviceWorkerConsoleCollector;
35
38
  #isRunningTrace = false;
36
39
  #screenRecorderData = null;
37
40
  #nextPageId = 1;
@@ -44,6 +47,7 @@ export class McpContext {
44
47
  #heapSnapshotManager = new HeapSnapshotManager();
45
48
  #roots = undefined;
46
49
  constructor(browser, logger, options, locatorClass) {
50
+ overrideDevToolsGlobals();
47
51
  this.browser = browser;
48
52
  this.logger = logger;
49
53
  this.#locatorClass = locatorClass;
@@ -62,19 +66,22 @@ export class McpContext {
62
66
  },
63
67
  };
64
68
  });
69
+ this.#serviceWorkerConsoleCollector = new ServiceWorkerConsoleCollector(this.browser);
65
70
  this.#devtoolsUniverseManager = new UniverseManager(this.browser);
66
71
  }
67
72
  async #init() {
68
73
  const pages = await this.createPagesSnapshot();
69
- await this.createExtensionServiceWorkersSnapshot();
74
+ const workers = await this.createExtensionServiceWorkersSnapshot();
70
75
  await this.#networkCollector.init(pages);
71
76
  await this.#consoleCollector.init(pages);
72
77
  await this.#devtoolsUniverseManager.init(pages);
78
+ await this.#serviceWorkerConsoleCollector.init(workers);
73
79
  }
74
80
  dispose() {
75
81
  this.#networkCollector.dispose();
76
82
  this.#consoleCollector.dispose();
77
83
  this.#devtoolsUniverseManager.dispose();
84
+ this.#serviceWorkerConsoleCollector.dispose();
78
85
  for (const mcpPage of this.#mcpPages.values()) {
79
86
  mcpPage.dispose();
80
87
  }
@@ -106,7 +113,7 @@ export class McpContext {
106
113
  setRoots(roots) {
107
114
  this.#roots = roots;
108
115
  }
109
- validatePath(filePath) {
116
+ async validatePath(filePath) {
110
117
  if (filePath === undefined) {
111
118
  return;
112
119
  }
@@ -114,19 +121,40 @@ export class McpContext {
114
121
  if (roots === undefined) {
115
122
  return;
116
123
  }
117
- const absolutePath = path.resolve(filePath);
124
+ let canonicalPath;
125
+ try {
126
+ canonicalPath = await resolveCanonicalPath(filePath);
127
+ }
128
+ catch (err) {
129
+ const errMsg = err instanceof Error ? err.message : String(err);
130
+ console.error(`[MCP Context] Error resolving real path for ${filePath}: ${errMsg}`);
131
+ throw new Error(`Access denied: Cannot resolve base path for ${filePath}.`);
132
+ }
133
+ let allowed = false;
118
134
  for (const root of roots) {
119
- const rootPath = path.resolve(fileURLToPath(root.uri));
120
- if (absolutePath === rootPath ||
121
- absolutePath.startsWith(rootPath + path.sep)) {
122
- return;
135
+ try {
136
+ const rootPathUri = root.uri;
137
+ const rootPath = path.resolve(fileURLToPath(rootPathUri));
138
+ const canonicalRoot = await fsPromises.realpath(rootPath);
139
+ if (canonicalPath === canonicalRoot ||
140
+ canonicalPath.startsWith(canonicalRoot + path.sep)) {
141
+ allowed = true;
142
+ break;
143
+ }
144
+ }
145
+ catch (rootErr) {
146
+ const errMsg = rootErr instanceof Error ? rootErr.message : String(rootErr);
147
+ console.warn(`[MCP Context] Could not resolve configured root ${root.uri}: ${errMsg}`);
148
+ // Skip this root if it cannot be resolved.
123
149
  }
124
150
  }
125
- throw new Error(`Access denied: path ${filePath} is not within any of the workspace roots ${JSON.stringify(roots)}.`);
151
+ if (!allowed) {
152
+ throw new Error(`Access denied: path ${filePath} (canonical: ${canonicalPath}) is not within any of the configured workspace roots.`);
153
+ }
126
154
  }
127
155
  resolveCdpRequestId(page, cdpRequestId) {
128
156
  if (!cdpRequestId) {
129
- this.logger('no network request');
157
+ this.logger?.('no network request');
130
158
  return;
131
159
  }
132
160
  const request = this.#networkCollector.find(page.pptrPage, request => {
@@ -134,7 +162,7 @@ export class McpContext {
134
162
  return request.id === cdpRequestId;
135
163
  });
136
164
  if (!request) {
137
- this.logger('no network request for ' + cdpRequestId);
165
+ this.logger?.('no network request for ' + cdpRequestId);
138
166
  return;
139
167
  }
140
168
  return this.#networkCollector.getIdForResource(request);
@@ -195,7 +223,13 @@ export class McpContext {
195
223
  const page = targetPage ?? this.getSelectedPptrPage();
196
224
  const mcpPage = this.#getMcpPage(page);
197
225
  const newSettings = { ...mcpPage.emulationSettings };
198
- if (!options.networkConditions) {
226
+ // Skip network emulation if blocklist/allowlist is configured, as it conflicts with blocking rules in Puppeteer.
227
+ if (this.#options.hasNetworkBlockOrAllowlist) {
228
+ if (options.networkConditions !== undefined) {
229
+ throw new Error('Network throttling is not supported when network blocking (allowlist/blocklist) is configured.');
230
+ }
231
+ }
232
+ else if (!options.networkConditions) {
199
233
  await page.emulateNetworkConditions(null);
200
234
  delete newSettings.networkConditions;
201
235
  }
@@ -213,12 +247,23 @@ export class McpContext {
213
247
  await page.emulateNetworkConditions(networkCondition);
214
248
  newSettings.networkConditions = options.networkConditions;
215
249
  }
250
+ const secondarySession = this.getDevToolsUniverse(mcpPage)?.session;
216
251
  if (!options.cpuThrottlingRate) {
217
252
  await page.emulateCPUThrottling(1);
253
+ if (secondarySession) {
254
+ await secondarySession.send('Emulation.setCPUThrottlingRate', {
255
+ rate: 1,
256
+ });
257
+ }
218
258
  delete newSettings.cpuThrottlingRate;
219
259
  }
220
260
  else {
221
261
  await page.emulateCPUThrottling(options.cpuThrottlingRate);
262
+ if (secondarySession) {
263
+ await secondarySession.send('Emulation.setCPUThrottlingRate', {
264
+ rate: options.cpuThrottlingRate,
265
+ });
266
+ }
222
267
  newSettings.cpuThrottlingRate = options.cpuThrottlingRate;
223
268
  }
224
269
  if (!options.geolocation) {
@@ -250,7 +295,6 @@ export class McpContext {
250
295
  newSettings.colorScheme = options.colorScheme;
251
296
  }
252
297
  if (!options.viewport) {
253
- await page.setViewport(null);
254
298
  delete newSettings.viewport;
255
299
  }
256
300
  else {
@@ -260,14 +304,22 @@ export class McpContext {
260
304
  hasTouch: false,
261
305
  isLandscape: false,
262
306
  };
263
- const viewport = { ...defaults, ...options.viewport };
264
- await page.setViewport(viewport);
265
- newSettings.viewport = viewport;
307
+ newSettings.viewport = { ...defaults, ...options.viewport };
308
+ }
309
+ if (options.extraHttpHeaders !== undefined) {
310
+ await page.setExtraHTTPHeaders(options.extraHttpHeaders);
311
+ newSettings.extraHttpHeaders = options.extraHttpHeaders;
312
+ if (Object.keys(options.extraHttpHeaders).length === 0) {
313
+ delete newSettings.extraHttpHeaders;
314
+ }
266
315
  }
267
316
  mcpPage.emulationSettings = Object.keys(newSettings).length
268
317
  ? newSettings
269
318
  : {};
270
319
  this.#updateSelectedPageTimeouts();
320
+ // This should happen after updating the page timeouts.
321
+ // Setting the viewport can trigger a reload which we don't want to timeout.
322
+ await page.setViewport(newSettings.viewport ?? null);
271
323
  }
272
324
  setIsRunningPerformanceTrace(x) {
273
325
  this.#isRunningTrace = x;
@@ -333,9 +385,9 @@ export class McpContext {
333
385
  page.pptrPage.setDefaultTimeout(DEFAULT_TIMEOUT * cpuMultiplier);
334
386
  // 10sec should be enough for the load event to be emitted during
335
387
  // navigations.
336
- // Increased in case we throttle the network requests
388
+ // Increased in case we throttle the network requests or the CPU
337
389
  const networkMultiplier = getNetworkMultiplierFromString(page.networkConditions);
338
- page.pptrPage.setDefaultNavigationTimeout(NAVIGATION_TIMEOUT * networkMultiplier);
390
+ page.pptrPage.setDefaultNavigationTimeout(NAVIGATION_TIMEOUT * networkMultiplier * cpuMultiplier);
339
391
  }
340
392
  // Linear scan over per-page snapshots. The page count is small (typically
341
393
  // 2-10) so a reverse index isn't worthwhile given the uid-reuse lifecycle
@@ -372,6 +424,9 @@ export class McpContext {
372
424
  });
373
425
  return this.#extensionServiceWorkers;
374
426
  }
427
+ getServiceWorkerConsoleData(extensionId) {
428
+ return this.#serviceWorkerConsoleCollector.getData(extensionId);
429
+ }
375
430
  async createPagesSnapshot() {
376
431
  const { pages: allPages, isolatedContextNames } = await this.#getAllPages();
377
432
  for (const page of allPages) {
@@ -381,7 +436,7 @@ export class McpContext {
381
436
  this.#mcpPages.set(page, mcpPage);
382
437
  // We emulate a focused page for all pages to support multi-agent workflows.
383
438
  void page.emulateFocusedPage(true).catch(error => {
384
- this.logger('Error turning on focused page emulation', error);
439
+ this.logger?.('Error turning on focused page emulation', error);
385
440
  });
386
441
  }
387
442
  mcpPage.isolatedContextName = isolatedContextNames.get(page);
@@ -427,7 +482,7 @@ export class McpContext {
427
482
  this.#extensionPages.set(target, page);
428
483
  }
429
484
  catch (e) {
430
- this.logger('Failed to get page for extension target', e);
485
+ this.logger?.('Failed to get page for extension target', e);
431
486
  }
432
487
  }
433
488
  }
@@ -462,7 +517,7 @@ export class McpContext {
462
517
  return { pages: allPages, isolatedContextNames };
463
518
  }
464
519
  async detectOpenDevToolsWindows() {
465
- this.logger('Detecting open DevTools windows');
520
+ this.logger?.('Detecting open DevTools windows');
466
521
  const { pages } = await this.#getAllPages();
467
522
  await Promise.all(pages.map(async (page) => {
468
523
  const mcpPage = this.#mcpPages.get(page);
@@ -499,7 +554,7 @@ export class McpContext {
499
554
  }
500
555
  async saveTemporaryFile(data, filename) {
501
556
  const filepath = await getTempFilePath(filename);
502
- this.validatePath(filepath);
557
+ await this.validatePath(filepath);
503
558
  try {
504
559
  await fs.writeFile(filepath, data);
505
560
  }
@@ -509,7 +564,7 @@ export class McpContext {
509
564
  return { filepath };
510
565
  }
511
566
  async saveFile(data, clientProvidedFilePath, extension) {
512
- this.validatePath(clientProvidedFilePath);
567
+ await this.validatePath(clientProvidedFilePath);
513
568
  try {
514
569
  const filePath = ensureExtension(path.resolve(clientProvidedFilePath), extension);
515
570
  await fs.mkdir(path.dirname(filePath), { recursive: true });
@@ -517,7 +572,7 @@ export class McpContext {
517
572
  return { filename: filePath };
518
573
  }
519
574
  catch (err) {
520
- this.logger(err);
575
+ this.logger?.(err);
521
576
  throw new Error('Could not save a file', { cause: err });
522
577
  }
523
578
  }
@@ -562,7 +617,6 @@ export class McpContext {
562
617
  await this.#networkCollector.init(pages);
563
618
  }
564
619
  async installExtension(extensionPath) {
565
- this.validatePath(extensionPath);
566
620
  const id = await this.browser.installExtension(extensionPath);
567
621
  return id;
568
622
  }
@@ -586,20 +640,28 @@ export class McpContext {
586
640
  return pptrExtensions.get(id);
587
641
  }
588
642
  async getHeapSnapshotAggregates(filePath) {
589
- this.validatePath(filePath);
590
643
  return await this.#heapSnapshotManager.getAggregates(filePath);
591
644
  }
592
645
  async getHeapSnapshotStats(filePath) {
593
- this.validatePath(filePath);
594
646
  return await this.#heapSnapshotManager.getStats(filePath);
595
647
  }
596
648
  async getHeapSnapshotStaticData(filePath) {
597
- this.validatePath(filePath);
598
649
  return await this.#heapSnapshotManager.getStaticData(filePath);
599
650
  }
600
- async getHeapSnapshotNodesByUid(filePath, uid) {
601
- this.validatePath(filePath);
602
- return await this.#heapSnapshotManager.getNodesByUid(filePath, uid);
651
+ async getHeapSnapshotNodesById(filePath, id) {
652
+ return await this.#heapSnapshotManager.getNodesById(filePath, id);
653
+ }
654
+ async getHeapSnapshotRetainers(filePath, nodeId) {
655
+ return await this.#heapSnapshotManager.getRetainers(filePath, nodeId);
656
+ }
657
+ async closeHeapSnapshot(filePath) {
658
+ return this.#heapSnapshotManager.dispose(filePath);
659
+ }
660
+ hasHeapSnapshots() {
661
+ return this.#heapSnapshotManager.hasSnapshots();
662
+ }
663
+ async getHeapSnapshotRetainingPaths(filePath, nodeId, maxDepth, maxNodes, maxSiblings) {
664
+ return await this.#heapSnapshotManager.getRetainingPaths(filePath, nodeId, maxDepth, maxNodes, maxSiblings);
603
665
  }
604
666
  }
605
667
  //# sourceMappingURL=McpContext.js.map
@@ -30,7 +30,7 @@ export class McpPage {
30
30
  // Dialog
31
31
  #dialog;
32
32
  #dialogHandler;
33
- thirdPartyDeveloperTools;
33
+ thirdPartyDeveloperTools = [];
34
34
  constructor(page, id) {
35
35
  this.pptrPage = page;
36
36
  this.id = id;
@@ -194,18 +194,18 @@ export class McpPage {
194
194
  for (const handle of oldHandles) {
195
195
  await handle
196
196
  .dispose()
197
- .catch(e => logger('Failed to dispose old handle', e));
197
+ .catch(e => logger?.('Failed to dispose old handle', e));
198
198
  }
199
199
  }
200
200
  const cdpElementIds = await Promise.all(elementHandles.map(async (elementHandle, index) => {
201
201
  const backendNodeId = await elementHandle.backendNodeId();
202
202
  if (!backendNodeId) {
203
- logger(`No backendNodeId for stashed DOM element with index ${index}`);
203
+ logger?.(`No backendNodeId for stashed DOM element with index ${index}`);
204
204
  return `stashed-${index}`;
205
205
  }
206
206
  const cdpElementId = this.resolveCdpElementId(backendNodeId);
207
207
  if (!cdpElementId) {
208
- logger(`Could not get cdpElementId for backend node ${backendNodeId}`);
208
+ logger?.(`Could not get cdpElementId for backend node ${backendNodeId}`);
209
209
  return `stashed-${index}`;
210
210
  }
211
211
  return cdpElementId;
@@ -263,12 +263,12 @@ export class McpPage {
263
263
  }
264
264
  resolveCdpElementId(cdpBackendNodeId) {
265
265
  if (!cdpBackendNodeId) {
266
- logger('no cdpBackendNodeId');
266
+ logger?.('no cdpBackendNodeId');
267
267
  return;
268
268
  }
269
269
  const snapshot = this.textSnapshot;
270
270
  if (!snapshot) {
271
- logger('no text snapshot');
271
+ logger?.('no text snapshot');
272
272
  return;
273
273
  }
274
274
  // TODO: index by backendNodeId instead.
@@ -286,10 +286,10 @@ export class McpPage {
286
286
  }
287
287
  async getDevToolsData() {
288
288
  try {
289
- logger('Getting DevTools UI data');
289
+ logger?.('Getting DevTools UI data');
290
290
  const devtoolsPage = this.devToolsPage;
291
291
  if (!devtoolsPage) {
292
- logger('No DevTools page detected');
292
+ logger?.('No DevTools page detected');
293
293
  return {};
294
294
  }
295
295
  const { cdpRequestId, cdpBackendNodeId } = await devtoolsPage.evaluate(async () => {
@@ -307,7 +307,7 @@ export class McpPage {
307
307
  return { cdpBackendNodeId, cdpRequestId };
308
308
  }
309
309
  catch (err) {
310
- logger('error getting devtools data', err);
310
+ logger?.('error getting devtools data', err);
311
311
  }
312
312
  return {};
313
313
  }