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
@@ -6,7 +6,9 @@
6
6
  import fs from 'node:fs/promises';
7
7
  import os from 'node:os';
8
8
  import path from 'node:path';
9
+ import { extractUrlLikeFromDevToolsTitle, urlsEqual } from './DevtoolsUtils.js';
9
10
  import { NetworkCollector, PageCollector } from './PageCollector.js';
11
+ import { Locator } from './third_party/index.js';
10
12
  import { listPages } from './tools/pages.js';
11
13
  import { takeSnapshot } from './tools/snapshot.js';
12
14
  import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js';
@@ -43,6 +45,7 @@ export class McpContext {
43
45
  logger;
44
46
  // The most recent page state.
45
47
  #pages = [];
48
+ #pageToDevToolsPage = new Map();
46
49
  #selectedPageIdx = 0;
47
50
  // The most recent snapshot.
48
51
  #textSnapshot = null;
@@ -54,22 +57,31 @@ export class McpContext {
54
57
  #dialog;
55
58
  #nextSnapshotId = 1;
56
59
  #traceResults = [];
57
- constructor(browser, logger) {
60
+ #locatorClass;
61
+ #options;
62
+ constructor(browser, logger, options, locatorClass) {
58
63
  this.browser = browser;
59
64
  this.logger = logger;
60
- this.#networkCollector = new NetworkCollector(this.browser, (page, collect) => {
61
- page.on('request', request => {
62
- collect(request);
63
- });
64
- });
65
- this.#consoleCollector = new PageCollector(this.browser, (page, collect) => {
66
- page.on('console', event => {
67
- collect(event);
68
- });
69
- page.on('pageerror', event => {
70
- collect(event);
71
- });
72
- });
65
+ this.#locatorClass = locatorClass;
66
+ this.#options = options;
67
+ this.#networkCollector = new NetworkCollector(this.browser, undefined, this.#options.experimentalIncludeAllPages);
68
+ this.#consoleCollector = new PageCollector(this.browser, collect => {
69
+ return {
70
+ console: event => {
71
+ collect(event);
72
+ },
73
+ pageerror: event => {
74
+ if (event instanceof Error) {
75
+ collect(event);
76
+ }
77
+ else {
78
+ const error = new Error(`${event}`);
79
+ error.stack = undefined;
80
+ collect(error);
81
+ }
82
+ },
83
+ };
84
+ }, this.#options.experimentalIncludeAllPages);
73
85
  }
74
86
  async #init() {
75
87
  await this.createPagesSnapshot();
@@ -77,18 +89,60 @@ export class McpContext {
77
89
  await this.#networkCollector.init();
78
90
  await this.#consoleCollector.init();
79
91
  }
