chrome-devtools-mcp 0.1.0 → 0.2.1

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 (114) hide show
  1. package/README.md +33 -11
  2. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Progress.js +60 -53
  3. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Settings.js +3 -32
  4. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/GdpClient.js +1 -1
  5. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +5 -2
  6. package/build/node_modules/chrome-devtools-frontend/front_end/core/i18n/i18n.js +35 -8
  7. package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +2 -0
  8. package/build/node_modules/chrome-devtools-frontend/front_end/core/root/Runtime.js +4 -1
  9. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +11 -10
  10. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSModel.js +1 -1
  11. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParserMatchers.js +24 -4
  12. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +1 -1
  13. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/EnhancedTracesParser.js +29 -24
  14. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +1 -1
  15. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +1 -1
  16. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +9 -15
  17. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RemoteObject.js +1 -1
  18. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ResourceTreeModel.js +1 -1
  19. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RuntimeModel.js +1 -1
  20. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ServiceWorkerManager.js +1 -1
  21. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +4 -31
  22. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TraceObject.js +5 -2
  23. package/build/node_modules/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +4 -4
  24. package/build/node_modules/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +12 -0
  25. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.js +6 -4
  26. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js +259 -179
  27. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js +366 -0
  28. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/UnitFormatters.js +10 -1
  29. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AICallTree.js +366 -0
  30. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIContext.js +75 -0
  31. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIQueries.js +105 -0
  32. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CSSWorkspaceBinding.js +243 -0
  33. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +407 -0
  34. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/ContentProviderBasedProject.js +130 -0
  35. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DebuggerLanguagePlugins.js +992 -0
  36. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DebuggerWorkspaceBinding.js +574 -0
  37. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DefaultScriptMapping.js +112 -0
  38. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/FileUtils.js +186 -0
  39. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/LiveLocation.js +60 -0
  40. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/NetworkProject.js +107 -0
  41. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/PresentationConsoleMessageHelper.js +244 -0
  42. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/ResourceMapping.js +473 -0
  43. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/ResourceScriptMapping.js +399 -0
  44. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/ResourceUtils.js +87 -0
  45. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/SASSSourceMapping.js +181 -0
  46. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/StylesSourceMapping.js +268 -0
  47. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/TempFile.js +55 -0
  48. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/bindings.js +20 -0
  49. package/build/node_modules/chrome-devtools-frontend/front_end/models/crux-manager/CrUXManager.js +283 -0
  50. package/build/node_modules/chrome-devtools-frontend/front_end/models/crux-manager/crux-manager.js +4 -0
  51. package/build/node_modules/chrome-devtools-frontend/front_end/models/emulation/DeviceModeModel.js +775 -0
  52. package/build/node_modules/chrome-devtools-frontend/front_end/models/emulation/EmulatedDevices.js +1706 -0
  53. package/build/node_modules/chrome-devtools-frontend/front_end/models/emulation/emulation.js +6 -0
  54. package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/FormatterWorkerPool.js +131 -0
  55. package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/ScriptFormatter.js +77 -0
  56. package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/formatter.js +6 -0
  57. package/build/node_modules/chrome-devtools-frontend/front_end/models/geometry/GeometryImpl.js +347 -0
  58. package/build/node_modules/chrome-devtools-frontend/front_end/models/geometry/geometry.js +4 -0
  59. package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/NamesResolver.js +626 -0
  60. package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/ScopeChainModel.js +59 -0
  61. package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/ScopeTreeCache.js +32 -0
  62. package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/source_map_scopes.js +7 -0
  63. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTrace.js +4 -0
  64. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceImpl.js +67 -0
  65. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceModel.js +97 -0
  66. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/Trie.js +113 -0
  67. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/stack_trace.js +5 -0
  68. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/stack_trace_impl.js +7 -0
  69. package/build/node_modules/chrome-devtools-frontend/front_end/models/text_utils/TextUtils.js +23 -0
  70. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/ModelImpl.js +4 -9
  71. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/Processor.js +16 -8
  72. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/AuctionWorkletsHandler.js +1 -1
  73. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/FramesHandler.js +2 -2
  74. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/LayoutShiftsHandler.js +3 -4
  75. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.js +10 -9
  76. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/ScreenshotsHandler.js +0 -1
  77. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/ScriptsHandler.js +4 -4
  78. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/UserInteractionsHandler.js +2 -10
  79. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/UserTimingsHandler.js +3 -4
  80. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/SamplesIntegrator.js +8 -6
  81. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Trace.js +1 -1
  82. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/CLSCulprits.js +1 -1
  83. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DocumentLatency.js +5 -4
  84. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DuplicatedJavaScript.js +1 -1
  85. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/INPBreakdown.js +1 -1
  86. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ImageDelivery.js +1 -1
  87. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +1 -1
  88. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPDiscovery.js +1 -1
  89. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ModernHTTP.js +1 -1
  90. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +1 -1
  91. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/RenderBlocking.js +1 -1
  92. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +21 -21
  93. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver/SourceMapsResolver.js +201 -0
  94. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver/trace_source_maps_resolver.js +4 -0
  95. package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/FileManager.js +64 -0
  96. package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/IgnoreListManager.js +511 -0
  97. package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/SearchConfig.js +113 -0
  98. package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/UISourceCode.js +563 -0
  99. package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/WorkspaceImpl.js +204 -0
  100. package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/workspace.js +9 -0
  101. package/build/src/McpContext.js +84 -19
  102. package/build/src/McpResponse.js +8 -7
  103. package/build/src/WaitForHelper.js +123 -0
  104. package/build/src/browser.js +15 -10
  105. package/build/src/index.js +4 -6
  106. package/build/src/logger.js +1 -0
  107. package/build/src/tools/input.js +12 -13
  108. package/build/src/tools/pages.js +2 -3
  109. package/build/src/tools/performance.js +31 -4
  110. package/build/src/tools/screenshot.js +1 -1
  111. package/build/src/tools/script.js +40 -15
  112. package/build/src/trace-processing/parse.js +26 -22
  113. package/package.json +15 -12
  114. package/build/src/waitForHelpers.js +0 -109
