chrome-devtools-mcp 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- 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/i18n/i18n.js +35 -8
- package/build/node_modules/chrome-devtools-frontend/front_end/core/root/Runtime.js +4 -1
- 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/PerformanceTraceFormatter.js +366 -0
- 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 +64 -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 +128 -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/Processor.js +1 -1
- 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/DocumentLatency.js +5 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver/SourceMapsResolver.js +199 -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 +24 -9
- package/build/src/McpResponse.js +3 -3
- package/build/src/browser.js +3 -1
- package/build/src/index.js +1 -1
- package/build/src/tools/input.js +7 -7
- package/build/src/tools/performance.js +29 -2
- package/build/src/tools/screenshot.js +1 -1
- package/build/src/tools/script.js +40 -14
- package/build/src/trace-processing/parse.js +26 -22
- package/package.json +9 -7
package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/WorkspaceImpl.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Copyright 2012 The Chromium Authors
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
import * as Common from '../../core/common/common.js';
|
|
5
|
+
import { UISourceCode } from './UISourceCode.js';
|
|
6
|
+
/* eslint-disable @typescript-eslint/naming-convention -- Used by web_tests. */
|
|
7
|
+
export var projectTypes;
|
|
8
|
+
(function (projectTypes) {
|
|
9
|
+
projectTypes["Debugger"] = "debugger";
|
|
10
|
+
projectTypes["Formatter"] = "formatter";
|
|
11
|
+
projectTypes["Network"] = "network";
|
|
12
|
+
projectTypes["FileSystem"] = "filesystem";
|
|
13
|
+
projectTypes["ConnectableFileSystem"] = "connectablefilesystem";
|
|
14
|
+
projectTypes["ContentScripts"] = "contentscripts";
|
|
15
|
+
projectTypes["Service"] = "service";
|
|
16
|
+
})(projectTypes || (projectTypes = {}));
|
|
17
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
18
|
+
export class ProjectStore {
|
|
19
|
+
#workspace;
|
|
20
|
+
#id;
|
|
21
|
+
#type;
|
|
22
|
+
#displayName;
|
|
23
|
+
#uiSourceCodes = new Map();
|
|
24
|
+
constructor(workspace, id, type, displayName) {
|
|
25
|
+
this.#workspace = workspace;
|
|
26
|
+
this.#id = id;
|
|
27
|
+
this.#type = type;
|
|
28
|
+
this.#displayName = displayName;
|
|
29
|
+
}
|
|
30
|
+
id() {
|
|
31
|
+
return this.#id;
|
|
32
|
+
}
|
|
33
|
+
type() {
|
|
34
|
+
return this.#type;
|
|
35
|
+
}
|
|
36
|
+
displayName() {
|
|
37
|
+
return this.#displayName;
|
|
38
|
+
}
|
|
39
|
+
workspace() {
|
|
40
|
+
return this.#workspace;
|
|
41
|
+
}
|
|
42
|
+
createUISourceCode(url, contentType) {
|
|
43
|
+
return new UISourceCode(this, url, contentType);
|
|
44
|
+
}
|
|
45
|
+
addUISourceCode(uiSourceCode) {
|
|
46
|
+
const url = uiSourceCode.url();
|
|
47
|
+
if (this.uiSourceCodeForURL(url)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
this.#uiSourceCodes.set(url, uiSourceCode);
|
|
51
|
+
this.#workspace.dispatchEventToListeners(Events.UISourceCodeAdded, uiSourceCode);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
removeUISourceCode(url) {
|
|
55
|
+
const uiSourceCode = this.#uiSourceCodes.get(url);
|
|
56
|
+
if (uiSourceCode === undefined) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this.#uiSourceCodes.delete(url);
|
|
60
|
+
this.#workspace.dispatchEventToListeners(Events.UISourceCodeRemoved, uiSourceCode);
|
|
61
|
+
}
|
|
62
|
+
removeProject() {
|
|
63
|
+
this.#workspace.removeProject(this);
|
|
64
|
+
this.#uiSourceCodes.clear();
|
|
65
|
+
}
|
|
66
|
+
uiSourceCodeForURL(url) {
|
|
67
|
+
return this.#uiSourceCodes.get(url) ?? null;
|
|
68
|
+
}
|
|
69
|
+
uiSourceCodes() {
|
|
70
|
+
return this.#uiSourceCodes.values();
|
|
71
|
+
}
|
|
72
|
+
renameUISourceCode(uiSourceCode, newName) {
|
|
73
|
+
const oldPath = uiSourceCode.url();
|
|
74
|
+
const newPath = uiSourceCode.parentURL() ?
|
|
75
|
+
Common.ParsedURL.ParsedURL.urlFromParentUrlAndName(uiSourceCode.parentURL(), newName) :
|
|
76
|
+
Common.ParsedURL.ParsedURL.preEncodeSpecialCharactersInPath(newName);
|
|
77
|
+
this.#uiSourceCodes.set(newPath, uiSourceCode);
|
|
78
|
+
this.#uiSourceCodes.delete(oldPath);
|
|
79
|
+
}
|
|
80
|
+
// No-op implementation for a handful of interface methods.
|
|
81
|
+
rename(_uiSourceCode, _newName, _callback) {
|
|
82
|
+
}
|
|
83
|
+
excludeFolder(_path) {
|
|
84
|
+
}
|
|
85
|
+
deleteFile(_uiSourceCode) {
|
|
86
|
+
}
|
|
87
|
+
deleteDirectoryRecursively(_path) {
|
|
88
|
+
return Promise.resolve(false);
|
|
89
|
+
}
|
|
90
|
+
remove() {
|
|
91
|
+
}
|
|
92
|
+
indexContent(_progress) {
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
let workspaceInstance;
|
|
96
|
+
export class WorkspaceImpl extends Common.ObjectWrapper.ObjectWrapper {
|
|
97
|
+
#projects = new Map();
|
|
98
|
+
#hasResourceContentTrackingExtensions = false;
|
|
99
|
+
constructor() {
|
|
100
|
+
super();
|
|
101
|
+
}
|
|
102
|
+
static instance(opts = { forceNew: null }) {
|
|
103
|
+
const { forceNew } = opts;
|
|
104
|
+
if (!workspaceInstance || forceNew) {
|
|
105
|
+
workspaceInstance = new WorkspaceImpl();
|
|
106
|
+
}
|
|
107
|
+
return workspaceInstance;
|
|
108
|
+
}
|
|
109
|
+
static removeInstance() {
|
|
110
|
+
workspaceInstance = undefined;
|
|
111
|
+
}
|
|
112
|
+
uiSourceCode(projectId, url) {
|
|
113
|
+
const project = this.#projects.get(projectId);
|
|
114
|
+
return project ? project.uiSourceCodeForURL(url) : null;
|
|
115
|
+
}
|
|
116
|
+
uiSourceCodeForURL(url) {
|
|
117
|
+
for (const project of this.#projects.values()) {
|
|
118
|
+
const uiSourceCode = project.uiSourceCodeForURL(url);
|
|
119
|
+
if (uiSourceCode) {
|
|
120
|
+
return uiSourceCode;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
findCompatibleUISourceCodes(uiSourceCode) {
|
|
126
|
+
const url = uiSourceCode.url();
|
|
127
|
+
const contentType = uiSourceCode.contentType();
|
|
128
|
+
const result = [];
|
|
129
|
+
for (const project of this.#projects.values()) {
|
|
130
|
+
if (uiSourceCode.project().type() !== project.type()) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const candidate = project.uiSourceCodeForURL(url);
|
|
134
|
+
if (candidate && candidate.url() === url && candidate.contentType() === contentType) {
|
|
135
|
+
result.push(candidate);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
uiSourceCodesForProjectType(type) {
|
|
141
|
+
const result = [];
|
|
142
|
+
for (const project of this.#projects.values()) {
|
|
143
|
+
if (project.type() === type) {
|
|
144
|
+
for (const uiSourceCode of project.uiSourceCodes()) {
|
|
145
|
+
result.push(uiSourceCode);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
addProject(project) {
|
|
152
|
+
console.assert(!this.#projects.has(project.id()), `A project with id ${project.id()} already exists!`);
|
|
153
|
+
this.#projects.set(project.id(), project);
|
|
154
|
+
this.dispatchEventToListeners(Events.ProjectAdded, project);
|
|
155
|
+
}
|
|
156
|
+
removeProject(project) {
|
|
157
|
+
this.#projects.delete(project.id());
|
|
158
|
+
this.dispatchEventToListeners(Events.ProjectRemoved, project);
|
|
159
|
+
}
|
|
160
|
+
project(projectId) {
|
|
161
|
+
return this.#projects.get(projectId) || null;
|
|
162
|
+
}
|
|
163
|
+
projectForFileSystemRoot(root) {
|
|
164
|
+
const projectId = Common.ParsedURL.ParsedURL.rawPathToUrlString(root);
|
|
165
|
+
return this.project(projectId);
|
|
166
|
+
}
|
|
167
|
+
projects() {
|
|
168
|
+
return [...this.#projects.values()];
|
|
169
|
+
}
|
|
170
|
+
projectsForType(type) {
|
|
171
|
+
function filterByType(project) {
|
|
172
|
+
return project.type() === type;
|
|
173
|
+
}
|
|
174
|
+
return this.projects().filter(filterByType);
|
|
175
|
+
}
|
|
176
|
+
uiSourceCodes() {
|
|
177
|
+
const result = [];
|
|
178
|
+
for (const project of this.#projects.values()) {
|
|
179
|
+
for (const uiSourceCode of project.uiSourceCodes()) {
|
|
180
|
+
result.push(uiSourceCode);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
setHasResourceContentTrackingExtensions(hasExtensions) {
|
|
186
|
+
this.#hasResourceContentTrackingExtensions = hasExtensions;
|
|
187
|
+
}
|
|
188
|
+
hasResourceContentTrackingExtensions() {
|
|
189
|
+
return this.#hasResourceContentTrackingExtensions;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export var Events;
|
|
193
|
+
(function (Events) {
|
|
194
|
+
/* eslint-disable @typescript-eslint/naming-convention -- Used by web_tests. */
|
|
195
|
+
Events["UISourceCodeAdded"] = "UISourceCodeAdded";
|
|
196
|
+
Events["UISourceCodeRemoved"] = "UISourceCodeRemoved";
|
|
197
|
+
Events["UISourceCodeRenamed"] = "UISourceCodeRenamed";
|
|
198
|
+
Events["WorkingCopyChanged"] = "WorkingCopyChanged";
|
|
199
|
+
Events["WorkingCopyCommitted"] = "WorkingCopyCommitted";
|
|
200
|
+
Events["WorkingCopyCommittedByUser"] = "WorkingCopyCommittedByUser";
|
|
201
|
+
Events["ProjectAdded"] = "ProjectAdded";
|
|
202
|
+
Events["ProjectRemoved"] = "ProjectRemoved";
|
|
203
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
204
|
+
})(Events || (Events = {}));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Copyright 2019 The Chromium Authors
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
import * as FileManager from './FileManager.js';
|
|
5
|
+
import * as IgnoreListManager from './IgnoreListManager.js';
|
|
6
|
+
import * as SearchConfig from './SearchConfig.js';
|
|
7
|
+
import * as UISourceCode from './UISourceCode.js';
|
|
8
|
+
import * as Workspace from './WorkspaceImpl.js';
|
|
9
|
+
export { FileManager, IgnoreListManager, SearchConfig, UISourceCode, Workspace, };
|
package/build/src/McpContext.js
CHANGED
|
@@ -11,13 +11,14 @@ export class McpContext {
|
|
|
11
11
|
#selectedPageIdx = 0;
|
|
12
12
|
// The most recent snapshot.
|
|
13
13
|
#textSnapshot = null;
|
|
14
|
-
#idToNodeMap = new Map();
|
|
15
14
|
#networkCollector;
|
|
16
15
|
#consoleCollector;
|
|
17
16
|
#isRunningTrace = false;
|
|
18
17
|
#networkConditions = null;
|
|
19
18
|
#cpuThrottlingRate = 1;
|
|
20
19
|
#dialog;
|
|
20
|
+
#nextSnapshotId = 1;
|
|
21
|
+
#traceResults = [];
|
|
21
22
|
constructor(browser, logger) {
|
|
22
23
|
this.browser = browser;
|
|
23
24
|
this.logger = logger;
|
|
@@ -135,10 +136,14 @@ export class McpContext {
|
|
|
135
136
|
newPage.setDefaultNavigationTimeout(10_000);
|
|
136
137
|
}
|
|
137
138
|
async getElementByUid(uid) {
|
|
138
|
-
if (!this.#
|
|
139
|
+
if (!this.#textSnapshot?.idToNode.size) {
|
|
139
140
|
throw new Error('No snapshot found. Use browser_snapshot to capture one');
|
|
140
141
|
}
|
|
141
|
-
const
|
|
142
|
+
const [snapshotId] = uid.split('_');
|
|
143
|
+
if (this.#textSnapshot.snapshotId !== snapshotId) {
|
|
144
|
+
throw new Error('This uid is coming from a stale snapshot. Call take_snapshot to get a fresh snapshot.');
|
|
145
|
+
}
|
|
146
|
+
const node = this.#textSnapshot?.idToNode.get(uid);
|
|
142
147
|
if (!node) {
|
|
143
148
|
throw new Error('No such element found in the snapshot');
|
|
144
149
|
}
|
|
@@ -165,26 +170,30 @@ export class McpContext {
|
|
|
165
170
|
const page = this.getSelectedPage();
|
|
166
171
|
const rootNode = await page.accessibility.snapshot();
|
|
167
172
|
if (!rootNode) {
|
|
168
|
-
return
|
|
173
|
+
return;
|
|
169
174
|
}
|
|
175
|
+
const snapshotId = this.#nextSnapshotId++;
|
|
170
176
|
// Iterate through the whole accessibility node tree and assign node ids that
|
|
171
177
|
// will be used for the tree serialization and mapping ids back to nodes.
|
|
172
178
|
let idCounter = 0;
|
|
173
|
-
|
|
179
|
+
const idToNode = new Map();
|
|
174
180
|
const assignIds = (node) => {
|
|
175
181
|
const nodeWithId = {
|
|
176
182
|
...node,
|
|
177
|
-
id: idCounter
|
|
183
|
+
id: `${snapshotId}_${idCounter++}`,
|
|
178
184
|
children: node.children
|
|
179
185
|
? node.children.map(child => assignIds(child))
|
|
180
186
|
: [],
|
|
181
187
|
};
|
|
182
|
-
|
|
188
|
+
idToNode.set(nodeWithId.id, nodeWithId);
|
|
183
189
|
return nodeWithId;
|
|
184
190
|
};
|
|
185
191
|
const rootNodeWithId = assignIds(rootNode);
|
|
186
|
-
this.#textSnapshot =
|
|
187
|
-
|
|
192
|
+
this.#textSnapshot = {
|
|
193
|
+
root: rootNodeWithId,
|
|
194
|
+
snapshotId: String(snapshotId),
|
|
195
|
+
idToNode,
|
|
196
|
+
};
|
|
188
197
|
}
|
|
189
198
|
getTextSnapshot() {
|
|
190
199
|
return this.#textSnapshot;
|
|
@@ -201,4 +210,10 @@ export class McpContext {
|
|
|
201
210
|
throw new Error('Could not save a screenshot to a file');
|
|
202
211
|
}
|
|
203
212
|
}
|
|
213
|
+
storeTraceRecording(result) {
|
|
214
|
+
this.#traceResults.push(result);
|
|
215
|
+
}
|
|
216
|
+
recordedTraces() {
|
|
217
|
+
return this.#traceResults;
|
|
218
|
+
}
|
|
204
219
|
}
|
package/build/src/McpResponse.js
CHANGED
|
@@ -100,9 +100,9 @@ Call browser_handle_dialog to handle it before continuing.`);
|
|
|
100
100
|
response.push(...parts);
|
|
101
101
|
}
|
|
102
102
|
if (this.#includeSnapshot) {
|
|
103
|
-
const
|
|
104
|
-
if (
|
|
105
|
-
const formattedSnapshot = formatA11ySnapshot(
|
|
103
|
+
const snapshot = context.getTextSnapshot();
|
|
104
|
+
if (snapshot) {
|
|
105
|
+
const formattedSnapshot = formatA11ySnapshot(snapshot.root);
|
|
106
106
|
response.push('## Page content');
|
|
107
107
|
response.push(formattedSnapshot);
|
|
108
108
|
}
|
package/build/src/browser.js
CHANGED
|
@@ -43,7 +43,9 @@ async function ensureBrowserConnected(browserURL) {
|
|
|
43
43
|
}
|
|
44
44
|
export async function launch(options) {
|
|
45
45
|
const { channel, executablePath, customDevTools, headless, isolated } = options;
|
|
46
|
-
const profileDirName = channel && channel !== 'stable'
|
|
46
|
+
const profileDirName = channel && channel !== 'stable'
|
|
47
|
+
? `chrome-profile-${channel}`
|
|
48
|
+
: 'chrome-profile';
|
|
47
49
|
let userDataDir = options.userDataDir;
|
|
48
50
|
if (!isolated && !userDataDir) {
|
|
49
51
|
userDataDir = path.join(os.homedir(), '.cache', 'chrome-devtools-mcp', profileDirName);
|
package/build/src/index.js
CHANGED
package/build/src/tools/input.js
CHANGED
|
@@ -16,7 +16,7 @@ export const click = defineTool({
|
|
|
16
16
|
},
|
|
17
17
|
schema: {
|
|
18
18
|
uid: z
|
|
19
|
-
.
|
|
19
|
+
.string()
|
|
20
20
|
.describe('The uid of an element on the page from the page content snapshot'),
|
|
21
21
|
dblClick: z
|
|
22
22
|
.boolean()
|
|
@@ -51,7 +51,7 @@ export const hover = defineTool({
|
|
|
51
51
|
},
|
|
52
52
|
schema: {
|
|
53
53
|
uid: z
|
|
54
|
-
.
|
|
54
|
+
.string()
|
|
55
55
|
.describe('The uid of an element on the page from the page content snapshot'),
|
|
56
56
|
},
|
|
57
57
|
handler: async (request, response, context) => {
|
|
@@ -78,7 +78,7 @@ export const fill = defineTool({
|
|
|
78
78
|
},
|
|
79
79
|
schema: {
|
|
80
80
|
uid: z
|
|
81
|
-
.
|
|
81
|
+
.string()
|
|
82
82
|
.describe('The uid of an element on the page from the page content snapshot'),
|
|
83
83
|
value: z.string().describe('The value to fill in'),
|
|
84
84
|
},
|
|
@@ -104,8 +104,8 @@ export const drag = defineTool({
|
|
|
104
104
|
readOnlyHint: false,
|
|
105
105
|
},
|
|
106
106
|
schema: {
|
|
107
|
-
from_uid: z.
|
|
108
|
-
to_uid: z.
|
|
107
|
+
from_uid: z.string().describe('The uid of the element to drag'),
|
|
108
|
+
to_uid: z.string().describe('The uid of the element to drop into'),
|
|
109
109
|
},
|
|
110
110
|
handler: async (request, response, context) => {
|
|
111
111
|
const fromHandle = await context.getElementByUid(request.params.from_uid);
|
|
@@ -135,7 +135,7 @@ export const fillForm = defineTool({
|
|
|
135
135
|
schema: {
|
|
136
136
|
elements: z
|
|
137
137
|
.array(z.object({
|
|
138
|
-
uid: z.
|
|
138
|
+
uid: z.string().describe('The uid of the element to fill out'),
|
|
139
139
|
value: z.string().describe('Value for the element'),
|
|
140
140
|
}))
|
|
141
141
|
.describe('Elements from snapshot to fill out.'),
|
|
@@ -165,7 +165,7 @@ export const uploadFile = defineTool({
|
|
|
165
165
|
},
|
|
166
166
|
schema: {
|
|
167
167
|
uid: z
|
|
168
|
-
.
|
|
168
|
+
.string()
|
|
169
169
|
.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
170
|
filePath: z.string().describe('The local path of the file to upload'),
|
|
171
171
|
},
|
|
@@ -5,7 +5,7 @@
|
|
|
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({
|
|
@@ -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
|
|
@@ -9,27 +9,53 @@ import { ToolCategories } from './categories.js';
|
|
|
9
9
|
import { waitForEventsAfterAction } from '../waitForHelpers.js';
|
|
10
10
|
export const evaluateScript = defineTool({
|
|
11
11
|
name: 'evaluate_script',
|
|
12
|
-
description: `Evaluate a JavaScript function inside the currently selected page. Returns the response as JSON
|
|
12
|
+
description: `Evaluate a JavaScript function inside the currently selected page. Returns the response as JSON
|
|
13
|
+
so returned values have to JSON-serializable.`,
|
|
13
14
|
annotations: {
|
|
14
15
|
category: ToolCategories.DEBUGGING,
|
|
15
16
|
readOnlyHint: false,
|
|
16
17
|
},
|
|
17
18
|
schema: {
|
|
18
|
-
function: z
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
function: z.string().describe(`A JavaScript function to run in the currently selected page.
|
|
20
|
+
Example without arguments: \`() => {
|
|
21
|
+
return document.title
|
|
22
|
+
}\` or \`async () => {
|
|
23
|
+
return await fetch("example.com")
|
|
24
|
+
}\`.
|
|
25
|
+
Example with arguments: \`(el) => {
|
|
26
|
+
return el.innerText;
|
|
27
|
+
}\`
|
|
28
|
+
`),
|
|
29
|
+
args: z
|
|
30
|
+
.array(z.object({
|
|
31
|
+
uid: z
|
|
32
|
+
.string()
|
|
33
|
+
.describe('The uid of an element on the page from the page content snapshot'),
|
|
34
|
+
}))
|
|
35
|
+
.optional()
|
|
36
|
+
.describe(`An optional list of arguments to pass to the function.`),
|
|
21
37
|
},
|
|
22
38
|
handler: async (request, response, context) => {
|
|
23
39
|
const page = context.getSelectedPage();
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
const fn = await page.evaluateHandle(`(${request.params.function})`);
|
|
41
|
+
const args = [fn];
|
|
42
|
+
try {
|
|
43
|
+
for (const el of request.params.args ?? []) {
|
|
44
|
+
args.push(await context.getElementByUid(el.uid));
|
|
45
|
+
}
|
|
46
|
+
await waitForEventsAfterAction(page, async () => {
|
|
47
|
+
const result = await page.evaluate(async (fn, ...args) => {
|
|
48
|
+
// @ts-expect-error no types.
|
|
49
|
+
return JSON.stringify(await fn(...args));
|
|
50
|
+
}, ...args);
|
|
51
|
+
response.appendResponseLine('Script ran on page and returned:');
|
|
52
|
+
response.appendResponseLine('```json');
|
|
53
|
+
response.appendResponseLine(`${result}`);
|
|
54
|
+
response.appendResponseLine('```');
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
Promise.allSettled(args.map(arg => arg.dispose())).catch(() => { });
|
|
59
|
+
}
|
|
34
60
|
},
|
|
35
61
|
});
|
|
@@ -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.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for Chrome DevTools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,14 +9,16 @@
|
|
|
9
9
|
"main": "index.js",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc && node --experimental-strip-types scripts/post-build.ts",
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
12
13
|
"format": "eslint --cache --fix . ;prettier --write --cache .",
|
|
13
14
|
"check-format": "eslint --cache .; prettier --check --cache .;",
|
|
14
15
|
"generate-docs": "npm run build && node --experimental-strip-types scripts/generate-docs.ts",
|
|
15
16
|
"start": "npm run build && node build/src/index.js",
|
|
16
17
|
"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'",
|
|
18
|
+
"test": "npm run build && node --require ./build/tests/setup.js --test-reporter spec --test-force-exit --test 'build/tests/**/*.test.js'",
|
|
19
|
+
"test:only": "npm run build && node --require ./build/tests/setup.js --test-reporter spec --test-force-exit --test --test-only 'build/tests/**/*.test.js'",
|
|
20
|
+
"test:only:no-build": "node --require ./build/tests/setup.js --test-reporter spec --test-force-exit --test --test-only 'build/tests/**/*.test.js'",
|
|
21
|
+
"test:update-snapshots": "npm run build && node --require ./build/tests/setup.js --test-force-exit --test --test-update-snapshots 'build/tests/**/*.test.js'",
|
|
20
22
|
"prepare": "node --experimental-strip-types scripts/prepare.ts"
|
|
21
23
|
},
|
|
22
24
|
"files": [
|
|
@@ -35,7 +37,7 @@
|
|
|
35
37
|
"dependencies": {
|
|
36
38
|
"@modelcontextprotocol/sdk": "1.18.0",
|
|
37
39
|
"debug": "4.4.3",
|
|
38
|
-
"puppeteer-core": "24.
|
|
40
|
+
"puppeteer-core": "24.22.0",
|
|
39
41
|
"yargs": "18.0.0"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
@@ -47,11 +49,11 @@
|
|
|
47
49
|
"@types/yargs": "^17.0.33",
|
|
48
50
|
"@typescript-eslint/eslint-plugin": "^8.43.0",
|
|
49
51
|
"@typescript-eslint/parser": "^8.43.0",
|
|
50
|
-
"chrome-devtools-frontend": "1.0.
|
|
52
|
+
"chrome-devtools-frontend": "1.0.1515796",
|
|
51
53
|
"eslint": "^9.35.0",
|
|
52
54
|
"globals": "^16.4.0",
|
|
53
55
|
"prettier": "^3.6.2",
|
|
54
|
-
"puppeteer": "24.
|
|
56
|
+
"puppeteer": "24.22.0",
|
|
55
57
|
"sinon": "^21.0.0",
|
|
56
58
|
"typescript": "^5.9.2",
|
|
57
59
|
"typescript-eslint": "^8.43.0"
|