80
- static async from(browser, logger) {
81
- const context = new McpContext(browser, logger);
92
+ static async from(browser, logger, opts,
93
+ /* Let tests use unbundled Locator class to avoid overly strict checks within puppeteer that fail when mixing bundled and unbundled class instances */
94
+ locatorClass = Locator) {
95
+ const context = new McpContext(browser, logger, opts, locatorClass);
82
96
  await context.#init();
83
97
  return context;
84
98
  }
85
- getNetworkRequests() {
99
+ resolveCdpRequestId(cdpRequestId) {
100
+ const selectedPage = this.getSelectedPage();
101
+ if (!cdpRequestId) {
102
+ this.logger('no network request');
103
+ return;
104
+ }
105
+ const request = this.#networkCollector.find(selectedPage, request => {
106
+ // @ts-expect-error id is internal.
107
+ return request.id === cdpRequestId;
108
+ });
109
+ if (!request) {
110
+ this.logger('no network request for ' + cdpRequestId);
111
+ return;
112
+ }
113
+ return this.#networkCollector.getIdForResource(request);
114
+ }
115
+ resolveCdpElementId(cdpBackendNodeId) {
116
+ if (!cdpBackendNodeId) {
117
+ this.logger('no cdpBackendNodeId');
118
+ return;
119
+ }
120
+ // TODO: index by backendNodeId instead.
121
+ const queue = [this.#textSnapshot?.root];
122
+ while (queue.length) {
123
+ const current = queue.pop();
124
+ if (current.backendNodeId === cdpBackendNodeId) {
125
+ return current.id;
126
+ }
127
+ for (const child of current.children) {
128
+ queue.push(child);
129
+ }
130
+ }
131
+ return;
132
+ }
133
+ getNetworkRequests(includePreservedRequests) {
86
134
  const page = this.getSelectedPage();
87
- return this.#networkCollector.getData(page);
135
+ return this.#networkCollector.getData(page, includePreservedRequests);
88
136
  }
89
- getConsoleData() {
137
+ getConsoleData(includePreservedMessages) {
90
138
  const page = this.getSelectedPage();
91
- return this.#consoleCollector.getData(page);
139
+ return this.#consoleCollector.getData(page, includePreservedMessages);
140
+ }
141
+ getConsoleMessageStableId(message) {
142
+ return this.#consoleCollector.getIdForResource(message);
143
+ }
144
+ getConsoleMessageById(id) {
145
+ return this.#consoleCollector.getById(this.getSelectedPage(), id);
92
146
  }
93
147
  async newPage() {
94
148
  const page = await this.browser.newPage();
@@ -106,17 +160,8 @@ export class McpContext {
106
160
  this.setSelectedPageIdx(0);
107
161
  await page.close({ runBeforeUnload: false });
108
162
  }
109
- getNetworkRequestByUrl(url) {
110
- const requests = this.getNetworkRequests();
111
- if (!requests.length) {
112
- throw new Error('No requests found for selected page');
113
- }
114
- for (const request of requests) {
115
- if (request.url() === url) {
116
- return request;
117
- }
118
- }
119
- throw new Error('Request not found for selected page');
163
+ getNetworkRequestById(reqid) {
164
+ return this.#networkCollector.getById(this.getSelectedPage(), reqid);
120
165
  }
121
166
  setNetworkConditions(conditions) {
122
167
  const page = this.getSelectedPage();
@@ -226,19 +271,88 @@ export class McpContext {
226
271
  * Creates a snapshot of the pages.
227
272
  */
228
273
  async createPagesSnapshot() {
229
- this.#pages = await this.browser.pages();
274
+ const allPages = await this.browser.pages(this.#options.experimentalIncludeAllPages);
275
+ this.#pages = allPages.filter(page => {
276
+ // If we allow debugging DevTools windows, return all pages.
277
+ // If we are in regular mode, the user should only see non-DevTools page.
278
+ return (this.#options.experimentalDevToolsDebugging ||
279
+ !page.url().startsWith('devtools://'));
280
+ });
281
+ await this.detectOpenDevToolsWindows();
230
282
  return this.#pages;
231
283
  }
284
+ async detectOpenDevToolsWindows() {
285
+ this.logger('Detecting open DevTools windows');
286
+ const pages = await this.browser.pages(this.#options.experimentalIncludeAllPages);
287
+ this.#pageToDevToolsPage = new Map();
288
+ for (const devToolsPage of pages) {
289
+ if (devToolsPage.url().startsWith('devtools://')) {
290
+ try {
291
+ this.logger('Calling getTargetInfo for ' + devToolsPage.url());
292
+ const data = await devToolsPage
293
+ // @ts-expect-error no types for _client().
294
+ ._client()
295
+ .send('Target.getTargetInfo');
296
+ const devtoolsPageTitle = data.targetInfo.title;
297
+ const urlLike = extractUrlLikeFromDevToolsTitle(devtoolsPageTitle);
298
+ if (!urlLike) {
299
+ continue;
300
+ }
301
+ // TODO: lookup without a loop.
302
+ for (const page of this.#pages) {
303
+ if (urlsEqual(page.url(), urlLike)) {
304
+ this.#pageToDevToolsPage.set(page, devToolsPage);
305
+ }
306
+ }
307
+ }
308
+ catch (error) {
309
+ this.logger('Issue occurred while trying to find DevTools', error);
310
+ }
311
+ }
312
+ }
313
+ }
232
314
  getPages() {
233
315
  return this.#pages;
234
316
  }
317
+ getDevToolsPage(page) {
318
+ return this.#pageToDevToolsPage.get(page);
319
+ }
320
+ async getDevToolsData() {
321
+ try {
322
+ this.logger('Getting DevTools UI data');
323
+ const selectedPage = this.getSelectedPage();
324
+ const devtoolsPage = this.getDevToolsPage(selectedPage);
325
+ if (!devtoolsPage) {
326
+ this.logger('No DevTools page detected');
327
+ return {};
328
+ }
329
+ const { cdpRequestId, cdpBackendNodeId } = await devtoolsPage.evaluate(async () => {
330
+ // @ts-expect-error no types
331
+ const UI = await import('/bundled/ui/legacy/legacy.js');
332
+ // @ts-expect-error no types
333
+ const SDK = await import('/bundled/core/sdk/sdk.js');
334
+ const request = UI.Context.Context.instance().flavor(SDK.NetworkRequest.NetworkRequest);
335
+ const node = UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode);
336
+ return {
337
+ cdpRequestId: request?.requestId(),
338
+ cdpBackendNodeId: node?.backendNodeId(),
339
+ };
340
+ });
341
+ return { cdpBackendNodeId, cdpRequestId };
342
+ }
343
+ catch (err) {
344
+ this.logger('error getting devtools data', err);
345
+ }
346
+ return {};
347
+ }
235
348
  /**
236
349
  * Creates a text snapshot of a page.
237
350
  */
238
- async createTextSnapshot() {
351
+ async createTextSnapshot(verbose = false, devtoolsData = undefined) {
239
352
  const page = this.getSelectedPage();
240
353
  const rootNode = await page.accessibility.snapshot({
241
354
  includeIframes: true,
355
+ interestingOnly: !verbose,
242
356
  });
243
357
  if (!rootNode) {
244
358
  return;
@@ -273,6 +387,10 @@ export class McpContext {
273
387
  snapshotId: String(snapshotId),
274
388
  idToNode,
275
389
  };
390
+ const data = devtoolsData ?? (await this.getDevToolsData());
391
+ if (data?.cdpBackendNodeId) {
392
+ this.#textSnapshot.selectedElementUid = this.resolveCdpElementId(data?.cdpBackendNodeId);
393
+ }
276
394
  }
277
395
  getTextSnapshot() {
278
396
  return this.#textSnapshot;
@@ -316,4 +434,35 @@ export class McpContext {
316
434
  const waitForHelper = this.getWaitForHelper(page, cpuMultiplier, networkMultiplier);
317
435
  return waitForHelper.waitForEventsAfterAction(action);
318
436
  }
437
+ getNetworkRequestStableId(request) {
438
+ return this.#networkCollector.getIdForResource(request);
439
+ }
440
+ waitForTextOnPage({ text, timeout, }) {
441
+ const page = this.getSelectedPage();
442
+ const frames = page.frames();
443
+ const locator = this.#locatorClass.race(frames.flatMap(frame => [
444
+ frame.locator(`aria/${text}`),
445
+ frame.locator(`text/${text}`),
446
+ ]));
447
+ if (timeout) {
448
+ locator.setTimeout(timeout);
449
+ }
450
+ return locator.wait();
451
+ }
452
+ /**
453
+ * We need to ignore favicon request as they make our test flaky
454
+ */
455
+ async setUpNetworkCollectorForTesting() {
456
+ this.#networkCollector = new NetworkCollector(this.browser, collect => {
457
+ return {
458
+ request: req => {
459
+ if (req.url().includes('favicon.ico')) {
460
+ return;
461
+ }
462
+ collect(req);
463
+ },
464
+ };
465
+ });
466
+ await this.#networkCollector.init();
467
+ }
319
468
  }
@@ -1,22 +1,28 @@
1
- import { formatConsoleEvent } from './formatters/consoleFormatter.js';
1
+ import { formatConsoleEventShort, formatConsoleEventVerbose, } from './formatters/consoleFormatter.js';
2
2
  import { getFormattedHeaderValue, getFormattedResponseBody, getFormattedRequestBody, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js';
3
- import { formatA11ySnapshot } from './formatters/snapshotFormatter.js';
3
+ import { formatSnapshotNode } from './formatters/snapshotFormatter.js';
4
4
  import { handleDialog } from './tools/pages.js';
5
5
  import { paginate } from './utils/pagination.js';
6
6
  export class McpResponse {
7
7
  #includePages = false;
8
- #includeSnapshot = false;
9
- #attachedNetworkRequestData;
10
- #includeConsoleData = false;
8
+ #snapshotParams;
9
+ #attachedNetworkRequestId;
10
+ #attachedConsoleMessageId;
11
11
  #textResponseLines = [];
12
- #formattedConsoleData;
13
12
  #images = [];
14
13
  #networkRequestsOptions;
14
+ #consoleDataOptions;
15
+ #devToolsData;
16
+ attachDevToolsData(data) {
17
+ this.#devToolsData = data;
18
+ }
15
19
  setIncludePages(value) {
16
20
  this.#includePages = value;
17
21
  }
18
- setIncludeSnapshot(value) {
19
- this.#includeSnapshot = value;
22
+ includeSnapshot(params) {
23
+ this.#snapshotParams = params ?? {
24
+ verbose: false,
25
+ };
20
26
  }
21
27
  setIncludeNetworkRequests(value, options) {
22
28
  if (!value) {
@@ -32,16 +38,33 @@ export class McpResponse {
32
38
  }
33
39
  : undefined,
34
40
  resourceTypes: options?.resourceTypes,
41
+ includePreservedRequests: options?.includePreservedRequests,
42
+ networkRequestIdInDevToolsUI: options?.networkRequestIdInDevToolsUI,
35
43
  };
36
44
  }
37
- setIncludeConsoleData(value) {
38
- this.#includeConsoleData = value;
39
- }
40
- attachNetworkRequest(url) {
41
- this.#attachedNetworkRequestData = {
42
- networkRequestUrl: url,
45
+ setIncludeConsoleData(value, options) {
46
+ if (!value) {
47
+ this.#consoleDataOptions = undefined;
48
+ return;
49
+ }
50
+ this.#consoleDataOptions = {
51
+ include: value,
52
+ pagination: options?.pageSize || options?.pageIdx
53
+ ? {
54
+ pageSize: options.pageSize,
55
+ pageIdx: options.pageIdx,
56
+ }
57
+ : undefined,
58
+ types: options?.types,
59
+ includePreservedMessages: options?.includePreservedMessages,
43
60
  };
44
61
  }
62
+ attachNetworkRequest(reqid) {
63
+ this.#attachedNetworkRequestId = reqid;
64
+ }
65
+ attachConsoleMessage(msgid) {
66
+ this.#attachedConsoleMessageId = msgid;
67
+ }
45
68
  get includePages() {
46
69
  return this.#includePages;
47
70
  }
@@ -49,14 +72,20 @@ export class McpResponse {
49
72
  return this.#networkRequestsOptions?.include ?? false;
50
73
  }
51
74
  get includeConsoleData() {
52
- return this.#includeConsoleData;
75
+ return this.#consoleDataOptions?.include ?? false;
53
76
  }
54
- get attachedNetworkRequestUrl() {
55
- return this.#attachedNetworkRequestData?.networkRequestUrl;
77
+ get attachedNetworkRequestId() {
78
+ return this.#attachedNetworkRequestId;
56
79
  }
57
80
  get networkRequestsPageIdx() {
58
81
  return this.#networkRequestsOptions?.pagination?.pageIdx;
59
82
  }
83
+ get consoleMessagesPageIdx() {
84
+ return this.#consoleDataOptions?.pagination?.pageIdx;
85
+ }
86
+ get consoleMessagesTypes() {
87
+ return this.#consoleDataOptions?.types;
88
+ }
60
89
  appendResponseLine(value) {
61
90
  this.#textResponseLines.push(value);
62
91
  }
@@ -69,37 +98,111 @@ export class McpResponse {
69
98
  get images() {
70
99
  return this.#images;
71
100
  }
72
- get includeSnapshot() {
73
- return this.#includeSnapshot;
101
+ get snapshotParams() {
102
+ return this.#snapshotParams;
74
103
  }
75
104
  async handle(toolName, context) {
76
105
  if (this.#includePages) {
77
106
  await context.createPagesSnapshot();
78
107
  }
79
- if (this.#includeSnapshot) {
80
- await context.createTextSnapshot();
108
+ let formattedSnapshot;
109
+ if (this.#snapshotParams) {
110
+ await context.createTextSnapshot(this.#snapshotParams.verbose, this.#devToolsData);
111
+ const snapshot = context.getTextSnapshot();
112
+ if (snapshot) {
113
+ if (this.#snapshotParams.filePath) {
114
+ await context.saveFile(new TextEncoder().encode(formatSnapshotNode(snapshot.root, snapshot)), this.#snapshotParams.filePath);
115
+ formattedSnapshot = `Saved snapshot to ${this.#snapshotParams.filePath}.`;
116
+ }
117
+ else {
118
+ formattedSnapshot = formatSnapshotNode(snapshot.root, snapshot);
119
+ }
120
+ }
81
121
  }
82
- let formattedConsoleMessages;
83
- if (this.#attachedNetworkRequestData?.networkRequestUrl) {
84
- const request = context.getNetworkRequestByUrl(this.#attachedNetworkRequestData.networkRequestUrl);
85
- this.#attachedNetworkRequestData.requestBody =
86
- await getFormattedRequestBody(request);
122
+ const bodies = {};
123
+ if (this.#attachedNetworkRequestId) {
124
+ const request = context.getNetworkRequestById(this.#attachedNetworkRequestId);
125
+ bodies.requestBody = await getFormattedRequestBody(request);
87
126
  const response = request.response();
88
127
  if (response) {
89
- this.#attachedNetworkRequestData.responseBody =
90
- await getFormattedResponseBody(response);
128
+ bodies.responseBody = await getFormattedResponseBody(response);
129
+ }
130
+ }
131
+ let consoleData;
132
+ if (this.#attachedConsoleMessageId) {
133
+ const message = context.getConsoleMessageById(this.#attachedConsoleMessageId);
134
+ const consoleMessageStableId = this.#attachedConsoleMessageId;
135
+ if ('args' in message) {
136
+ const consoleMessage = message;
137
+ consoleData = {
138
+ consoleMessageStableId,
139
+ type: consoleMessage.type(),
140
+ message: consoleMessage.text(),
141
+ args: await Promise.all(consoleMessage.args().map(async (arg) => {
142
+ const stringArg = await arg.jsonValue().catch(() => {
143
+ // Ignore errors.
144
+ });
145
+ return typeof stringArg === 'object'
146
+ ? JSON.stringify(stringArg)
147
+ : String(stringArg);
148
+ })),
149
+ };
150
+ }
151
+ else {
152
+ consoleData = {
153
+ consoleMessageStableId,
154
+ type: 'error',
155
+ message: message.message,
156
+ args: [],
157
+ };
91
158
  }
92
159
  }
93
- if (this.#includeConsoleData) {
94
- const consoleMessages = context.getConsoleData();
95
- if (consoleMessages) {
96
- formattedConsoleMessages = await Promise.all(consoleMessages.map(message => formatConsoleEvent(message)));
97
- this.#formattedConsoleData = formattedConsoleMessages;
160
+ let consoleListData;
161
+ if (this.#consoleDataOptions?.include) {
162
+ let messages = context.getConsoleData(this.#consoleDataOptions.includePreservedMessages);
163
+ if (this.#consoleDataOptions.types?.length) {
164
+ const normalizedTypes = new Set(this.#consoleDataOptions.types);
165
+ messages = messages.filter(message => {
166
+ if ('type' in message) {
167
+ return normalizedTypes.has(message.type());
168
+ }
169
+ return normalizedTypes.has('error');
170
+ });
98
171
  }
172
+ consoleListData = await Promise.all(messages.map(async (item) => {
173
+ const consoleMessageStableId = context.getConsoleMessageStableId(item);
174
+ if ('args' in item) {
175
+ const consoleMessage = item;
176
+ return {
177
+ consoleMessageStableId,
178
+ type: consoleMessage.type(),
179
+ message: consoleMessage.text(),
180
+ args: await Promise.all(consoleMessage.args().map(async (arg) => {
181
+ const stringArg = await arg.jsonValue().catch(() => {
182
+ // Ignore errors.
183
+ });
184
+ return typeof stringArg === 'object'
185
+ ? JSON.stringify(stringArg)
186
+ : String(stringArg);
187
+ })),
188
+ };
189
+ }
190
+ return {
191
+ consoleMessageStableId,
192
+ type: 'error',
193
+ message: item.message,
194
+ args: [],
195
+ };
196
+ }));
99
197
  }
100
- return this.format(toolName, context);
198
+ return this.format(toolName, context, {
199
+ bodies,
200
+ consoleData,
201
+ consoleListData,
202
+ formattedSnapshot,
203
+ });
101
204
  }
102
- format(toolName, context) {
205
+ format(toolName, context, data) {
103
206
  const response = [`# ${toolName} response`];
104
207
  for (const line of this.#textResponseLines) {
105
208
  response.push(line);
@@ -133,17 +236,14 @@ Call ${handleDialog.name} to handle it before continuing.`);
133
236
  }
134
237
  response.push(...parts);
135
238
  }
136
- if (this.#includeSnapshot) {
137
- const snapshot = context.getTextSnapshot();
138
- if (snapshot) {
139
- const formattedSnapshot = formatA11ySnapshot(snapshot.root);
140
- response.push('## Page content');
141
- response.push(formattedSnapshot);
142
- }
239
+ if (data.formattedSnapshot) {
240
+ response.push('## Page content');
241
+ response.push(data.formattedSnapshot);
143
242
  }
144
- response.push(...this.#getIncludeNetworkRequestsData(context));
243
+ response.push(...this.#formatNetworkRequestData(context, data.bodies));
244
+ response.push(...this.#formatConsoleData(data.consoleData));
145
245
  if (this.#networkRequestsOptions?.include) {
146
- let requests = context.getNetworkRequests();
246
+ let requests = context.getNetworkRequests(this.#networkRequestsOptions?.includePreservedRequests);
147
247
  // Apply resource type filtering if specified
148
248
  if (this.#networkRequestsOptions.resourceTypes?.length) {
149
249
  const normalizedTypes = new Set(this.#networkRequestsOptions.resourceTypes);
@@ -157,17 +257,21 @@ Call ${handleDialog.name} to handle it before continuing.`);
157
257
  const data = this.#dataWithPagination(requests, this.#networkRequestsOptions.pagination);
158
258
  response.push(...data.info);
159
259
  for (const request of data.items) {
160
- response.push(getShortDescriptionForRequest(request));
260
+ response.push(getShortDescriptionForRequest(request, context.getNetworkRequestStableId(request), context.getNetworkRequestStableId(request) ===
261
+ this.#networkRequestsOptions?.networkRequestIdInDevToolsUI));
161
262
  }
162
263
  }
163
264
  else {
164
265
  response.push('No requests found.');
165
266
  }
166
267
  }
167
- if (this.#includeConsoleData && this.#formattedConsoleData) {
268
+ if (this.#consoleDataOptions?.include) {
269
+ const messages = data.consoleListData ?? [];
168
270
  response.push('## Console messages');
169
- if (this.#formattedConsoleData.length) {
170
- response.push(...this.#formattedConsoleData);
271
+ if (messages.length) {
272
+ const data = this.#dataWithPagination(messages, this.#consoleDataOptions.pagination);
273
+ response.push(...data.info);
274
+ response.push(...data.items.map(message => formatConsoleEventShort(message)));
171
275
  }
172
276
  else {
173
277
  response.push('<no console messages found>');
@@ -206,22 +310,30 @@ Call ${handleDialog.name} to handle it before continuing.`);
206
310
  items: paginationResult.items,
207
311
  };
208
312
  }
209
- #getIncludeNetworkRequestsData(context) {
313
+ #formatConsoleData(data) {
314
+ const response = [];
315
+ if (!data) {
316
+ return response;
317
+ }
318
+ response.push(formatConsoleEventVerbose(data));
319
+ return response;
320
+ }
321
+ #formatNetworkRequestData(context, data) {
210
322
  const response = [];
211
- const url = this.#attachedNetworkRequestData?.networkRequestUrl;
212
- if (!url) {
323
+ const id = this.#attachedNetworkRequestId;
324
+ if (!id) {
213
325
  return response;
214
326
  }
215
- const httpRequest = context.getNetworkRequestByUrl(url);
327
+ const httpRequest = context.getNetworkRequestById(id);
216
328
  response.push(`## Request ${httpRequest.url()}`);
217
329
  response.push(`Status: ${getStatusFromRequest(httpRequest)}`);
218
330
  response.push(`### Request Headers`);
219
331
  for (const line of getFormattedHeaderValue(httpRequest.headers())) {
220
332
  response.push(line);
221
333
  }
222
- if (this.#attachedNetworkRequestData?.requestBody) {
334
+ if (data.requestBody) {
223
335
  response.push(`### Request Body`);
224
- response.push(this.#attachedNetworkRequestData.requestBody);
336
+ response.push(data.requestBody);
225
337
  }
226
338
  const httpResponse = httpRequest.response();
227
339
  if (httpResponse) {
@@ -230,9 +342,9 @@ Call ${handleDialog.name} to handle it before continuing.`);
230
342
  response.push(line);
231
343
  }
232
344
  }
233
- if (this.#attachedNetworkRequestData?.responseBody) {
345
+ if (data.responseBody) {
234
346
  response.push(`### Response Body`);
235
- response.push(this.#attachedNetworkRequestData.responseBody);
347
+ response.push(data.responseBody);
236
348
  }
237
349
  const httpFailure = httpRequest.failure();
238
350
  if (httpFailure) {
@@ -244,7 +356,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
244
356
  response.push(`### Redirect chain`);
245
357
  let indent = 0;
246
358
  for (const request of redirectChain.reverse()) {
247
- response.push(`${' '.repeat(indent)}${getShortDescriptionForRequest(request)}`);
359
+ response.push(`${' '.repeat(indent)}${getShortDescriptionForRequest(request, context.getNetworkRequestStableId(request))}`);
248
360
  indent++;
249
361
  }
250
362
  }