@@ -6,7 +6,6 @@
6
6
  import z from 'zod';
7
7
  import { defineTool } from './ToolDefinition.js';
8
8
  import { ToolCategories } from './categories.js';
9
- import { waitForEventsAfterAction } from '../waitForHelpers.js';
10
9
  export const click = defineTool({
11
10
  name: 'click',
12
11
  description: `Clicks on the provided element`,
@@ -16,7 +15,7 @@ export const click = defineTool({
16
15
  },
17
16
  schema: {
18
17
  uid: z
19
- .number()
18
+ .string()
20
19
  .describe('The uid of an element on the page from the page content snapshot'),
21
20
  dblClick: z
22
21
  .boolean()
@@ -27,7 +26,7 @@ export const click = defineTool({
27
26
  const uid = request.params.uid;
28
27
  const handle = await context.getElementByUid(uid);
29
28
  try {
30
- await waitForEventsAfterAction(handle.frame.page(), async () => {
29
+ await context.waitForEventsAfterAction(async () => {
31
30
  await handle.asLocator().click({
32
31
  count: request.params.dblClick ? 2 : 1,
33
32
  });
@@ -51,14 +50,14 @@ export const hover = defineTool({
51
50
  },
52
51
  schema: {
53
52
  uid: z
54
- .number()
53
+ .string()
55
54
  .describe('The uid of an element on the page from the page content snapshot'),
56
55
  },
57
56
  handler: async (request, response, context) => {
58
57
  const uid = request.params.uid;
59
58
  const handle = await context.getElementByUid(uid);
60
59
  try {
61
- await waitForEventsAfterAction(handle.frame.page(), async () => {
60
+ await context.waitForEventsAfterAction(async () => {
62
61
  await handle.asLocator().hover();
63
62
  });
64
63
  response.appendResponseLine(`Successfully hovered over the element`);
@@ -78,14 +77,14 @@ export const fill = defineTool({
78
77
  },
79
78
  schema: {
80
79
  uid: z
81
- .number()
80
+ .string()
82
81
  .describe('The uid of an element on the page from the page content snapshot'),
83
82
  value: z.string().describe('The value to fill in'),
84
83
  },
85
84
  handler: async (request, response, context) => {
86
85
  const handle = await context.getElementByUid(request.params.uid);
87
86
  try {
88
- await waitForEventsAfterAction(handle.frame.page(), async () => {
87
+ await context.waitForEventsAfterAction(async () => {
89
88
  await handle.asLocator().fill(request.params.value);
90
89
  });
91
90
  response.appendResponseLine(`Successfully filled out the element`);
@@ -104,14 +103,14 @@ export const drag = defineTool({
104
103
  readOnlyHint: false,
105
104
  },
106
105
  schema: {
107
- from_uid: z.number().describe('The uid of the element to drag'),
108
- to_uid: z.number().describe('The uid of the element to drop into'),
106
+ from_uid: z.string().describe('The uid of the element to drag'),
107
+ to_uid: z.string().describe('The uid of the element to drop into'),
109
108
  },
110
109
  handler: async (request, response, context) => {
111
110
  const fromHandle = await context.getElementByUid(request.params.from_uid);
112
111
  const toHandle = await context.getElementByUid(request.params.to_uid);
113
112
  try {
114
- await waitForEventsAfterAction(fromHandle.frame.page(), async () => {
113
+ await context.waitForEventsAfterAction(async () => {
115
114
  await fromHandle.drag(toHandle);
116
115
  await new Promise(resolve => setTimeout(resolve, 50));
117
116
  await toHandle.drop(fromHandle);
@@ -135,7 +134,7 @@ export const fillForm = defineTool({
135
134
  schema: {
136
135
  elements: z
137
136
  .array(z.object({
138
- uid: z.number().describe('The uid of the element to fill out'),
137
+ uid: z.string().describe('The uid of the element to fill out'),
139
138
  value: z.string().describe('Value for the element'),
140
139
  }))
141
140
  .describe('Elements from snapshot to fill out.'),
@@ -144,7 +143,7 @@ export const fillForm = defineTool({
144
143
  for (const element of request.params.elements) {
145
144
  const handle = await context.getElementByUid(element.uid);
146
145
  try {
147
- await waitForEventsAfterAction(handle.frame.page(), async () => {
146
+ await context.waitForEventsAfterAction(async () => {
148
147
  await handle.asLocator().fill(element.value);
149
148
  });
150
149
  }
@@ -165,7 +164,7 @@ export const uploadFile = defineTool({
165
164
  },
166
165
  schema: {
167
166
  uid: z
168
- .number()
167
+ .string()
169
168
  .describe('The uid of the file input element or an element that will open file chooser on the page from the page content snapshot'),
170
169
  filePath: z.string().describe('The local path of the file to upload'),
171
170
  },
@@ -6,7 +6,6 @@
6
6
  import z from 'zod';
7
7
  import { defineTool } from './ToolDefinition.js';
8
8
  import { ToolCategories } from './categories.js';
9
- import { waitForEventsAfterAction } from '../waitForHelpers.js';
10
9
  export const listPages = defineTool({
11
10
  name: 'list_pages',
12
11
  description: `Get a list of pages open in the browser.`,
@@ -69,7 +68,7 @@ export const newPage = defineTool({
69
68
  },
70
69
  handler: async (request, response, context) => {
71
70
  const page = await context.newPage();
72
- await waitForEventsAfterAction(page, async () => {
71
+ await context.waitForEventsAfterAction(async () => {
73
72
  await page.goto(request.params.url);
74
73
  });
75
74
  response.setIncludePages(true);
@@ -87,7 +86,7 @@ export const navigatePage = defineTool({
87
86
  },
88
87
  handler: async (request, response, context) => {
89
88
  const page = context.getSelectedPage();
90
- await waitForEventsAfterAction(page, async () => {
89
+ await context.waitForEventsAfterAction(async () => {
91
90
  await page.goto(request.params.url);
92
91
  });
93
92
  response.setIncludePages(true);
@@ -5,12 +5,12 @@
5
5
  */
6
6
  import z from 'zod';
7
7
  import { defineTool } from './ToolDefinition.js';
8
- import { insightOutput, parseRawTraceBuffer } from '../trace-processing/parse.js';
8
+ import { getInsightOutput, getTraceSummary, parseRawTraceBuffer, } from '../trace-processing/parse.js';
9
9
  import { logger } from '../logger.js';
10
10
  import { ToolCategories } from './categories.js';
11
11
  export const startTrace = defineTool({
12
12
  name: 'performance_start_trace',
13
- description: 'Starts a performance trace recording',
13
+ description: 'Starts a performance trace recording on the selected page.',
14
14
  annotations: {
15
15
  category: ToolCategories.PERFORMANCE,
16
16
  readOnlyHint: true,
@@ -77,7 +77,7 @@ export const startTrace = defineTool({
77
77
  });
78
78
  export const stopTrace = defineTool({
79
79
  name: 'performance_stop_trace',
80
- description: 'Stops the active performance trace recording',
80
+ description: 'Stops the active performance trace recording on the selected page.',
81
81
  annotations: {
82
82
  category: ToolCategories.PERFORMANCE,
83
83
  readOnlyHint: true,
@@ -91,13 +91,40 @@ export const stopTrace = defineTool({
91
91
  await stopTracingAndAppendOutput(page, response, context);
92
92
  },
93
93
  });
94
+ export const analyzeInsight = defineTool({
95
+ name: 'performance_analyze_insight',
96
+ description: 'Provides more detailed information on a specific Performance Insight that was highlighed in the results of a trace recording.',
97
+ annotations: {
98
+ category: ToolCategories.PERFORMANCE,
99
+ readOnlyHint: true,
100
+ },
101
+ schema: {
102
+ insightName: z
103
+ .string()
104
+ .describe('The name of the Insight you want more information on. For example: "DocumentLatency" or "LCPBreakdown"'),
105
+ },
106
+ handler: async (request, response, context) => {
107
+ const lastRecording = context.recordedTraces().at(-1);
108
+ if (!lastRecording) {
109
+ response.appendResponseLine('No recorded traces found. Record a performance trace so you have Insights to analyze.');
110
+ return;
111
+ }
112
+ const insightOutput = getInsightOutput(lastRecording, request.params.insightName);
113
+ if ('error' in insightOutput) {
114
+ response.appendResponseLine(insightOutput.error);
115
+ return;
116
+ }
117
+ response.appendResponseLine(insightOutput.output);
118
+ },
119
+ });
94
120
  async function stopTracingAndAppendOutput(page, response, context) {
95
121
  try {
96
122
  const traceEventsBuffer = await page.tracing.stop();
97
123
  const result = await parseRawTraceBuffer(traceEventsBuffer);
98
124
  response.appendResponseLine('The performance trace has been stopped.');
99
125
  if (result) {
100
- const insightText = insightOutput(result);
126
+ context.storeTraceRecording(result);
127
+ const insightText = getTraceSummary(result);
101
128
  if (insightText) {
102
129
  response.appendResponseLine('Insights with performance opportunities:');
103
130
  response.appendResponseLine(insightText);
@@ -19,7 +19,7 @@ export const screenshot = defineTool({
19
19
  .default('png')
20
20
  .describe('Type of format to save the screenshot as. Default is "png"'),
21
21
  uid: z
22
- .number()
22
+ .string()
23
23
  .optional()
24
24
  .describe('The uid of an element on the page from the page content snapshot. If omitted takes a pages screenshot.'),
25
25
  fullPage: z
@@ -6,30 +6,55 @@
6
6
  import z from 'zod';
7
7
  import { defineTool } from './ToolDefinition.js';
8
8
  import { ToolCategories } from './categories.js';
9
- import { waitForEventsAfterAction } from '../waitForHelpers.js';
10
9
  export const evaluateScript = defineTool({
11
10
  name: 'evaluate_script',
12
- description: `Evaluate a JavaScript function inside the currently selected page. Returns the response as JSON.`,
11
+ description: `Evaluate a JavaScript function inside the currently selected page. Returns the response as JSON
12
+ so returned values have to JSON-serializable.`,
13
13
  annotations: {
14
14
  category: ToolCategories.DEBUGGING,
15
15
  readOnlyHint: false,
16
16
  },
17
17
  schema: {
18
- function: z
19
- .string()
20
- .describe('A JavaScript function to run in the currently selected page. Example: `() => {return document.title}` or `async () => {return await fetch("example.com")}`'),
18
+ function: z.string().describe(`A JavaScript function to run in the currently selected page.
19
+ Example without arguments: \`() => {
20
+ return document.title
21
+ }\` or \`async () => {
22
+ return await fetch("example.com")
23
+ }\`.
24
+ Example with arguments: \`(el) => {
25
+ return el.innerText;
26
+ }\`
27
+ `),
28
+ args: z
29
+ .array(z.object({
30
+ uid: z
31
+ .string()
32
+ .describe('The uid of an element on the page from the page content snapshot'),
33
+ }))
34
+ .optional()
35
+ .describe(`An optional list of arguments to pass to the function.`),
21
36
  },
22
37
  handler: async (request, response, context) => {
23
38
  const page = context.getSelectedPage();
24
- const script = `(async () => {
25
- return JSON.stringify(await (${request.params.function})());
26
- })()`;
27
- await waitForEventsAfterAction(page, async () => {
28
- const result = await page.evaluate(script);
29
- response.appendResponseLine('Script ran on page and returned:');
30
- response.appendResponseLine('```json');
31
- response.appendResponseLine(`${result}`);
32
- response.appendResponseLine('```');
33
- });
39
+ const fn = await page.evaluateHandle(`(${request.params.function})`);
40
+ const args = [fn];
41
+ try {
42
+ for (const el of request.params.args ?? []) {
43
+ args.push(await context.getElementByUid(el.uid));
44
+ }
45
+ await context.waitForEventsAfterAction(async () => {
46
+ const result = await page.evaluate(async (fn, ...args) => {
47
+ // @ts-expect-error no types.
48
+ return JSON.stringify(await fn(...args));
49
+ }, ...args);
50
+ response.appendResponseLine('Script ran on page and returned:');
51
+ response.appendResponseLine('```json');
52
+ response.appendResponseLine(`${result}`);
53
+ response.appendResponseLine('```');
54
+ });
55
+ }
56
+ finally {
57
+ Promise.allSettled(args.map(arg => arg.dispose())).catch(() => { });
58
+ }
34
59
  },
35
60
  });
@@ -3,9 +3,11 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
+ import { PerformanceTraceFormatter } from '../../node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js';
6
7
  import { PerformanceInsightFormatter } from '../../node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js';
7
8
  import * as TraceEngine from '../../node_modules/chrome-devtools-frontend/front_end/models/trace/trace.js';
8
9
  import { logger } from '../logger.js';
10
+ import { AgentFocus } from '../../node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIContext.js';
9
11
  const engine = TraceEngine.TraceModel.Model.createWithAllHandlers();
10
12
  export async function parseRawTraceBuffer(buffer) {
11
13
  engine.resetProcessor();
@@ -43,31 +45,33 @@ export async function parseRawTraceBuffer(buffer) {
43
45
  return null;
44
46
  }
45
47
  }
46
- // TODO(jactkfranklin): move the formatters from DevTools to use here.
47
- // This is a very temporary helper to output some text from the tool call to aid development.
48
- export function insightOutput(result) {
48
+ export function getTraceSummary(result) {
49
+ const focus = AgentFocus.full(result.parsedTrace);
50
+ const serializer = new TraceEngine.EventsSerializer.EventsSerializer();
51
+ const formatter = new PerformanceTraceFormatter(focus, serializer);
52
+ const output = formatter.formatTraceSummary();
53
+ return output;
54
+ }
55
+ export function getInsightOutput(result, insightName) {
56
+ // Currently, we do not support inspecting traces with multiple navigations. We either:
57
+ // 1. Find Insights from the first navigation (common case: user records a trace with a page reload to test load performance)
58
+ // 2. Fall back to finding Insights not associated with a navigation (common case: user tests an interaction without a page load).
49
59
  const mainNavigationId = result.parsedTrace.data.Meta.mainFrameNavigations.at(0)?.args.data
50
60
  ?.navigationId;
51
- if (!mainNavigationId) {
52
- return '';
53
- }
54
- let text = '';
55
- const insightsForNav = result.insights.get(mainNavigationId);
61
+ const insightsForNav = result.insights.get(mainNavigationId ?? TraceEngine.Types.Events.NO_NAVIGATION);
56
62
  if (!insightsForNav) {
57
- text += 'No Performance insights were found for this trace.';
58
- return text;
63
+ return {
64
+ error: 'No Performance Insights for this trace.',
65
+ };
59
66
  }
60
- const failingInsightKeys = Object.keys(insightsForNav.model).filter(insightKey => {
61
- const key = insightKey;
62
- const data = insightsForNav.model[key] ?? null;
63
- return data?.state === 'fail';
64
- });
65
- logger(`Found failing Insight keys: ${failingInsightKeys.join(', ')}`);
66
- for (const failingKey of failingInsightKeys) {
67
- const modelData = insightsForNav.model[failingKey];
68
- const formatter = new PerformanceInsightFormatter(result.parsedTrace, modelData);
69
- const output = formatter.formatInsight();
70
- text += `${output}\n`;
67
+ const matchingInsight = insightName in insightsForNav.model
68
+ ? insightsForNav.model[insightName]
69
+ : null;
70
+ if (!matchingInsight) {
71
+ return {
72
+ error: `No Insight with the name ${insightName} found. Double check the name you provided is accurate and try again.`,
73
+ };
71
74
  }
72
- return text;
75
+ const formatter = new PerformanceInsightFormatter(result.parsedTrace, matchingInsight);
76
+ return { output: formatter.formatInsight() };
73
77
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-devtools-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "MCP server for Chrome DevTools",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,15 +8,18 @@
8
8
  },
9
9
  "main": "index.js",
10
10
  "scripts": {
11
- "build": "tsc && node --experimental-strip-types scripts/post-build.ts",
12
- "format": "eslint --cache --fix . ;prettier --write --cache .",
13
- "check-format": "eslint --cache .; prettier --check --cache .;",
14
- "generate-docs": "npm run build && node --experimental-strip-types scripts/generate-docs.ts",
11
+ "build": "tsc && node --experimental-strip-types --no-warnings=ExperimentalWarning scripts/post-build.ts",
12
+ "typecheck": "tsc --noEmit",
13
+ "format": "eslint --cache --fix . && prettier --write --cache .",
14
+ "check-format": "eslint --cache . && prettier --check --cache .;",
15
+ "docs": "npm run build && npm run docs:generate && npm run format",
16
+ "docs:generate": "node --experimental-strip-types scripts/generate-docs.ts",
15
17
  "start": "npm run build && node build/src/index.js",
16
18
  "start-debug": "DEBUG=mcp:* DEBUG_COLORS=false npm run build && node build/src/index.js",
17
- "test": "npm run build && node --test-reporter spec --test-force-exit --test 'build/tests/**/*.test.js'",
18
- "test:only": "npm run build && node --test-reporter spec --test-force-exit --test --test-only 'build/tests/**/*.test.js'",
19
- "test:only:no-build": "node --test-reporter spec --test-force-exit --test --test-only 'build/tests/**/*.test.js'",
19
+ "test": "npm run build && node --require ./build/tests/setup.js --no-warnings=ExperimentalWarning --test-reporter spec --test-force-exit --test \"build/tests/**/*.test.js\"",
20
+ "test:only": "npm run build && node --require ./build/tests/setup.js --no-warnings=ExperimentalWarning --test-reporter spec --test-force-exit --test --test-only \"build/tests/**/*.test.js\"",
21
+ "test:only:no-build": "node --require ./build/tests/setup.js --no-warnings=ExperimentalWarning --test-reporter spec --test-force-exit --test --test-only \"build/tests/**/*.test.js\"",
22
+ "test:update-snapshots": "npm run build && node --require ./build/tests/setup.js --no-warnings=ExperimentalWarning --test-force-exit --test --test-update-snapshots \"build/tests/**/*.test.js\"",
20
23
  "prepare": "node --experimental-strip-types scripts/prepare.ts"
21
24
  },
22
25
  "files": [
@@ -33,9 +36,9 @@
33
36
  },
34
37
  "homepage": "https://github.com/ChromeDevTools/chrome-devtools-mcp#readme",
35
38
  "dependencies": {
36
- "@modelcontextprotocol/sdk": "1.18.0",
39
+ "@modelcontextprotocol/sdk": "1.18.1",
37
40
  "debug": "4.4.3",
38
- "puppeteer-core": "24.21.0",
41
+ "puppeteer-core": "24.22.0",
39
42
  "yargs": "18.0.0"
40
43
  },
41
44
  "devDependencies": {
@@ -47,11 +50,11 @@
47
50
  "@types/yargs": "^17.0.33",
48
51
  "@typescript-eslint/eslint-plugin": "^8.43.0",
49
52
  "@typescript-eslint/parser": "^8.43.0",
50
- "chrome-devtools-frontend": "1.0.1514545",
53
+ "chrome-devtools-frontend": "1.0.1516909",
51
54
  "eslint": "^9.35.0",
52
55
  "globals": "^16.4.0",
53
56
  "prettier": "^3.6.2",
54
- "puppeteer": "24.21.0",
57
+ "puppeteer": "24.22.0",
55
58
  "sinon": "^21.0.0",
56
59
  "typescript": "^5.9.2",
57
60
  "typescript-eslint": "^8.43.0"
@@ -1,109 +0,0 @@
1
- import { logger } from './logger.js';
2
- async function waitForStableDom(page, signal) {
3
- const stableDomObserver = await page.evaluateHandle(() => {
4
- let timeoutId;
5
- function callback() {
6
- clearTimeout(timeoutId);
7
- timeoutId = setTimeout(() => {
8
- domObserver.resolver.resolve();
9
- domObserver.observer.disconnect();
10
- }, 100);
11
- }
12
- const domObserver = {
13
- resolver: Promise.withResolvers(),
14
- observer: new MutationObserver(callback),
15
- };
16
- // It's possible that the DOM is not gonna change so we
17
- // need to start the timeout initially.
18
- callback();
19
- domObserver.observer.observe(document.body, {
20
- childList: true,
21
- subtree: true,
22
- attributes: true,
23
- });
24
- return domObserver;
25
- });
26
- signal.addEventListener('abort', async () => {
27
- try {
28
- await stableDomObserver.evaluate(observer => {
29
- observer.observer.disconnect();
30
- observer.resolver.resolve();
31
- });
32
- await stableDomObserver.dispose();
33
- }
34
- catch {
35
- // Ignored cleanup errors
36
- }
37
- });
38
- return Promise.race([
39
- stableDomObserver.evaluate(async (observer) => {
40
- return await observer.resolver.promise;
41
- }),
42
- timeout(3000, signal).then(() => {
43
- throw new Error('Timeout');
44
- }),
45
- ]);
46
- }
47
- async function waitForNavigationStarted(page, signal) {
48
- // Currently Puppeteer does not have API
49
- // For when a navigation is about to start
50
- const navigationStartedPromise = new Promise(resolve => {
51
- const listener = (event) => {
52
- if ([
53
- 'historySameDocument',
54
- 'historyDifferentDocument',
55
- 'sameDocument',
56
- ].includes(event.navigationType)) {
57
- resolve(false);
58
- return;
59
- }
60
- resolve(true);
61
- };
62
- page._client().on('Page.frameStartedNavigating', listener);
63
- signal.addEventListener('abort', () => {
64
- resolve(false);
65
- page._client().off('Page.frameStartedNavigating', listener);
66
- });
67
- });
68
- return await Promise.race([
69
- navigationStartedPromise,
70
- timeout(100).then(() => false),
71
- ]);
72
- }
73
- function timeout(time, signal) {
74
- return new Promise(res => {
75
- const id = setTimeout(res, time);
76
- signal?.addEventListener('abort', () => {
77
- res();
78
- clearTimeout(id);
79
- });
80
- });
81
- }
82
- /**
83
- * A wrapper that executes a action and waits for
84
- * a potential navigation, after which it waits
85
- * for the DOM to be stable before returning.
86
- */
87
- export async function waitForEventsAfterAction(page, callback) {
88
- const controller = new AbortController();
89
- const navigationStartedPromise = waitForNavigationStarted(page, controller.signal);
90
- await callback();
91
- try {
92
- const navigationStated = await navigationStartedPromise;
93
- if (navigationStated) {
94
- await page.waitForNavigation({
95
- timeout: 3000,
96
- signal: controller.signal,
97
- });
98
- }
99
- // Wait for stable dom after navigation so we execute in
100
- // the correct context
101
- await waitForStableDom(page, controller.signal);
102
- }
103
- catch (error) {
104
- logger(error);
105
- }
106
- finally {
107
- controller.abort();
108
- }
109
- }