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.
- package/README.md +33 -11
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Progress.js +60 -53
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Settings.js +3 -32
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/GdpClient.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +5 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/i18n/i18n.js +35 -8
- package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +2 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/root/Runtime.js +4 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +11 -10
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSModel.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParserMatchers.js +24 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/EnhancedTracesParser.js +29 -24
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +9 -15
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RemoteObject.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ResourceTreeModel.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RuntimeModel.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ServiceWorkerManager.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +4 -31
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TraceObject.js +5 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +4 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +12 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.js +6 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js +259 -179
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js +366 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/UnitFormatters.js +10 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AICallTree.js +366 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIContext.js +75 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIQueries.js +105 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CSSWorkspaceBinding.js +243 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +407 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/ContentProviderBasedProject.js +130 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DebuggerLanguagePlugins.js +992 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DebuggerWorkspaceBinding.js +574 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DefaultScriptMapping.js +112 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/FileUtils.js +186 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/LiveLocation.js +60 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/NetworkProject.js +107 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/PresentationConsoleMessageHelper.js +244 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/ResourceMapping.js +473 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/ResourceScriptMapping.js +399 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/ResourceUtils.js +87 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/SASSSourceMapping.js +181 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/StylesSourceMapping.js +268 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/TempFile.js +55 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/bindings.js +20 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/crux-manager/CrUXManager.js +283 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/crux-manager/crux-manager.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/emulation/DeviceModeModel.js +775 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/emulation/EmulatedDevices.js +1706 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/emulation/emulation.js +6 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/FormatterWorkerPool.js +131 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/ScriptFormatter.js +77 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/formatter.js +6 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/geometry/GeometryImpl.js +347 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/geometry/geometry.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/NamesResolver.js +626 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/ScopeChainModel.js +59 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/ScopeTreeCache.js +32 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/source_map_scopes.js +7 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTrace.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceImpl.js +67 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceModel.js +97 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/Trie.js +113 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/stack_trace.js +5 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/stack_trace_impl.js +7 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/text_utils/TextUtils.js +23 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/ModelImpl.js +4 -9
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/Processor.js +16 -8
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/AuctionWorkletsHandler.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/FramesHandler.js +2 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/LayoutShiftsHandler.js +3 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.js +10 -9
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/ScreenshotsHandler.js +0 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/ScriptsHandler.js +4 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/UserInteractionsHandler.js +2 -10
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/UserTimingsHandler.js +3 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/SamplesIntegrator.js +8 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Trace.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/CLSCulprits.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DocumentLatency.js +5 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DuplicatedJavaScript.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/INPBreakdown.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ImageDelivery.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPDiscovery.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ModernHTTP.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/RenderBlocking.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +21 -21
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver/SourceMapsResolver.js +201 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver/trace_source_maps_resolver.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/FileManager.js +64 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/IgnoreListManager.js +511 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/SearchConfig.js +113 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/UISourceCode.js +563 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/WorkspaceImpl.js +204 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/workspace.js +9 -0
- package/build/src/McpContext.js +84 -19
- package/build/src/McpResponse.js +8 -7
- package/build/src/WaitForHelper.js +123 -0
- package/build/src/browser.js +15 -10
- package/build/src/index.js +4 -6
- package/build/src/logger.js +1 -0
- package/build/src/tools/input.js +12 -13
- package/build/src/tools/pages.js +2 -3
- package/build/src/tools/performance.js +31 -4
- package/build/src/tools/screenshot.js +1 -1
- package/build/src/tools/script.js +40 -15
- package/build/src/trace-processing/parse.js +26 -22
- package/package.json +15 -12
- package/build/src/waitForHelpers.js +0 -109
package/build/src/tools/input.js
CHANGED
|
@@ -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
|
-
.
|
|
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(
|
|
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
|
-
.
|
|
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(
|
|
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
|
-
.
|
|
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(
|
|
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.
|
|
108
|
-
to_uid: z.
|
|
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(
|
|
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.
|
|
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(
|
|
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
|
-
.
|
|
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
|
},
|
package/build/src/tools/pages.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
20
|
-
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
63
|
+
return {
|
|
64
|
+
error: 'No Performance Insights for this trace.',
|
|
65
|
+
};
|
|
59
66
|
}
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
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
|
|
18
|
-
"test:only": "npm run build && node --test-reporter spec --test-force-exit --test --test-only
|
|
19
|
-
"test:only:no-build": "node --test-reporter spec --test-force-exit --test --test-only
|
|
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.
|
|
39
|
+
"@modelcontextprotocol/sdk": "1.18.1",
|
|
37
40
|
"debug": "4.4.3",
|
|
38
|
-
"puppeteer-core": "24.
|
|
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.
|
|
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.
|
|
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
|
-
}
|