@narumitw/pi-chrome-devtools 0.52.3 → 0.53.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 +62 -9
- package/dist/chunks/{browser-settings-menu-VWCNG4HB.js → browser-settings-menu-N5O65I7S.js} +81 -7
- package/dist/chunks/browser-settings-menu-N5O65I7S.js.map +7 -0
- package/dist/chunks/chunk-B7B63PWB.js +465 -0
- package/dist/chunks/chunk-B7B63PWB.js.map +7 -0
- package/dist/chunks/{chunk-RH6KESL3.js → chunk-GVBXT3FB.js} +369 -514
- package/dist/chunks/chunk-GVBXT3FB.js.map +7 -0
- package/dist/chunks/chunk-RTECHAEZ.js +373 -0
- package/dist/chunks/chunk-RTECHAEZ.js.map +7 -0
- package/dist/chunks/{menu-PLD3S3EO.js → menu-UBHW6UP2.js} +72 -39
- package/dist/chunks/menu-UBHW6UP2.js.map +7 -0
- package/dist/chunks/tools-V7OR5KJ3.js +945 -0
- package/dist/chunks/tools-V7OR5KJ3.js.map +7 -0
- package/dist/index.ts +158 -195
- package/dist/index.ts.map +4 -4
- package/package.json +4 -3
- package/src/browser-manager.ts +282 -109
- package/src/browser-settings-menu.ts +98 -4
- package/src/cdp-client.ts +329 -83
- package/src/chrome-devtools.ts +51 -11
- package/src/lazy-tools.ts +37 -4
- package/src/menu.ts +80 -39
- package/src/render.ts +12 -1
- package/src/runtime.ts +92 -0
- package/src/settings.ts +47 -10
- package/src/tool-names.ts +16 -1
- package/src/tool-selector.ts +67 -30
- package/src/tools.ts +86 -10
- package/src/webmcp/discovery.ts +235 -0
- package/src/webmcp/policy.ts +402 -0
- package/src/webmcp/protocol.ts +414 -0
- package/src/webmcp/tools.ts +190 -0
- package/dist/chunks/browser-settings-menu-VWCNG4HB.js.map +0 -7
- package/dist/chunks/chunk-RH6KESL3.js.map +0 -7
- package/dist/chunks/menu-PLD3S3EO.js.map +0 -7
package/src/settings.ts
CHANGED
|
@@ -61,6 +61,7 @@ export interface ResolvedChromeDevToolsSettings {
|
|
|
61
61
|
tools?: ChromeDevToolsToolName[];
|
|
62
62
|
updatedAt?: number;
|
|
63
63
|
browser: EffectiveBrowserSettings;
|
|
64
|
+
webmcp: { enabled: boolean };
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
export interface SettingsLoadOptions {
|
|
@@ -74,6 +75,7 @@ export type UserSettingsFileStatus =
|
|
|
74
75
|
|
|
75
76
|
interface SettingsLoadBase {
|
|
76
77
|
effectiveBrowser: EffectiveBrowserSettings;
|
|
78
|
+
effectiveWebMcpEnabled: boolean;
|
|
77
79
|
paths: { user: string; project?: string };
|
|
78
80
|
userFile: UserSettingsFileStatus;
|
|
79
81
|
warnings: string[];
|
|
@@ -94,6 +96,7 @@ interface NormalizedSettingsDocument {
|
|
|
94
96
|
tools?: ChromeDevToolsToolName[];
|
|
95
97
|
updatedAt?: number;
|
|
96
98
|
browser?: UserBrowserSettings;
|
|
99
|
+
webmcp?: { enabled: boolean };
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
interface SettingsDocumentResult {
|
|
@@ -165,7 +168,8 @@ export async function loadSettings(options: SettingsLoadOptions = {}): Promise<S
|
|
|
165
168
|
const recognized =
|
|
166
169
|
settings.tools !== undefined ||
|
|
167
170
|
user.normalized?.browser !== undefined ||
|
|
168
|
-
project.normalized?.browser !== undefined
|
|
171
|
+
project.normalized?.browser !== undefined ||
|
|
172
|
+
user.normalized?.webmcp !== undefined;
|
|
169
173
|
const invalidReasons = [user, project]
|
|
170
174
|
.filter((result) => result.kind === "invalid")
|
|
171
175
|
.map((result) => result.reason)
|
|
@@ -179,6 +183,7 @@ export async function loadSettings(options: SettingsLoadOptions = {}): Promise<S
|
|
|
179
183
|
: { kind: user.kind };
|
|
180
184
|
const base = {
|
|
181
185
|
effectiveBrowser,
|
|
186
|
+
effectiveWebMcpEnabled: settings.webmcp.enabled,
|
|
182
187
|
paths,
|
|
183
188
|
userFile,
|
|
184
189
|
warnings,
|
|
@@ -200,6 +205,7 @@ function resolveSettings(
|
|
|
200
205
|
return {
|
|
201
206
|
...(user?.tools ? { tools: user.tools, updatedAt: user.updatedAt } : {}),
|
|
202
207
|
browser,
|
|
208
|
+
webmcp: { enabled: user?.webmcp?.enabled ?? false },
|
|
203
209
|
};
|
|
204
210
|
}
|
|
205
211
|
|
|
@@ -293,7 +299,7 @@ async function readSettingsDocument(
|
|
|
293
299
|
return { kind: "invalid", reason, warnings: [`Chrome DevTools settings ignored: ${reason}`] };
|
|
294
300
|
}
|
|
295
301
|
|
|
296
|
-
const scopeWarnings =
|
|
302
|
+
const scopeWarnings = projectOwnedSettingsWarnings(parsed, scope, filePath);
|
|
297
303
|
try {
|
|
298
304
|
const normalized = await normalizeSettingsDocument(parsed, scope, filePath, cwd);
|
|
299
305
|
return { kind: "valid", document: { ...parsed }, normalized, warnings: scopeWarnings };
|
|
@@ -307,19 +313,27 @@ async function readSettingsDocument(
|
|
|
307
313
|
}
|
|
308
314
|
}
|
|
309
315
|
|
|
310
|
-
function
|
|
316
|
+
function projectOwnedSettingsWarnings(
|
|
311
317
|
document: Record<string, unknown>,
|
|
312
318
|
scope: "project" | "user",
|
|
313
319
|
filePath: string,
|
|
314
320
|
) {
|
|
315
|
-
if (scope !== "project"
|
|
316
|
-
const browser = document.browser;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
321
|
+
if (scope !== "project") return [];
|
|
322
|
+
const browser = isRecord(document.browser) ? document.browser : undefined;
|
|
323
|
+
const warnings = browser
|
|
324
|
+
? ["endpoint", "autoLaunch", "executablePath"]
|
|
325
|
+
.filter((field) => browser[field] !== undefined)
|
|
326
|
+
.map(
|
|
327
|
+
(field) =>
|
|
328
|
+
`Chrome DevTools project browser.${field} ignored in ${filePath}; configure this machine-owned setting in ${settingsFilePath()}.`,
|
|
329
|
+
)
|
|
330
|
+
: [];
|
|
331
|
+
if (document.webmcp !== undefined) {
|
|
332
|
+
warnings.push(
|
|
333
|
+
`Chrome DevTools project webmcp settings ignored in ${filePath}; only the user settings file may enable experimental WebMCP.`,
|
|
322
334
|
);
|
|
335
|
+
}
|
|
336
|
+
return warnings;
|
|
323
337
|
}
|
|
324
338
|
|
|
325
339
|
async function normalizeSettingsDocument(
|
|
@@ -346,6 +360,13 @@ async function normalizeSettingsDocument(
|
|
|
346
360
|
normalized.browser = await normalizeBrowserSection(document.browser, scope, filePath, cwd);
|
|
347
361
|
}
|
|
348
362
|
|
|
363
|
+
if (scope === "user" && document.webmcp !== undefined) {
|
|
364
|
+
if (!isRecord(document.webmcp) || typeof document.webmcp.enabled !== "boolean") {
|
|
365
|
+
throw new Error("expected webmcp.enabled to be a boolean");
|
|
366
|
+
}
|
|
367
|
+
normalized.webmcp = { enabled: document.webmcp.enabled };
|
|
368
|
+
}
|
|
369
|
+
|
|
349
370
|
return normalized;
|
|
350
371
|
}
|
|
351
372
|
|
|
@@ -526,6 +547,22 @@ export function saveSettings(
|
|
|
526
547
|
);
|
|
527
548
|
}
|
|
528
549
|
|
|
550
|
+
export function saveWebMcpSettings(
|
|
551
|
+
enabled: boolean,
|
|
552
|
+
operations: Partial<SettingsFileOperations> = {},
|
|
553
|
+
): Promise<void> {
|
|
554
|
+
return queueSettingsMutation(
|
|
555
|
+
(current) => ({
|
|
556
|
+
...current,
|
|
557
|
+
webmcp: {
|
|
558
|
+
...(isRecord(current.webmcp) ? current.webmcp : {}),
|
|
559
|
+
enabled,
|
|
560
|
+
},
|
|
561
|
+
}),
|
|
562
|
+
operations,
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
529
566
|
export function saveBrowserSettings(
|
|
530
567
|
patch: BrowserSettingsPatch,
|
|
531
568
|
operations: Partial<SettingsFileOperations> = {},
|
package/src/tool-names.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const CORE_CHROME_DEVTOOLS_TOOL_NAMES = [
|
|
2
2
|
"chrome_devtools_list_pages",
|
|
3
3
|
"chrome_devtools_select_page",
|
|
4
4
|
"chrome_devtools_navigate",
|
|
@@ -6,4 +6,19 @@ export const CHROME_DEVTOOLS_TOOL_NAMES = [
|
|
|
6
6
|
"chrome_devtools_screenshot",
|
|
7
7
|
] as const;
|
|
8
8
|
|
|
9
|
+
export const WEBMCP_TOOL_NAMES = [
|
|
10
|
+
"chrome_devtools_webmcp_list_tools",
|
|
11
|
+
"chrome_devtools_webmcp_call_tool",
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
export const CHROME_DEVTOOLS_TOOL_NAMES = [
|
|
15
|
+
...CORE_CHROME_DEVTOOLS_TOOL_NAMES,
|
|
16
|
+
...WEBMCP_TOOL_NAMES,
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
9
19
|
export type ChromeDevToolsToolName = (typeof CHROME_DEVTOOLS_TOOL_NAMES)[number];
|
|
20
|
+
export type WebMcpToolName = (typeof WEBMCP_TOOL_NAMES)[number];
|
|
21
|
+
|
|
22
|
+
export function isWebMcpToolName(value: string): value is WebMcpToolName {
|
|
23
|
+
return WEBMCP_TOOL_NAMES.includes(value as WebMcpToolName);
|
|
24
|
+
}
|
package/src/tool-selector.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
launchAttemptLines,
|
|
10
10
|
launchHint,
|
|
11
11
|
launchModeLabel,
|
|
12
|
+
managedBrowserForOwner,
|
|
12
13
|
} from "./browser-manager.js";
|
|
13
14
|
import {
|
|
14
15
|
applyAvailableChromeDevtoolsTools,
|
|
@@ -16,9 +17,14 @@ import {
|
|
|
16
17
|
CHROME_DEVTOOLS_LOAD_TOOL_NAME,
|
|
17
18
|
chromeDevtoolsToolExposureMode,
|
|
18
19
|
} from "./lazy-tools.js";
|
|
19
|
-
import { state } from "./runtime.js";
|
|
20
|
+
import { invalidateWebMcpOperations, state, webMcpEnabled } from "./runtime.js";
|
|
20
21
|
import { loadSettings, saveSettings, settingsFilePath } from "./settings.js";
|
|
21
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
CHROME_DEVTOOLS_TOOL_NAMES,
|
|
24
|
+
type ChromeDevToolsToolName,
|
|
25
|
+
CORE_CHROME_DEVTOOLS_TOOL_NAMES,
|
|
26
|
+
isWebMcpToolName,
|
|
27
|
+
} from "./tool-names.js";
|
|
22
28
|
|
|
23
29
|
type CommandContext = ExtensionCommandContext;
|
|
24
30
|
|
|
@@ -35,6 +41,7 @@ interface ToolStatusSummary {
|
|
|
35
41
|
availableChromeToolCount: number;
|
|
36
42
|
loadedChromeToolCount: number;
|
|
37
43
|
activeNonChromeToolCount: number;
|
|
44
|
+
capabilityCount: number;
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
type ToolSelectionSaveResult = "saved" | "active-tools-changed" | "failed";
|
|
@@ -48,7 +55,7 @@ export async function updateChromeDevtoolsTools(
|
|
|
48
55
|
const generation = state.sessionGeneration;
|
|
49
56
|
const result = await transactSelectedTools(pi, ctx, selectedTools, generation);
|
|
50
57
|
if (result !== "saved" || generation !== state.sessionGeneration) return;
|
|
51
|
-
const status = await buildToolStatusMessage(pi);
|
|
58
|
+
const status = await buildToolStatusMessage(pi, ctx.sessionManager);
|
|
52
59
|
if (generation !== state.sessionGeneration) return;
|
|
53
60
|
ctx.ui.notify(`Chrome DevTools tool catalog ${action}.\n\n${status}`, "info");
|
|
54
61
|
}
|
|
@@ -109,6 +116,14 @@ async function transactSelectedToolsNow(
|
|
|
109
116
|
const previousActiveTools = pi.getActiveTools();
|
|
110
117
|
const previousAvailableTools = availableChromeDevtoolsTools(pi);
|
|
111
118
|
try {
|
|
119
|
+
const previousWebMcpTools = previousAvailableTools.filter(isWebMcpToolName);
|
|
120
|
+
const selectedWebMcpTools = selectedTools.filter(isWebMcpToolName);
|
|
121
|
+
if (!arraysEqual(previousWebMcpTools, selectedWebMcpTools)) {
|
|
122
|
+
invalidateWebMcpOperations(
|
|
123
|
+
ctx.sessionManager,
|
|
124
|
+
"Chrome DevTools WebMCP gateway availability changed",
|
|
125
|
+
);
|
|
126
|
+
}
|
|
112
127
|
applyChromeDevtoolsTools(pi, selectedTools);
|
|
113
128
|
await persistSettings(selectedTools);
|
|
114
129
|
return expectedGeneration === state.sessionGeneration ? "saved" : "failed";
|
|
@@ -154,7 +169,7 @@ export function applyChromeDevtoolsTools(
|
|
|
154
169
|
applyAvailableChromeDevtoolsTools(pi, selectedTools);
|
|
155
170
|
}
|
|
156
171
|
|
|
157
|
-
function getToolStatusSummary(pi: ExtensionAPI): ToolStatusSummary {
|
|
172
|
+
function getToolStatusSummary(pi: ExtensionAPI, owner: object): ToolStatusSummary {
|
|
158
173
|
const chromeToolNames = new Set<string>(CHROME_DEVTOOLS_TOOL_NAMES);
|
|
159
174
|
const activeToolNames = new Set(pi.getActiveTools());
|
|
160
175
|
const loadedChromeToolCount = CHROME_DEVTOOLS_TOOL_NAMES.filter((name) =>
|
|
@@ -164,8 +179,9 @@ function getToolStatusSummary(pi: ExtensionAPI): ToolStatusSummary {
|
|
|
164
179
|
const activeNonChromeToolCount = Array.from(activeToolNames).filter(
|
|
165
180
|
(name) => !chromeToolNames.has(name) && name !== CHROME_DEVTOOLS_LOAD_TOOL_NAME,
|
|
166
181
|
).length;
|
|
182
|
+
const capabilityCount = effectiveCatalog(owner).length;
|
|
167
183
|
const availabilityStatus =
|
|
168
|
-
availableChromeToolCount ===
|
|
184
|
+
availableChromeToolCount === capabilityCount
|
|
169
185
|
? "enabled"
|
|
170
186
|
: availableChromeToolCount === 0
|
|
171
187
|
? "disabled"
|
|
@@ -176,36 +192,39 @@ function getToolStatusSummary(pi: ExtensionAPI): ToolStatusSummary {
|
|
|
176
192
|
availableChromeToolCount,
|
|
177
193
|
loadedChromeToolCount,
|
|
178
194
|
activeNonChromeToolCount,
|
|
195
|
+
capabilityCount,
|
|
179
196
|
};
|
|
180
197
|
}
|
|
181
198
|
|
|
182
|
-
export async function buildToolStatusMessage(pi: ExtensionAPI) {
|
|
183
|
-
const summary = getToolStatusSummary(pi);
|
|
199
|
+
export async function buildToolStatusMessage(pi: ExtensionAPI, owner: object) {
|
|
200
|
+
const summary = getToolStatusSummary(pi, owner);
|
|
184
201
|
const persistedSetting = await persistedSettingLabel();
|
|
185
202
|
return sanitizeChromeDevtoolsDisplay(
|
|
186
203
|
[
|
|
187
204
|
`Chrome DevTools tools available: ${formatRuntimeStatus(summary)}`,
|
|
188
205
|
`Tool exposure: ${chromeDevtoolsToolExposureMode(pi)}`,
|
|
189
|
-
`Loaded capability tools this session: ${summary.loadedChromeToolCount}/${
|
|
206
|
+
`Loaded capability tools this session: ${summary.loadedChromeToolCount}/${summary.capabilityCount}`,
|
|
207
|
+
`WebMCP: ${webMcpEnabled(owner) ? "enabled · experimental · confirmation required for every call" : "disabled · experimental"}`,
|
|
190
208
|
`Loader: ${pi.getActiveTools().includes(CHROME_DEVTOOLS_LOAD_TOOL_NAME) ? "active" : "inactive"}`,
|
|
191
209
|
`Persisted tool catalog: ${persistedSetting}`,
|
|
192
|
-
...browserSettingsStatusLines(),
|
|
210
|
+
...browserSettingsStatusLines(owner),
|
|
193
211
|
...(state.settingsNotice ? [`Settings note: ${state.settingsNotice}`] : []),
|
|
194
212
|
`Other active tools preserved: ${summary.activeNonChromeToolCount}`,
|
|
195
|
-
`Endpoint: ${devToolsEndpoint()}`,
|
|
213
|
+
`Endpoint: ${devToolsEndpoint(owner)}`,
|
|
196
214
|
`Endpoint source: ${endpointSourceLabel()}`,
|
|
197
|
-
`Launch mode: ${launchModeLabel()}`,
|
|
198
|
-
...launchAttemptLines(),
|
|
215
|
+
`Launch mode: ${launchModeLabel(owner)}`,
|
|
216
|
+
...launchAttemptLines(owner),
|
|
199
217
|
].join("\n"),
|
|
200
218
|
);
|
|
201
219
|
}
|
|
202
220
|
|
|
203
|
-
export function buildQuickstartMessage() {
|
|
204
|
-
return buildSettingsSetupMessage();
|
|
221
|
+
export function buildQuickstartMessage(owner: object) {
|
|
222
|
+
return buildSettingsSetupMessage(owner);
|
|
205
223
|
}
|
|
206
224
|
|
|
207
|
-
export function buildBrowserStatusMessage() {
|
|
208
|
-
const lifecycle = browserLifecycleState();
|
|
225
|
+
export function buildBrowserStatusMessage(owner?: object) {
|
|
226
|
+
const lifecycle = browserLifecycleState(owner);
|
|
227
|
+
const webMcpIsEnabled = owner ? webMcpEnabled(owner) : false;
|
|
209
228
|
const browserState =
|
|
210
229
|
lifecycle === "starting"
|
|
211
230
|
? "starting managed browser"
|
|
@@ -221,36 +240,46 @@ export function buildBrowserStatusMessage() {
|
|
|
221
240
|
[
|
|
222
241
|
`Browser: ${browserState}`,
|
|
223
242
|
"Viewing this status does not probe the endpoint or launch Chrome.",
|
|
224
|
-
`Endpoint: ${devToolsEndpoint()}`,
|
|
243
|
+
`Endpoint: ${devToolsEndpoint(owner)}`,
|
|
225
244
|
`Endpoint source: ${endpointSourceLabel()}`,
|
|
226
|
-
`Launch mode: ${launchModeLabel()}`,
|
|
245
|
+
`Launch mode: ${launchModeLabel(owner)}`,
|
|
227
246
|
`Unpacked extensions: ${state.extensionPaths.length} (${state.extensionPathsSource})`,
|
|
247
|
+
`WebMCP: ${webMcpIsEnabled ? "enabled · experimental" : "disabled · experimental"}`,
|
|
248
|
+
...(webMcpIsEnabled && !managedBrowserForOwner(owner)?.ready
|
|
249
|
+
? [
|
|
250
|
+
"WebMCP warning: attached browser profiles may contain everyday authenticated sessions and sensitive state.",
|
|
251
|
+
]
|
|
252
|
+
: []),
|
|
228
253
|
...(state.extensionPaths.length > 0
|
|
229
254
|
? ["Unpacked extensions execute trusted browser code in an isolated managed browser."]
|
|
230
255
|
: []),
|
|
231
|
-
...launchAttemptLines(),
|
|
256
|
+
...launchAttemptLines(owner),
|
|
232
257
|
...(needsRecovery ? [launchHint(), endpointConfigHint()] : []),
|
|
233
258
|
].join("\n"),
|
|
234
259
|
);
|
|
235
260
|
}
|
|
236
261
|
|
|
237
|
-
export function buildSettingsSetupMessage() {
|
|
262
|
+
export function buildSettingsSetupMessage(owner: object) {
|
|
238
263
|
return sanitizeChromeDevtoolsDisplay(
|
|
239
264
|
[
|
|
240
|
-
`Chrome DevTools endpoint: ${devToolsEndpoint()}`,
|
|
265
|
+
`Chrome DevTools endpoint: ${devToolsEndpoint(owner)}`,
|
|
241
266
|
`Endpoint source: ${endpointSourceLabel()}`,
|
|
242
|
-
`Launch mode: ${launchModeLabel()}`,
|
|
243
|
-
...browserSettingsStatusLines(),
|
|
267
|
+
`Launch mode: ${launchModeLabel(owner)}`,
|
|
268
|
+
...browserSettingsStatusLines(owner),
|
|
244
269
|
launchHint(),
|
|
245
270
|
browserCandidateHint(),
|
|
246
|
-
...launchAttemptLines(),
|
|
271
|
+
...launchAttemptLines(owner),
|
|
247
272
|
endpointConfigHint(),
|
|
248
273
|
].join("\n"),
|
|
249
274
|
);
|
|
250
275
|
}
|
|
251
276
|
|
|
252
277
|
export function sanitizeChromeDevtoolsDisplay(value: string, maxCharacters = 50_000) {
|
|
253
|
-
const
|
|
278
|
+
const withoutBidi = stripVTControlCharacters(value).replace(
|
|
279
|
+
/[\u202a-\u202e\u2066-\u2069]/gu,
|
|
280
|
+
"�",
|
|
281
|
+
);
|
|
282
|
+
const sanitized = Array.from(withoutBidi, (character) => {
|
|
254
283
|
const codePoint = character.codePointAt(0) ?? 0;
|
|
255
284
|
const unsafeControl =
|
|
256
285
|
(codePoint >= 0 && codePoint <= 8) ||
|
|
@@ -262,7 +291,7 @@ export function sanitizeChromeDevtoolsDisplay(value: string, maxCharacters = 50_
|
|
|
262
291
|
return `${sanitized.slice(0, Math.max(0, maxCharacters - 1))}…`;
|
|
263
292
|
}
|
|
264
293
|
|
|
265
|
-
function browserSettingsStatusLines() {
|
|
294
|
+
function browserSettingsStatusLines(owner: object) {
|
|
266
295
|
const extensionLines =
|
|
267
296
|
state.extensionPaths.length > 0
|
|
268
297
|
? state.extensionPaths.map((extensionPath) => ` - ${extensionPath}`)
|
|
@@ -278,6 +307,7 @@ function browserSettingsStatusLines() {
|
|
|
278
307
|
`Browser executable: ${state.browserExecutable ?? "automatic discovery"} (${state.browserExecutableSource})`,
|
|
279
308
|
`Unpacked extensions (${state.extensionPathsSource}):`,
|
|
280
309
|
...extensionLines,
|
|
310
|
+
`WebMCP: ${webMcpEnabled(owner) ? "enabled · experimental · every call requires confirmation" : "disabled · experimental"}`,
|
|
281
311
|
"Confirmed menu settings apply before the next browser connection; manual JSON edits require /reload or session replacement.",
|
|
282
312
|
...(state.extensionPaths.length > 0
|
|
283
313
|
? [
|
|
@@ -287,9 +317,10 @@ function browserSettingsStatusLines() {
|
|
|
287
317
|
];
|
|
288
318
|
}
|
|
289
319
|
|
|
290
|
-
export function buildCommandGuide() {
|
|
320
|
+
export function buildCommandGuide(owner: object) {
|
|
291
321
|
return [
|
|
292
322
|
"Chrome DevTools commands:",
|
|
323
|
+
`WebMCP: ${webMcpEnabled(owner) ? "enabled" : "disabled"} · experimental · every page-provided call requires confirmation`,
|
|
293
324
|
"/chrome-devtools — open this menu",
|
|
294
325
|
"/chrome-devtools help — show command usage",
|
|
295
326
|
"/chrome-devtools quickstart — show endpoint and launch help",
|
|
@@ -302,8 +333,8 @@ export function buildCommandGuide() {
|
|
|
302
333
|
].join("\n");
|
|
303
334
|
}
|
|
304
335
|
|
|
305
|
-
export function allChromeDevtoolsTools() {
|
|
306
|
-
return
|
|
336
|
+
export function allChromeDevtoolsTools(owner: object) {
|
|
337
|
+
return effectiveCatalog(owner);
|
|
307
338
|
}
|
|
308
339
|
|
|
309
340
|
export function orderedChromeDevtoolsTools(selectedTools: ReadonlySet<ChromeDevToolsToolName>) {
|
|
@@ -311,7 +342,7 @@ export function orderedChromeDevtoolsTools(selectedTools: ReadonlySet<ChromeDevT
|
|
|
311
342
|
}
|
|
312
343
|
|
|
313
344
|
function formatRuntimeStatus(summary: ToolStatusSummary) {
|
|
314
|
-
return `${summary.availabilityStatus} (${summary.availableChromeToolCount}/${
|
|
345
|
+
return `${summary.availabilityStatus} (${summary.availableChromeToolCount}/${summary.capabilityCount} available)`;
|
|
315
346
|
}
|
|
316
347
|
|
|
317
348
|
async function persistedSettingLabel() {
|
|
@@ -334,6 +365,12 @@ function formatPersistedSelection(tools: readonly ChromeDevToolsToolName[]) {
|
|
|
334
365
|
return `${tools.length}/${CHROME_DEVTOOLS_TOOL_NAMES.length} selected: ${tools.join(", ")}`;
|
|
335
366
|
}
|
|
336
367
|
|
|
368
|
+
function effectiveCatalog(owner: object): ChromeDevToolsToolName[] {
|
|
369
|
+
return webMcpEnabled(owner)
|
|
370
|
+
? [...CHROME_DEVTOOLS_TOOL_NAMES]
|
|
371
|
+
: [...CORE_CHROME_DEVTOOLS_TOOL_NAMES];
|
|
372
|
+
}
|
|
373
|
+
|
|
337
374
|
async function persistSettings(selectedTools: readonly ChromeDevToolsToolName[]) {
|
|
338
375
|
await saveSettings({ tools: [...selectedTools], updatedAt: Date.now() });
|
|
339
376
|
}
|
package/src/tools.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { renderScreenshotResult, renderTextResult, renderToolCall, withStatus } from "./render.js";
|
|
13
13
|
import { state } from "./runtime.js";
|
|
14
14
|
import { formatScreenshotText, saveScreenshot, throwIfAborted } from "./screenshot.js";
|
|
15
|
-
import { CHROME_DEVTOOLS_TOOL_NAMES } from "./tool-names.js";
|
|
15
|
+
import { CHROME_DEVTOOLS_TOOL_NAMES, WEBMCP_TOOL_NAMES } from "./tool-names.js";
|
|
16
16
|
|
|
17
17
|
export const listPagesTool = defineTool({
|
|
18
18
|
name: CHROME_DEVTOOLS_TOOL_NAMES[0],
|
|
@@ -21,9 +21,9 @@ export const listPagesTool = defineTool({
|
|
|
21
21
|
parameters: Type.Object({}),
|
|
22
22
|
renderCall: renderToolCall("list pages"),
|
|
23
23
|
renderResult: renderTextResult,
|
|
24
|
-
async execute(_toolCallId, _params,
|
|
24
|
+
async execute(_toolCallId, _params, signal, _onUpdate, ctx) {
|
|
25
25
|
return withStatus(ctx, "list pages", async () => {
|
|
26
|
-
const pages = await listPages();
|
|
26
|
+
const pages = await listPages({ sessionOwner: ctx.sessionManager, signal });
|
|
27
27
|
return textResult(JSON.stringify(pages.map(formatPage), null, 2), { pages });
|
|
28
28
|
});
|
|
29
29
|
},
|
|
@@ -38,9 +38,9 @@ export const selectPageTool = defineTool({
|
|
|
38
38
|
}),
|
|
39
39
|
renderCall: renderToolCall("select page"),
|
|
40
40
|
renderResult: renderTextResult,
|
|
41
|
-
async execute(_toolCallId, params,
|
|
41
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
42
42
|
return withStatus(ctx, "select page", async () => {
|
|
43
|
-
const page = await getPage(params.pageId);
|
|
43
|
+
const page = await getPage(params.pageId, { sessionOwner: ctx.sessionManager, signal });
|
|
44
44
|
state.activePageId = page.id;
|
|
45
45
|
return textResult(`Selected page ${page.id}: ${page.title}\n${page.url}`, {
|
|
46
46
|
page: formatPage(page),
|
|
@@ -62,9 +62,12 @@ export const navigateTool = defineTool({
|
|
|
62
62
|
}),
|
|
63
63
|
renderCall: renderToolCall("navigate"),
|
|
64
64
|
renderResult: renderTextResult,
|
|
65
|
-
async execute(_toolCallId, params,
|
|
65
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
66
66
|
return withStatus(ctx, "navigate", async () => {
|
|
67
|
-
const { created, page } = await resolvePageForNavigation(params.pageId
|
|
67
|
+
const { created, page } = await resolvePageForNavigation(params.pageId, {
|
|
68
|
+
sessionOwner: ctx.sessionManager,
|
|
69
|
+
signal,
|
|
70
|
+
});
|
|
68
71
|
const result = await withCdp(page, async (client) => {
|
|
69
72
|
await client.send("Page.enable");
|
|
70
73
|
return client.send("Page.navigate", { url: params.url });
|
|
@@ -96,9 +99,12 @@ export const evaluateTool = defineTool({
|
|
|
96
99
|
}),
|
|
97
100
|
renderCall: renderToolCall("evaluate"),
|
|
98
101
|
renderResult: renderTextResult,
|
|
99
|
-
async execute(_toolCallId, params,
|
|
102
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
100
103
|
return withStatus(ctx, "evaluate", async () => {
|
|
101
|
-
const page = await resolvePage(params.pageId
|
|
104
|
+
const page = await resolvePage(params.pageId, {
|
|
105
|
+
sessionOwner: ctx.sessionManager,
|
|
106
|
+
signal,
|
|
107
|
+
});
|
|
102
108
|
const result = await withCdp(page, (client) =>
|
|
103
109
|
client.send("Runtime.evaluate", {
|
|
104
110
|
expression: params.expression,
|
|
@@ -113,6 +119,73 @@ export const evaluateTool = defineTool({
|
|
|
113
119
|
},
|
|
114
120
|
});
|
|
115
121
|
|
|
122
|
+
export const webMcpListToolsTool = defineTool({
|
|
123
|
+
name: WEBMCP_TOOL_NAMES[0],
|
|
124
|
+
label: "Chrome DevTools: List WebMCP Tools (Experimental)",
|
|
125
|
+
description:
|
|
126
|
+
"List bounded page-provided WebMCP tool descriptors from the selected Chrome page through the experimental CDP WebMCP domain. Definitions remain in this tool result and are never registered as dynamic Pi tools.",
|
|
127
|
+
parameters: Type.Object({
|
|
128
|
+
pageId: Type.Optional(
|
|
129
|
+
Type.String({
|
|
130
|
+
description: "Optional page id. Defaults to selected or first page.",
|
|
131
|
+
maxLength: 512,
|
|
132
|
+
}),
|
|
133
|
+
),
|
|
134
|
+
}),
|
|
135
|
+
renderCall: renderToolCall("list WebMCP tools"),
|
|
136
|
+
renderResult: renderTextResult,
|
|
137
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
138
|
+
return withStatus(ctx, "list WebMCP tools", async () => {
|
|
139
|
+
const implementation = await import("./webmcp/tools.js");
|
|
140
|
+
return implementation.executeWebMcpListTool(params, signal, ctx);
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
export const webMcpCallTool = defineTool({
|
|
146
|
+
name: WEBMCP_TOOL_NAMES[1],
|
|
147
|
+
label: "Chrome DevTools: Call WebMCP Tool (Experimental)",
|
|
148
|
+
description:
|
|
149
|
+
"Call one previously listed page-provided WebMCP tool after exact page, document, frame, origin, schema, annotation, and session revalidation. Every call requires observable user confirmation and output is bounded to Pi's 50 KB or 2,000-line limit.",
|
|
150
|
+
parameters: Type.Object({
|
|
151
|
+
sessionGeneration: Type.String({
|
|
152
|
+
description: "Session generation token returned by chrome_devtools_webmcp_list_tools.",
|
|
153
|
+
maxLength: 100,
|
|
154
|
+
}),
|
|
155
|
+
pageId: Type.String({ description: "Page id returned by the list tool.", maxLength: 512 }),
|
|
156
|
+
documentId: Type.String({
|
|
157
|
+
description: "Document loader id returned by the list tool.",
|
|
158
|
+
maxLength: 512,
|
|
159
|
+
}),
|
|
160
|
+
frameId: Type.String({ description: "Frame id returned by the list tool.", maxLength: 512 }),
|
|
161
|
+
frameOrigin: Type.String({
|
|
162
|
+
description: "Exact frame origin returned by the list tool.",
|
|
163
|
+
maxLength: 2_048,
|
|
164
|
+
}),
|
|
165
|
+
toolName: Type.String({
|
|
166
|
+
description: "Exact tool name returned by the list tool.",
|
|
167
|
+
maxLength: 512,
|
|
168
|
+
}),
|
|
169
|
+
schemaDigest: Type.String({
|
|
170
|
+
description: "Schema and annotation digest returned by the list tool.",
|
|
171
|
+
minLength: 64,
|
|
172
|
+
maxLength: 64,
|
|
173
|
+
pattern: "^[a-f0-9]{64}$",
|
|
174
|
+
}),
|
|
175
|
+
input: Type.Record(Type.String(), Type.Unknown(), {
|
|
176
|
+
description: "JSON object matching the page-provided input schema.",
|
|
177
|
+
}),
|
|
178
|
+
}),
|
|
179
|
+
renderCall: renderToolCall("call WebMCP tool"),
|
|
180
|
+
renderResult: renderTextResult,
|
|
181
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
182
|
+
return withStatus(ctx, "call WebMCP tool", async () => {
|
|
183
|
+
const implementation = await import("./webmcp/tools.js");
|
|
184
|
+
return implementation.executeWebMcpCallTool(params, signal, ctx);
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
116
189
|
export const screenshotTool = defineTool({
|
|
117
190
|
name: CHROME_DEVTOOLS_TOOL_NAMES[4],
|
|
118
191
|
label: "Chrome DevTools: Screenshot",
|
|
@@ -135,7 +208,10 @@ export const screenshotTool = defineTool({
|
|
|
135
208
|
renderResult: renderScreenshotResult,
|
|
136
209
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
137
210
|
return withStatus(ctx, "screenshot", async () => {
|
|
138
|
-
const page = await resolvePage(params.pageId
|
|
211
|
+
const page = await resolvePage(params.pageId, {
|
|
212
|
+
sessionOwner: ctx.sessionManager,
|
|
213
|
+
signal,
|
|
214
|
+
});
|
|
139
215
|
const result = await withCdp(page, async (client) => {
|
|
140
216
|
throwIfAborted(signal);
|
|
141
217
|
await client.send("Page.enable");
|