chrome-devtools-mcp 0.10.2 → 0.12.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 +73 -10
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/common.js +1 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/AidaClient.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHost.js +31 -449
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHostStub.js +430 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/ResourceLoader.js +10 -22
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +2 -5
- package/build/node_modules/chrome-devtools-frontend/front_end/core/i18n/collect-ui-strings.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/i18n/generate-locales-js.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/HostRuntime.js +19 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/StringUtilities.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/api/HostRuntime.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/api/api.js +5 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/{common/Worker.js → platform/browser/HostRuntime.js} +18 -7
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/browser/browser.js +5 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/node/HostRuntime.js +72 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/node/node.js +5 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/platform.js +2 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/root/DevToolsContext.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/root/Runtime.js +7 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/AnimationModel.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +2 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMetadata.js +17 -5
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSModel.js +3 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSProperty.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParserMatchers.js +10 -10
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ConsoleModel.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Cookie.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DOMModel.js +5 -2
- 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/NetworkManager.js +46 -34
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +3 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/PageResourceLoader.js +43 -33
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/PreloadingModel.js +1 -1
- 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/Script.js +26 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +4 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapCache.js +16 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapManager.js +11 -5
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMapScopesInfo.js +129 -20
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Target.js +4 -13
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TargetManager.js +35 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/sdk-meta.js +72 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/foundation/Universe.js +5 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/Deprecation.js +48 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +45 -42
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +56 -77
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js +21 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AICallTree.js +9 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CSSWorkspaceBinding.js +4 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +34 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DebuggerWorkspaceBinding.js +15 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/ResourceMapping.js +59 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/ResourceScriptMapping.js +38 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/SASSSourceMapping.js +5 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/models/cpu_profile/CPUProfileDataModel.js +9 -7
- package/build/node_modules/chrome-devtools-frontend/front_end/models/crux-manager/CrUXManager.js +5 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/FormatterWorkerPool.js +2 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/AttributionReportingIssue.js +5 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/BounceTrackingIssue.js +3 -11
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/ClientHintIssue.js +4 -9
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/ContentSecurityPolicyIssue.js +4 -9
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/ContrastCheckTrigger.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/CookieDeprecationMetadataIssue.js +5 -11
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/CookieIssue.js +26 -25
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/CorsIssue.js +7 -14
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/CrossOriginEmbedderPolicyIssue.js +4 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/DeprecationIssue.js +6 -11
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/ElementAccessibilityIssue.js +6 -11
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/FederatedAuthRequestIssue.js +3 -8
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/FederatedAuthUserInfoRequestIssue.js +3 -8
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/GenericIssue.js +36 -21
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/HeavyAdIssue.js +3 -8
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/Issue.js +6 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/IssueAggregator.js +6 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/LowTextContrastIssue.js +2 -7
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/MixedContentIssue.js +6 -10
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/PartitioningBlobURLIssue.js +3 -8
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/PropertyRuleIssue.js +5 -10
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/QuirksModeIssue.js +2 -7
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/SRIMessageSignatureIssue.js +6 -10
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/SharedArrayBufferIssue.js +3 -8
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/SharedDictionaryIssue.js +5 -10
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/StylesheetLoadingIssue.js +7 -11
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/UnencodedDigestIssue.js +1 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/genericNavigationEntryMarkedSkippable.md +7 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/FunctionCodeResolver.js +192 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/source_map_scopes.js +2 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/SamplesHandler.js +3 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Trace.js +10 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver/SourceMapsResolver.js +23 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/IgnoreListManager.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/UISourceCode.js +38 -0
- package/build/node_modules/chrome-devtools-frontend/mcp/HostBindings.js +222 -0
- package/build/node_modules/chrome-devtools-frontend/mcp/mcp.js +15 -1
- package/build/src/DevToolsConnectionAdapter.js +56 -19
- package/build/src/DevtoolsUtils.js +143 -1
- package/build/src/McpContext.js +33 -11
- package/build/src/McpResponse.js +18 -26
- package/build/src/PageCollector.js +4 -11
- package/build/src/browser.js +52 -2
- package/build/src/cli.js +31 -3
- package/build/src/formatters/consoleFormatter.js +81 -3
- package/build/src/formatters/snapshotFormatter.js +18 -4
- package/build/src/issue-descriptions.js +4 -0
- package/build/src/main.js +27 -50
- package/build/src/third_party/THIRD_PARTY_NOTICES +27 -27
- package/build/src/third_party/index.js +21898 -15109
- package/build/src/tools/console.js +0 -4
- package/build/src/tools/emulation.js +29 -6
- package/build/src/tools/screenshot.js +4 -2
- package/build/src/tools/snapshot.js +1 -1
- package/build/src/tools/tools.js +29 -0
- package/build/src/utils/keyboard.js +5 -0
- package/package.json +7 -7
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Linkifier.js +0 -34
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/DOMUtilities.js +0 -122
- package/build/src/features.js +0 -14
- /package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/{genericFormAriaLabelledByToNonExistingId.md → genericFormAriaLabelledByToNonExistingIdError.md} +0 -0
- /package/build/node_modules/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/{genericFormLabelHasNeitherForNorNestedInput.md → genericFormLabelHasNeitherForNorNestedInputError.md} +0 -0
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
import { Common, I18n, } from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
|
|
6
|
+
import { DebuggerModel, Foundation, TargetManager, MarkdownIssueDescription, Marked, ProtocolClient, Common, I18n, } from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
|
|
7
|
+
import { PuppeteerDevToolsConnection } from './DevToolsConnectionAdapter.js';
|
|
8
|
+
import { ISSUE_UTILS } from './issue-descriptions.js';
|
|
9
|
+
import { logger } from './logger.js';
|
|
10
|
+
import { Mutex } from './Mutex.js';
|
|
7
11
|
export function extractUrlLikeFromDevToolsTitle(title) {
|
|
8
12
|
const match = title.match(new RegExp(`DevTools - (.*)`));
|
|
9
13
|
return match?.[1] ?? undefined;
|
|
@@ -20,6 +24,7 @@ export function urlsEqual(url1, url2) {
|
|
|
20
24
|
* 1. We do not care about the protocol.
|
|
21
25
|
* 2. We do not care about trailing slashes.
|
|
22
26
|
* 3. We do not care about "www".
|
|
27
|
+
* 4. We ignore the hash parts.
|
|
23
28
|
*
|
|
24
29
|
* For example, if the user types "record a trace on foo.com", we would want to
|
|
25
30
|
* match a tab in the connected Chrome instance that is showing "www.foo.com/"
|
|
@@ -37,6 +42,12 @@ function normalizeUrl(url) {
|
|
|
37
42
|
if (result.startsWith('www.')) {
|
|
38
43
|
result = result.slice(4);
|
|
39
44
|
}
|
|
45
|
+
// We use target URLs to locate DevTools but those often do
|
|
46
|
+
// no include hash.
|
|
47
|
+
const hashIdx = result.lastIndexOf('#');
|
|
48
|
+
if (hashIdx !== -1) {
|
|
49
|
+
result = result.slice(0, hashIdx);
|
|
50
|
+
}
|
|
40
51
|
// Remove trailing slash
|
|
41
52
|
if (result.endsWith('/')) {
|
|
42
53
|
result = result.slice(0, -1);
|
|
@@ -53,6 +64,46 @@ export class FakeIssuesManager extends Common.ObjectWrapper
|
|
|
53
64
|
return [];
|
|
54
65
|
}
|
|
55
66
|
}
|
|
67
|
+
export function mapIssueToMessageObject(issue) {
|
|
68
|
+
const count = issue.getAggregatedIssuesCount();
|
|
69
|
+
const markdownDescription = issue.getDescription();
|
|
70
|
+
const filename = markdownDescription?.file;
|
|
71
|
+
if (!markdownDescription) {
|
|
72
|
+
logger(`no description found for issue:` + issue.code);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const rawMarkdown = filename
|
|
76
|
+
? ISSUE_UTILS.getIssueDescription(filename)
|
|
77
|
+
: null;
|
|
78
|
+
if (!rawMarkdown) {
|
|
79
|
+
logger(`no markdown ${filename} found for issue:` + issue.code);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
let processedMarkdown;
|
|
83
|
+
let title;
|
|
84
|
+
try {
|
|
85
|
+
processedMarkdown = MarkdownIssueDescription.substitutePlaceholders(rawMarkdown, markdownDescription.substitutions);
|
|
86
|
+
const markdownAst = Marked.Marked.lexer(processedMarkdown);
|
|
87
|
+
title = MarkdownIssueDescription.findTitleFromMarkdownAst(markdownAst);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
logger('error parsing markdown for issue ' + issue.code());
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
if (!title) {
|
|
94
|
+
logger('cannot read issue title from ' + filename);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
type: 'issue',
|
|
99
|
+
item: issue,
|
|
100
|
+
message: title,
|
|
101
|
+
count,
|
|
102
|
+
description: processedMarkdown,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// DevTools CDP errors can get noisy.
|
|
106
|
+
ProtocolClient.InspectorBackend.test.suppressRequestErrors = true;
|
|
56
107
|
I18n.DevToolsLocale.DevToolsLocale.instance({
|
|
57
108
|
create: true,
|
|
58
109
|
data: {
|
|
@@ -62,3 +113,94 @@ I18n.DevToolsLocale.DevToolsLocale.instance({
|
|
|
62
113
|
},
|
|
63
114
|
});
|
|
64
115
|
I18n.i18n.registerLocaleDataForTest('en-US', {});
|
|
116
|
+
export class UniverseManager {
|
|
117
|
+
#browser;
|
|
118
|
+
#createUniverseFor;
|
|
119
|
+
#universes = new WeakMap();
|
|
120
|
+
/** Guard access to #universes so we don't create unnecessary universes */
|
|
121
|
+
#mutex = new Mutex();
|
|
122
|
+
constructor(browser, factory = DEFAULT_FACTORY) {
|
|
123
|
+
this.#browser = browser;
|
|
124
|
+
this.#createUniverseFor = factory;
|
|
125
|
+
}
|
|
126
|
+
async init(pages) {
|
|
127
|
+
try {
|
|
128
|
+
await this.#mutex.acquire();
|
|
129
|
+
const promises = [];
|
|
130
|
+
for (const page of pages) {
|
|
131
|
+
promises.push(this.#createUniverseFor(page).then(targetUniverse => this.#universes.set(page, targetUniverse)));
|
|
132
|
+
}
|
|
133
|
+
this.#browser.on('targetcreated', this.#onTargetCreated);
|
|
134
|
+
this.#browser.on('targetdestroyed', this.#onTargetDestroyed);
|
|
135
|
+
await Promise.all(promises);
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
this.#mutex.release();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
get(page) {
|
|
142
|
+
return this.#universes.get(page) ?? null;
|
|
143
|
+
}
|
|
144
|
+
dispose() {
|
|
145
|
+
this.#browser.off('targetcreated', this.#onTargetCreated);
|
|
146
|
+
this.#browser.off('targetdestroyed', this.#onTargetDestroyed);
|
|
147
|
+
}
|
|
148
|
+
#onTargetCreated = async (target) => {
|
|
149
|
+
const page = await target.page();
|
|
150
|
+
try {
|
|
151
|
+
await this.#mutex.acquire();
|
|
152
|
+
if (!page || this.#universes.has(page)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
this.#universes.set(page, await this.#createUniverseFor(page));
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
this.#mutex.release();
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
#onTargetDestroyed = async (target) => {
|
|
162
|
+
const page = await target.page();
|
|
163
|
+
try {
|
|
164
|
+
await this.#mutex.acquire();
|
|
165
|
+
if (!page || !this.#universes.has(page)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this.#universes.delete(page);
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
this.#mutex.release();
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const DEFAULT_FACTORY = async (page) => {
|
|
176
|
+
const settingStorage = new Common.Settings.SettingsStorage({});
|
|
177
|
+
const universe = new Foundation.Universe.Universe({
|
|
178
|
+
settingsCreationOptions: {
|
|
179
|
+
syncedStorage: settingStorage,
|
|
180
|
+
globalStorage: settingStorage,
|
|
181
|
+
localStorage: settingStorage,
|
|
182
|
+
settingRegistrations: Common.SettingRegistration.getRegisteredSettings(),
|
|
183
|
+
},
|
|
184
|
+
overrideAutoStartModels: new Set([DebuggerModel]),
|
|
185
|
+
});
|
|
186
|
+
const session = await page.createCDPSession();
|
|
187
|
+
const connection = new PuppeteerDevToolsConnection(session);
|
|
188
|
+
const targetManager = universe.context.get(TargetManager);
|
|
189
|
+
targetManager.observeModels(DebuggerModel, SKIP_ALL_PAUSES);
|
|
190
|
+
const target = targetManager.createTarget('main', '', 'frame', // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
191
|
+
/* parentTarget */ null, session.id(), undefined, connection);
|
|
192
|
+
return { target, universe };
|
|
193
|
+
};
|
|
194
|
+
// We don't want to pause any DevTools universe session ever on the MCP side.
|
|
195
|
+
//
|
|
196
|
+
// Note that calling `setSkipAllPauses` only affects the session on which it was
|
|
197
|
+
// sent. This means DevTools can still pause, step and do whatever. We just won't
|
|
198
|
+
// see the `Debugger.paused`/`Debugger.resumed` events on the MCP side.
|
|
199
|
+
const SKIP_ALL_PAUSES = {
|
|
200
|
+
modelAdded(model) {
|
|
201
|
+
void model.agent.invoke_setSkipAllPauses({ skip: true });
|
|
202
|
+
},
|
|
203
|
+
modelRemoved() {
|
|
204
|
+
// Do nothing.
|
|
205
|
+
},
|
|
206
|
+
};
|
package/build/src/McpContext.js
CHANGED
|
@@ -54,6 +54,7 @@ export class McpContext {
|
|
|
54
54
|
#isRunningTrace = false;
|
|
55
55
|
#networkConditionsMap = new WeakMap();
|
|
56
56
|
#cpuThrottlingRateMap = new WeakMap();
|
|
57
|
+
#geolocationMap = new WeakMap();
|
|
57
58
|
#dialog;
|
|
58
59
|
#nextSnapshotId = 1;
|
|
59
60
|
#traceResults = [];
|
|
@@ -64,7 +65,7 @@ export class McpContext {
|
|
|
64
65
|
this.logger = logger;
|
|
65
66
|
this.#locatorClass = locatorClass;
|
|
66
67
|
this.#options = options;
|
|
67
|
-
this.#networkCollector = new NetworkCollector(this.browser
|
|
68
|
+
this.#networkCollector = new NetworkCollector(this.browser);
|
|
68
69
|
this.#consoleCollector = new ConsoleCollector(this.browser, collect => {
|
|
69
70
|
return {
|
|
70
71
|
console: event => {
|
|
@@ -84,12 +85,12 @@ export class McpContext {
|
|
|
84
85
|
collect(event);
|
|
85
86
|
},
|
|
86
87
|
};
|
|
87
|
-
}
|
|
88
|
+
});
|
|
88
89
|
}
|
|
89
90
|
async #init() {
|
|
90
|
-
await this.createPagesSnapshot();
|
|
91
|
-
await this.#networkCollector.init();
|
|
92
|
-
await this.#consoleCollector.init();
|
|
91
|
+
const pages = await this.createPagesSnapshot();
|
|
92
|
+
await this.#networkCollector.init(pages);
|
|
93
|
+
await this.#consoleCollector.init(pages);
|
|
93
94
|
}
|
|
94
95
|
dispose() {
|
|
95
96
|
this.#networkCollector.dispose();
|
|
@@ -123,8 +124,12 @@ export class McpContext {
|
|
|
123
124
|
this.logger('no cdpBackendNodeId');
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
127
|
+
if (this.#textSnapshot === null) {
|
|
128
|
+
this.logger('no text snapshot');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
126
131
|
// TODO: index by backendNodeId instead.
|
|
127
|
-
const queue = [this.#textSnapshot
|
|
132
|
+
const queue = [this.#textSnapshot.root];
|
|
128
133
|
while (queue.length) {
|
|
129
134
|
const current = queue.pop();
|
|
130
135
|
if (current.backendNodeId === cdpBackendNodeId) {
|
|
@@ -191,6 +196,19 @@ export class McpContext {
|
|
|
191
196
|
const page = this.getSelectedPage();
|
|
192
197
|
return this.#cpuThrottlingRateMap.get(page) ?? 1;
|
|
193
198
|
}
|
|
199
|
+
setGeolocation(geolocation) {
|
|
200
|
+
const page = this.getSelectedPage();
|
|
201
|
+
if (geolocation === null) {
|
|
202
|
+
this.#geolocationMap.delete(page);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
this.#geolocationMap.set(page, geolocation);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
getGeolocation() {
|
|
209
|
+
const page = this.getSelectedPage();
|
|
210
|
+
return this.#geolocationMap.get(page) ?? null;
|
|
211
|
+
}
|
|
194
212
|
setIsRunningPerformanceTrace(x) {
|
|
195
213
|
this.#isRunningTrace = x;
|
|
196
214
|
}
|
|
@@ -284,7 +302,8 @@ export class McpContext {
|
|
|
284
302
|
return (this.#options.experimentalDevToolsDebugging ||
|
|
285
303
|
!page.url().startsWith('devtools://'));
|
|
286
304
|
});
|
|
287
|
-
if (!this.#selectedPage || this.#pages.indexOf(this.#selectedPage) === -1)
|
|
305
|
+
if ((!this.#selectedPage || this.#pages.indexOf(this.#selectedPage) === -1) &&
|
|
306
|
+
this.#pages[0]) {
|
|
288
307
|
this.selectPage(this.#pages[0]);
|
|
289
308
|
}
|
|
290
309
|
await this.detectOpenDevToolsWindows();
|
|
@@ -395,9 +414,12 @@ export class McpContext {
|
|
|
395
414
|
root: rootNodeWithId,
|
|
396
415
|
snapshotId: String(snapshotId),
|
|
397
416
|
idToNode,
|
|
417
|
+
hasSelectedElement: false,
|
|
418
|
+
verbose,
|
|
398
419
|
};
|
|
399
420
|
const data = devtoolsData ?? (await this.getDevToolsData());
|
|
400
421
|
if (data?.cdpBackendNodeId) {
|
|
422
|
+
this.#textSnapshot.hasSelectedElement = true;
|
|
401
423
|
this.#textSnapshot.selectedElementUid = this.resolveCdpElementId(data?.cdpBackendNodeId);
|
|
402
424
|
}
|
|
403
425
|
}
|
|
@@ -446,15 +468,15 @@ export class McpContext {
|
|
|
446
468
|
getNetworkRequestStableId(request) {
|
|
447
469
|
return this.#networkCollector.getIdForResource(request);
|
|
448
470
|
}
|
|
449
|
-
waitForTextOnPage(
|
|
471
|
+
waitForTextOnPage(text, timeout) {
|
|
450
472
|
const page = this.getSelectedPage();
|
|
451
473
|
const frames = page.frames();
|
|
452
|
-
|
|
474
|
+
let locator = this.#locatorClass.race(frames.flatMap(frame => [
|
|
453
475
|
frame.locator(`aria/${text}`),
|
|
454
476
|
frame.locator(`text/${text}`),
|
|
455
477
|
]));
|
|
456
478
|
if (timeout) {
|
|
457
|
-
locator.setTimeout(timeout);
|
|
479
|
+
locator = locator.setTimeout(timeout);
|
|
458
480
|
}
|
|
459
481
|
return locator.wait();
|
|
460
482
|
}
|
|
@@ -472,6 +494,6 @@ export class McpContext {
|
|
|
472
494
|
},
|
|
473
495
|
};
|
|
474
496
|
});
|
|
475
|
-
await this.#networkCollector.init();
|
|
497
|
+
await this.#networkCollector.init(await this.browser.pages());
|
|
476
498
|
}
|
|
477
499
|
}
|
package/build/src/McpResponse.js
CHANGED
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
import { AggregatedIssue
|
|
6
|
+
import { AggregatedIssue } from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
|
|
7
|
+
import { mapIssueToMessageObject } from './DevtoolsUtils.js';
|
|
7
8
|
import { formatConsoleEventShort, formatConsoleEventVerbose, } from './formatters/consoleFormatter.js';
|
|
8
9
|
import { getFormattedHeaderValue, getFormattedResponseBody, getFormattedRequestBody, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js';
|
|
9
10
|
import { formatSnapshotNode } from './formatters/snapshotFormatter.js';
|
|
10
|
-
import { getIssueDescription } from './issue-descriptions.js';
|
|
11
|
-
import { logger } from './logger.js';
|
|
12
11
|
import { handleDialog } from './tools/pages.js';
|
|
13
12
|
import { paginate } from './utils/pagination.js';
|
|
14
13
|
export class McpResponse {
|
|
@@ -156,6 +155,15 @@ export class McpResponse {
|
|
|
156
155
|
})),
|
|
157
156
|
};
|
|
158
157
|
}
|
|
158
|
+
else if (message instanceof AggregatedIssue) {
|
|
159
|
+
const mappedIssueMessage = mapIssueToMessageObject(message);
|
|
160
|
+
if (!mappedIssueMessage)
|
|
161
|
+
throw new Error("Can't provide detals for the msgid " + consoleMessageStableId);
|
|
162
|
+
consoleData = {
|
|
163
|
+
consoleMessageStableId,
|
|
164
|
+
...mappedIssueMessage,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
159
167
|
else {
|
|
160
168
|
consoleData = {
|
|
161
169
|
consoleMessageStableId,
|
|
@@ -199,28 +207,12 @@ export class McpResponse {
|
|
|
199
207
|
};
|
|
200
208
|
}
|
|
201
209
|
if (item instanceof AggregatedIssue) {
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
const rawMarkdown = filename
|
|
205
|
-
? getIssueDescription(filename)
|
|
206
|
-
: null;
|
|
207
|
-
if (!rawMarkdown) {
|
|
208
|
-
logger(`no markdown ${filename} found for issue:` + item.code);
|
|
210
|
+
const mappedIssueMessage = mapIssueToMessageObject(item);
|
|
211
|
+
if (!mappedIssueMessage)
|
|
209
212
|
return null;
|
|
210
|
-
}
|
|
211
|
-
const markdownAst = Marked.Marked.lexer(rawMarkdown);
|
|
212
|
-
const title = findTitleFromMarkdownAst(markdownAst);
|
|
213
|
-
if (!title) {
|
|
214
|
-
logger('cannot read issue title from ' + filename);
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
213
|
return {
|
|
218
214
|
consoleMessageStableId,
|
|
219
|
-
|
|
220
|
-
item,
|
|
221
|
-
message: title,
|
|
222
|
-
count,
|
|
223
|
-
args: [],
|
|
215
|
+
...mappedIssueMessage,
|
|
224
216
|
};
|
|
225
217
|
}
|
|
226
218
|
return {
|
|
@@ -273,11 +265,11 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
273
265
|
response.push(...parts);
|
|
274
266
|
}
|
|
275
267
|
if (data.formattedSnapshot) {
|
|
276
|
-
response.push('##
|
|
268
|
+
response.push('## Latest page snapshot');
|
|
277
269
|
response.push(data.formattedSnapshot);
|
|
278
270
|
}
|
|
279
271
|
response.push(...this.#formatNetworkRequestData(context, data.bodies));
|
|
280
|
-
response.push(...this.#formatConsoleData(data.consoleData));
|
|
272
|
+
response.push(...this.#formatConsoleData(context, data.consoleData));
|
|
281
273
|
if (this.#networkRequestsOptions?.include) {
|
|
282
274
|
let requests = context.getNetworkRequests(this.#networkRequestsOptions?.includePreservedRequests);
|
|
283
275
|
// Apply resource type filtering if specified
|
|
@@ -346,12 +338,12 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
346
338
|
items: paginationResult.items,
|
|
347
339
|
};
|
|
348
340
|
}
|
|
349
|
-
#formatConsoleData(data) {
|
|
341
|
+
#formatConsoleData(context, data) {
|
|
350
342
|
const response = [];
|
|
351
343
|
if (!data) {
|
|
352
344
|
return response;
|
|
353
345
|
}
|
|
354
|
-
response.push(formatConsoleEventVerbose(data));
|
|
346
|
+
response.push(formatConsoleEventVerbose(data, context));
|
|
355
347
|
return response;
|
|
356
348
|
}
|
|
357
349
|
#formatNetworkRequestData(context, data) {
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { createIssuesFromProtocolIssue, IssueAggregator, } from '../node_modules/chrome-devtools-frontend/mcp/mcp.js';
|
|
7
7
|
import { FakeIssuesManager } from './DevtoolsUtils.js';
|
|
8
|
-
import { features } from './features.js';
|
|
9
8
|
import { logger } from './logger.js';
|
|
10
9
|
function createIdGenerator() {
|
|
11
10
|
let i = 1;
|
|
@@ -22,20 +21,17 @@ export class PageCollector {
|
|
|
22
21
|
#listenersInitializer;
|
|
23
22
|
#listeners = new WeakMap();
|
|
24
23
|
#maxNavigationSaved = 3;
|
|
25
|
-
#includeAllPages;
|
|
26
24
|
/**
|
|
27
25
|
* This maps a Page to a list of navigations with a sub-list
|
|
28
26
|
* of all collected resources.
|
|
29
27
|
* The newer navigations come first.
|
|
30
28
|
*/
|
|
31
29
|
storage = new WeakMap();
|
|
32
|
-
constructor(browser, listeners
|
|
30
|
+
constructor(browser, listeners) {
|
|
33
31
|
this.#browser = browser;
|
|
34
32
|
this.#listenersInitializer = listeners;
|
|
35
|
-
this.#includeAllPages = includeAllPages;
|
|
36
33
|
}
|
|
37
|
-
async init() {
|
|
38
|
-
const pages = await this.#browser.pages(this.#includeAllPages);
|
|
34
|
+
async init(pages) {
|
|
39
35
|
for (const page of pages) {
|
|
40
36
|
this.addPage(page);
|
|
41
37
|
}
|
|
@@ -154,9 +150,6 @@ export class ConsoleCollector extends PageCollector {
|
|
|
154
150
|
#subscribedPages = new WeakMap();
|
|
155
151
|
addPage(page) {
|
|
156
152
|
super.addPage(page);
|
|
157
|
-
if (!features.issues) {
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
153
|
if (!this.#subscribedPages.has(page)) {
|
|
161
154
|
const subscriber = new PageIssueSubscriber(page);
|
|
162
155
|
this.#subscribedPages.set(page, subscriber);
|
|
@@ -262,8 +255,8 @@ export class NetworkCollector extends PageCollector {
|
|
|
262
255
|
collect(req);
|
|
263
256
|
},
|
|
264
257
|
};
|
|
265
|
-
}
|
|
266
|
-
super(browser, listeners
|
|
258
|
+
}) {
|
|
259
|
+
super(browser, listeners);
|
|
267
260
|
}
|
|
268
261
|
splitAfterNavigation(page) {
|
|
269
262
|
const navigations = this.storage.get(page) ?? [];
|
package/build/src/browser.js
CHANGED
|
@@ -19,6 +19,10 @@ function makeTargetFilter() {
|
|
|
19
19
|
if (target.url() === 'chrome://newtab/') {
|
|
20
20
|
return true;
|
|
21
21
|
}
|
|
22
|
+
// Could be the only page opened in the browser.
|
|
23
|
+
if (target.url().startsWith('chrome://inspect')) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
22
26
|
for (const prefix of ignoredPrefixes) {
|
|
23
27
|
if (target.url().startsWith(prefix)) {
|
|
24
28
|
return false;
|
|
@@ -28,6 +32,7 @@ function makeTargetFilter() {
|
|
|
28
32
|
};
|
|
29
33
|
}
|
|
30
34
|
export async function ensureBrowserConnected(options) {
|
|
35
|
+
const { channel } = options;
|
|
31
36
|
if (browser?.connected) {
|
|
32
37
|
return browser;
|
|
33
38
|
}
|
|
@@ -45,11 +50,56 @@ export async function ensureBrowserConnected(options) {
|
|
|
45
50
|
else if (options.browserURL) {
|
|
46
51
|
connectOptions.browserURL = options.browserURL;
|
|
47
52
|
}
|
|
53
|
+
else if (channel || options.userDataDir) {
|
|
54
|
+
const userDataDir = options.userDataDir;
|
|
55
|
+
if (userDataDir) {
|
|
56
|
+
// TODO: re-expose this logic via Puppeteer.
|
|
57
|
+
const portPath = path.join(userDataDir, 'DevToolsActivePort');
|
|
58
|
+
try {
|
|
59
|
+
const fileContent = await fs.promises.readFile(portPath, 'utf8');
|
|
60
|
+
const [rawPort, rawPath] = fileContent
|
|
61
|
+
.split('\n')
|
|
62
|
+
.map(line => {
|
|
63
|
+
return line.trim();
|
|
64
|
+
})
|
|
65
|
+
.filter(line => {
|
|
66
|
+
return !!line;
|
|
67
|
+
});
|
|
68
|
+
if (!rawPort || !rawPath) {
|
|
69
|
+
throw new Error(`Invalid DevToolsActivePort '${fileContent}' found`);
|
|
70
|
+
}
|
|
71
|
+
const port = parseInt(rawPort, 10);
|
|
72
|
+
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
73
|
+
throw new Error(`Invalid port '${rawPort}' found`);
|
|
74
|
+
}
|
|
75
|
+
const browserWSEndpoint = `ws://127.0.0.1:${port}${rawPath}`;
|
|
76
|
+
connectOptions.browserWSEndpoint = browserWSEndpoint;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
throw new Error(`Could not connect to Chrome in ${userDataDir}. Check if Chrome is running and remote debugging is enabled.`, {
|
|
80
|
+
cause: error,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
if (!channel) {
|
|
86
|
+
throw new Error('Channel must be provided if userDataDir is missing');
|
|
87
|
+
}
|
|
88
|
+
connectOptions.channel = (channel === 'stable' ? 'chrome' : `chrome-${channel}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
48
91
|
else {
|
|
49
|
-
throw new Error('Either browserURL or
|
|
92
|
+
throw new Error('Either browserURL, wsEndpoint, channel or userDataDir must be provided');
|
|
50
93
|
}
|
|
51
94
|
logger('Connecting Puppeteer to ', JSON.stringify(connectOptions));
|
|
52
|
-
|
|
95
|
+
try {
|
|
96
|
+
browser = await puppeteer.connect(connectOptions);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
throw new Error('Could not connect to Chrome. Check if Chrome is running and remote debugging is enabled by going to chrome://inspect/#remote-debugging.', {
|
|
100
|
+
cause: err,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
53
103
|
logger('Connected Puppeteer');
|
|
54
104
|
return browser;
|
|
55
105
|
}
|
package/build/src/cli.js
CHANGED
|
@@ -5,9 +5,21 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { yargs, hideBin } from './third_party/index.js';
|
|
7
7
|
export const cliOptions = {
|
|
8
|
+
autoConnect: {
|
|
9
|
+
type: 'boolean',
|
|
10
|
+
description: 'If specified, automatically connects to a browser (Chrome 145+) running in the user data directory identified by the channel param. Requires remote debugging being enabled in Chrome here: chrome://inspect/#remote-debugging.',
|
|
11
|
+
conflicts: ['isolated', 'executablePath'],
|
|
12
|
+
default: false,
|
|
13
|
+
coerce: (value) => {
|
|
14
|
+
if (!value) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
},
|
|
19
|
+
},
|
|
8
20
|
browserUrl: {
|
|
9
21
|
type: 'string',
|
|
10
|
-
description: 'Connect to a running Chrome instance
|
|
22
|
+
description: 'Connect to a running, debuggable Chrome instance (e.g. `http://127.0.0.1:9222`). For more details see: https://github.com/ChromeDevTools/chrome-devtools-mcp#connecting-to-a-running-chrome-instance.',
|
|
11
23
|
alias: 'u',
|
|
12
24
|
conflicts: 'wsEndpoint',
|
|
13
25
|
coerce: (url) => {
|
|
@@ -80,8 +92,12 @@ export const cliOptions = {
|
|
|
80
92
|
},
|
|
81
93
|
isolated: {
|
|
82
94
|
type: 'boolean',
|
|
83
|
-
description: 'If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed.',
|
|
84
|
-
|
|
95
|
+
description: 'If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed. Defaults to false.',
|
|
96
|
+
},
|
|
97
|
+
userDataDir: {
|
|
98
|
+
type: 'string',
|
|
99
|
+
description: 'Path to the user data directory for Chrome. Default is $HOME/.cache/chrome-devtools-mcp/chrome-profile$CHANNEL_SUFFIX_IF_NON_STABLE',
|
|
100
|
+
conflicts: ['browserUrl', 'wsEndpoint', 'isolated'],
|
|
85
101
|
},
|
|
86
102
|
channel: {
|
|
87
103
|
type: 'string',
|
|
@@ -196,6 +212,18 @@ export function parseArguments(version, argv = process.argv) {
|
|
|
196
212
|
'Disable tools in the performance category',
|
|
197
213
|
],
|
|
198
214
|
['$0 --no-category-network', 'Disable tools in the network category'],
|
|
215
|
+
[
|
|
216
|
+
'$0 --user-data-dir=/tmp/user-data-dir',
|
|
217
|
+
'Use a custom user data directory',
|
|
218
|
+
],
|
|
219
|
+
[
|
|
220
|
+
'$0 --auto-connect',
|
|
221
|
+
'Connect to a stable Chrome instance (Chrome 145+) running instead of launching a new instance',
|
|
222
|
+
],
|
|
223
|
+
[
|
|
224
|
+
'$0 --auto-connect --channel=canary',
|
|
225
|
+
'Connect to a canary Chrome instance (Chrome 145+) running instead of launching a new instance',
|
|
226
|
+
],
|
|
199
227
|
]);
|
|
200
228
|
return yargsInstance
|
|
201
229
|
.wrap(Math.min(120, yargsInstance.terminalWidth()))
|
|
@@ -19,11 +19,12 @@ function getArgs(msg) {
|
|
|
19
19
|
return args;
|
|
20
20
|
}
|
|
21
21
|
// The verbose format for a console message, including all details.
|
|
22
|
-
export function formatConsoleEventVerbose(msg) {
|
|
22
|
+
export function formatConsoleEventVerbose(msg, context) {
|
|
23
|
+
const aggregatedIssue = msg.item;
|
|
23
24
|
const result = [
|
|
24
25
|
`ID: ${msg.consoleMessageStableId}`,
|
|
25
|
-
`Message: ${msg.type}> ${msg.message}`,
|
|
26
|
-
formatArgs(msg),
|
|
26
|
+
`Message: ${msg.type}> ${aggregatedIssue ? formatIssue(aggregatedIssue, msg.description, context) : msg.message}`,
|
|
27
|
+
aggregatedIssue ? undefined : formatArgs(msg),
|
|
27
28
|
].filter(line => !!line);
|
|
28
29
|
return result.join('\n');
|
|
29
30
|
}
|
|
@@ -41,3 +42,80 @@ function formatArgs(consoleData) {
|
|
|
41
42
|
}
|
|
42
43
|
return result.join('\n');
|
|
43
44
|
}
|
|
45
|
+
export function formatIssue(issue, description, context) {
|
|
46
|
+
const result = [];
|
|
47
|
+
let processedMarkdown = description?.trim();
|
|
48
|
+
// Remove heading in order not to conflict with the whole console message response markdown
|
|
49
|
+
if (processedMarkdown?.startsWith('# ')) {
|
|
50
|
+
processedMarkdown = processedMarkdown.substring(2).trimStart();
|
|
51
|
+
}
|
|
52
|
+
if (processedMarkdown)
|
|
53
|
+
result.push(processedMarkdown);
|
|
54
|
+
const links = issue.getDescription()?.links;
|
|
55
|
+
if (links && links.length > 0) {
|
|
56
|
+
result.push('Learn more:');
|
|
57
|
+
for (const link of links) {
|
|
58
|
+
result.push(`[${link.linkTitle}](${link.link})`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const issues = issue.getAllIssues();
|
|
62
|
+
const affectedResources = [];
|
|
63
|
+
for (const singleIssue of issues) {
|
|
64
|
+
const details = singleIssue.details();
|
|
65
|
+
if (!details)
|
|
66
|
+
continue;
|
|
67
|
+
// We send the remaining details as untyped JSON because the DevTools
|
|
68
|
+
// frontend code is currently not re-usable.
|
|
69
|
+
// eslint-disable-next-line
|
|
70
|
+
const data = structuredClone(details);
|
|
71
|
+
let uid;
|
|
72
|
+
let request;
|
|
73
|
+
if ('violatingNodeId' in details && details.violatingNodeId && context) {
|
|
74
|
+
uid = context.resolveCdpElementId(details.violatingNodeId);
|
|
75
|
+
delete data.violatingNodeId;
|
|
76
|
+
}
|
|
77
|
+
if ('nodeId' in details && details.nodeId && context) {
|
|
78
|
+
uid = context.resolveCdpElementId(details.nodeId);
|
|
79
|
+
delete data.nodeId;
|
|
80
|
+
}
|
|
81
|
+
if ('documentNodeId' in details && details.documentNodeId && context) {
|
|
82
|
+
uid = context.resolveCdpElementId(details.documentNodeId);
|
|
83
|
+
delete data.documentNodeId;
|
|
84
|
+
}
|
|
85
|
+
if ('request' in details && details.request) {
|
|
86
|
+
request = details.request.url;
|
|
87
|
+
if (details.request.requestId && context) {
|
|
88
|
+
const resolvedId = context.resolveCdpRequestId(details.request.requestId);
|
|
89
|
+
if (resolvedId) {
|
|
90
|
+
request = resolvedId;
|
|
91
|
+
delete data.request.requestId;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// These fields has no use for the MCP client (redundant or irrelevant).
|
|
96
|
+
delete data.errorType;
|
|
97
|
+
delete data.frameId;
|
|
98
|
+
affectedResources.push({
|
|
99
|
+
uid,
|
|
100
|
+
data: data,
|
|
101
|
+
request,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (affectedResources.length) {
|
|
105
|
+
result.push('### Affected resources');
|
|
106
|
+
}
|
|
107
|
+
result.push(...affectedResources.map(item => {
|
|
108
|
+
const details = [];
|
|
109
|
+
if (item.uid)
|
|
110
|
+
details.push(`uid=${item.uid}`);
|
|
111
|
+
if (item.request) {
|
|
112
|
+
details.push((typeof item.request === 'number' ? `reqid=` : 'url=') + item.request);
|
|
113
|
+
}
|
|
114
|
+
if (item.data)
|
|
115
|
+
details.push(`data=${JSON.stringify(item.data)}`);
|
|
116
|
+
return details.join(' ');
|
|
117
|
+
}));
|
|
118
|
+
if (result.length === 0)
|
|
119
|
+
return 'No affected resources found';
|
|
120
|
+
return result.join('\n');
|
|
121
|
+
}
|