brave-mcp 1.2.0 → 1.3.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 +21 -2
- package/build/src/HeapSnapshotManager.js +21 -20
- package/build/src/McpContext.js +31 -1
- package/build/src/McpResponse.js +51 -7
- package/build/src/bin/brave-devtools-cli-options.js +48 -0
- package/build/src/bin/brave-devtools-mcp-cli-options.js +45 -1
- package/build/src/devtools/DevtoolsUtils.js +3 -4
- package/build/src/devtools/McpHostBindingAdapter.js +165 -0
- package/build/src/formatters/HeapSnapshotFormatter.js +8 -0
- package/build/src/third_party/THIRD_PARTY_NOTICES +2 -2
- package/build/src/third_party/bundled-packages.json +1 -1
- package/build/src/third_party/devtools-formatter-worker.js +0 -1
- package/build/src/third_party/devtools-heap-snapshot-worker.js +54 -3
- package/build/src/third_party/index.js +449 -196
- package/build/src/tools/memory.js +45 -0
- package/build/src/tools/screencast.js +30 -9
- package/build/src/tools/screenshot.js +158 -76
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -632,11 +632,13 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
|
|
|
632
632
|
- [`take_snapshot`](docs/tool-reference.md#take_snapshot)
|
|
633
633
|
- [`screencast_start`](docs/tool-reference.md#screencast_start)
|
|
634
634
|
- [`screencast_stop`](docs/tool-reference.md#screencast_stop)
|
|
635
|
-
- **Memory** (
|
|
635
|
+
- **Memory** (9 tools)
|
|
636
636
|
- [`take_heapsnapshot`](docs/tool-reference.md#take_heapsnapshot)
|
|
637
637
|
- [`close_heapsnapshot`](docs/tool-reference.md#close_heapsnapshot)
|
|
638
638
|
- [`get_heapsnapshot_class_nodes`](docs/tool-reference.md#get_heapsnapshot_class_nodes)
|
|
639
639
|
- [`get_heapsnapshot_details`](docs/tool-reference.md#get_heapsnapshot_details)
|
|
640
|
+
- [`get_heapsnapshot_dominators`](docs/tool-reference.md#get_heapsnapshot_dominators)
|
|
641
|
+
- [`get_heapsnapshot_edges`](docs/tool-reference.md#get_heapsnapshot_edges)
|
|
640
642
|
- [`get_heapsnapshot_retainers`](docs/tool-reference.md#get_heapsnapshot_retainers)
|
|
641
643
|
- [`get_heapsnapshot_retaining_paths`](docs/tool-reference.md#get_heapsnapshot_retaining_paths)
|
|
642
644
|
- [`get_heapsnapshot_summary`](docs/tool-reference.md#get_heapsnapshot_summary)
|
|
@@ -749,7 +751,7 @@ The Brave DevTools MCP server supports the following configuration options:
|
|
|
749
751
|
- **Type:** string
|
|
750
752
|
|
|
751
753
|
- **`--categoryExperimentalWebmcp`/ `--category-experimental-webmcp`**
|
|
752
|
-
Set to true to enable debugging WebMCP tools. Requires Brave with the following flags: `--enable-features=
|
|
754
|
+
Set to true to enable debugging WebMCP tools. Requires Brave with the following flags: `--enable-features=WebMCP,DevToolsWebMCPSupport`
|
|
753
755
|
- **Type:** boolean
|
|
754
756
|
|
|
755
757
|
- **`--braveArg`/ `--brave-arg`**
|
|
@@ -803,6 +805,23 @@ The Brave DevTools MCP server supports the following configuration options:
|
|
|
803
805
|
- **Type:** boolean
|
|
804
806
|
- **Default:** `false`
|
|
805
807
|
|
|
808
|
+
- **`--screenshotFormat`/ `--screenshot-format`**
|
|
809
|
+
Override the default output format used by take_screenshot when the caller does not specify one. JPEG and WebP are ~3-5x smaller than PNG, which helps reduce context size in AI conversations. Unset preserves the existing default ("png").
|
|
810
|
+
- **Type:** string
|
|
811
|
+
- **Choices:** `jpeg`, `png`, `webp`
|
|
812
|
+
|
|
813
|
+
- **`--screenshotQuality`/ `--screenshot-quality`**
|
|
814
|
+
Override the default compression quality (0-100) used by take_screenshot for JPEG and WebP when the caller does not specify one. Lower values mean smaller files. Ignored for PNG. Unset preserves the Puppeteer default.
|
|
815
|
+
- **Type:** number
|
|
816
|
+
|
|
817
|
+
- **`--screenshotMaxWidth`/ `--screenshot-max-width`**
|
|
818
|
+
Maximum width in pixels for screenshots. If the captured image is wider, it is downscaled (preserving aspect ratio) before being returned. Reduces context size in AI conversations. Unset means no resize.
|
|
819
|
+
- **Type:** number
|
|
820
|
+
|
|
821
|
+
- **`--screenshotMaxHeight`/ `--screenshot-max-height`**
|
|
822
|
+
Maximum height in pixels for screenshots. If the captured image is taller, it is downscaled (preserving aspect ratio) before being returned. Can be combined with --screenshot-max-width; the smaller scale factor wins. Unset means no resize.
|
|
823
|
+
- **Type:** number
|
|
824
|
+
|
|
806
825
|
- **`--slim`**
|
|
807
826
|
Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.
|
|
808
827
|
- **Type:** boolean
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import fsSync from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
-
import { isNodeLike } from './formatters/HeapSnapshotFormatter.js';
|
|
9
8
|
import { DevTools } from './third_party/index.js';
|
|
10
9
|
import { createIdGenerator, stableIdSymbol, } from './utils/id.js';
|
|
11
10
|
export class HeapSnapshotManager {
|
|
@@ -67,38 +66,40 @@ export class HeapSnapshotManager {
|
|
|
67
66
|
const provider = snapshot.createNodesProviderForClass(className, filter);
|
|
68
67
|
return await provider.serializeItemsRange(0, Infinity);
|
|
69
68
|
}
|
|
70
|
-
async findNodeIndexById(filePath, nodeId) {
|
|
71
|
-
const snapshot = await this.getSnapshot(filePath);
|
|
72
|
-
const aggregates = await this.getAggregates(filePath);
|
|
73
|
-
const filter = new DevTools.HeapSnapshotModel.HeapSnapshotModel.NodeFilter();
|
|
74
|
-
for (const classKey of Object.keys(aggregates)) {
|
|
75
|
-
const provider = snapshot.createNodesProviderForClass(classKey, filter);
|
|
76
|
-
const range = await provider.serializeItemsRange(0, Infinity);
|
|
77
|
-
for (const item of range.items) {
|
|
78
|
-
if (isNodeLike(item) && item.id === nodeId) {
|
|
79
|
-
return item.nodeIndex;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return undefined;
|
|
84
|
-
}
|
|
85
69
|
async getRetainers(filePath, nodeId) {
|
|
86
|
-
const
|
|
70
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
71
|
+
const nodeIndex = await snapshot.nodeIndexForId(nodeId);
|
|
87
72
|
if (nodeIndex === undefined) {
|
|
88
73
|
throw new Error(`Node with ID ${nodeId} not found`);
|
|
89
74
|
}
|
|
90
|
-
const snapshot = await this.getSnapshot(filePath);
|
|
91
75
|
const provider = snapshot.createRetainingEdgesProvider(nodeIndex);
|
|
92
76
|
return await provider.serializeItemsRange(0, Infinity);
|
|
93
77
|
}
|
|
94
78
|
async getRetainingPaths(filePath, nodeId, maxDepth, maxNodes, maxSiblings) {
|
|
95
|
-
const
|
|
79
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
80
|
+
const nodeIndex = await snapshot.nodeIndexForId(nodeId);
|
|
96
81
|
if (nodeIndex === undefined) {
|
|
97
82
|
throw new Error(`Node with ID ${nodeId} not found`);
|
|
98
83
|
}
|
|
99
|
-
const snapshot = await this.getSnapshot(filePath);
|
|
100
84
|
return await snapshot.getRetainingPaths(nodeIndex, maxDepth, maxNodes, maxSiblings);
|
|
101
85
|
}
|
|
86
|
+
async getDominatorsOf(filePath, nodeId) {
|
|
87
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
88
|
+
const nodeIndex = await snapshot.nodeIndexForId(nodeId);
|
|
89
|
+
if (nodeIndex === undefined) {
|
|
90
|
+
throw new Error(`Node with ID ${nodeId} not found`);
|
|
91
|
+
}
|
|
92
|
+
return await snapshot.getDominatorsOf(nodeIndex);
|
|
93
|
+
}
|
|
94
|
+
async getEdges(filePath, nodeId) {
|
|
95
|
+
const snapshot = await this.getSnapshot(filePath);
|
|
96
|
+
const nodeIndex = await snapshot.nodeIndexForId(nodeId);
|
|
97
|
+
if (nodeIndex === undefined) {
|
|
98
|
+
throw new Error(`Node with ID ${nodeId} not found`);
|
|
99
|
+
}
|
|
100
|
+
const provider = snapshot.createEdgesProvider(nodeIndex);
|
|
101
|
+
return await provider.serializeItemsRange(0, Infinity);
|
|
102
|
+
}
|
|
102
103
|
#getCachedSnapshot(filePath) {
|
|
103
104
|
const absolutePath = path.resolve(filePath);
|
|
104
105
|
const cached = this.#snapshots.get(absolutePath);
|
package/build/src/McpContext.js
CHANGED
|
@@ -47,7 +47,11 @@ export class McpContext {
|
|
|
47
47
|
#heapSnapshotManager = new HeapSnapshotManager();
|
|
48
48
|
#roots = undefined;
|
|
49
49
|
constructor(browser, logger, options, locatorClass) {
|
|
50
|
-
overrideDevToolsGlobals(
|
|
50
|
+
overrideDevToolsGlobals({
|
|
51
|
+
loadResource: (url) => {
|
|
52
|
+
return this.loadResource(url);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
51
55
|
this.browser = browser;
|
|
52
56
|
this.logger = logger;
|
|
53
57
|
this.#locatorClass = locatorClass;
|
|
@@ -663,5 +667,31 @@ export class McpContext {
|
|
|
663
667
|
async getHeapSnapshotRetainingPaths(filePath, nodeId, maxDepth, maxNodes, maxSiblings) {
|
|
664
668
|
return await this.#heapSnapshotManager.getRetainingPaths(filePath, nodeId, maxDepth, maxNodes, maxSiblings);
|
|
665
669
|
}
|
|
670
|
+
async getHeapSnapshotDominators(filePath, nodeId) {
|
|
671
|
+
return await this.#heapSnapshotManager.getDominatorsOf(filePath, nodeId);
|
|
672
|
+
}
|
|
673
|
+
async loadResource(path) {
|
|
674
|
+
const url = new URL(path);
|
|
675
|
+
switch (url.protocol) {
|
|
676
|
+
case 'https:':
|
|
677
|
+
case 'http:': {
|
|
678
|
+
// TODO: Verify allow/block list
|
|
679
|
+
const response = await fetch(url);
|
|
680
|
+
if (!response.ok) {
|
|
681
|
+
throw new Error(`Failed to load resource: ${url}`);
|
|
682
|
+
}
|
|
683
|
+
return response.text();
|
|
684
|
+
}
|
|
685
|
+
case 'file:': {
|
|
686
|
+
await this.validatePath(fileURLToPath(url));
|
|
687
|
+
return await fsPromises.readFile(url, 'utf-8');
|
|
688
|
+
}
|
|
689
|
+
default:
|
|
690
|
+
throw new Error(`Unsupported protocol for: ${url}`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
async getHeapSnapshotEdges(filePath, nodeId) {
|
|
694
|
+
return await this.#heapSnapshotManager.getEdges(filePath, nodeId);
|
|
695
|
+
}
|
|
666
696
|
}
|
|
667
697
|
//# sourceMappingURL=McpContext.js.map
|
package/build/src/McpResponse.js
CHANGED
|
@@ -73,6 +73,9 @@ async function getToolGroups(page) {
|
|
|
73
73
|
return [];
|
|
74
74
|
}
|
|
75
75
|
const toolGroups = await page.pptrPage.evaluate(() => {
|
|
76
|
+
if (window.__dtmcp) {
|
|
77
|
+
window.__dtmcp.toolGroups = [];
|
|
78
|
+
}
|
|
76
79
|
return new Promise(resolve => {
|
|
77
80
|
const event = new CustomEvent('devtoolstooldiscovery');
|
|
78
81
|
const groups = [];
|
|
@@ -85,7 +88,8 @@ async function getToolGroups(page) {
|
|
|
85
88
|
window.__dtmcp.toolGroups = [];
|
|
86
89
|
}
|
|
87
90
|
if (typeof toolGroup.name !== 'string' ||
|
|
88
|
-
|
|
91
|
+
(toolGroup.description &&
|
|
92
|
+
typeof toolGroup.description !== 'string') ||
|
|
89
93
|
!Array.isArray(toolGroup.tools)) {
|
|
90
94
|
console.error('Invalid toolGroup:', toolGroup);
|
|
91
95
|
return;
|
|
@@ -337,6 +341,13 @@ export class McpResponse {
|
|
|
337
341
|
retainingPaths,
|
|
338
342
|
};
|
|
339
343
|
}
|
|
344
|
+
setHeapSnapshotDominators(dominators) {
|
|
345
|
+
this.#heapSnapshotOptions = {
|
|
346
|
+
...this.#heapSnapshotOptions,
|
|
347
|
+
include: true,
|
|
348
|
+
dominators,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
340
351
|
attachImage(value) {
|
|
341
352
|
this.#images.push(value);
|
|
342
353
|
}
|
|
@@ -535,7 +546,7 @@ export class McpResponse {
|
|
|
535
546
|
errorMessage: this.#error?.message,
|
|
536
547
|
}, useToon);
|
|
537
548
|
}
|
|
538
|
-
format(toolName, context, data, useToon) {
|
|
549
|
+
async format(toolName, context, data, useToon) {
|
|
539
550
|
const structuredContent = {};
|
|
540
551
|
const response = [];
|
|
541
552
|
if (this.#textResponseLines.length) {
|
|
@@ -615,8 +626,12 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
615
626
|
const contextLabel = isolatedContextName
|
|
616
627
|
? ` isolatedContext=${isolatedContextName}`
|
|
617
628
|
: '';
|
|
618
|
-
|
|
619
|
-
|
|
629
|
+
const title = await fetchPageTitle(page);
|
|
630
|
+
const pageLabel = title
|
|
631
|
+
? `${truncateTitle(title)} (${page.url()})`
|
|
632
|
+
: page.url();
|
|
633
|
+
parts.push(`${context.getPageId(page)}: ${pageLabel}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`);
|
|
634
|
+
structuredPages.push(createStructuredPage(page, context, title));
|
|
620
635
|
}
|
|
621
636
|
response.push(...parts);
|
|
622
637
|
structuredContent.pages = structuredPages;
|
|
@@ -630,8 +645,12 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
630
645
|
const contextLabel = isolatedContextName
|
|
631
646
|
? ` isolatedContext=${isolatedContextName}`
|
|
632
647
|
: '';
|
|
633
|
-
|
|
634
|
-
|
|
648
|
+
const title = await fetchPageTitle(page);
|
|
649
|
+
const pageLabel = title
|
|
650
|
+
? `${truncateTitle(title)} (${page.url()})`
|
|
651
|
+
: page.url();
|
|
652
|
+
response.push(`${context.getPageId(page)}: ${pageLabel}${context.isPageSelected(page) ? ' [selected]' : ''}${contextLabel}`);
|
|
653
|
+
structuredExtensionPages.push(createStructuredPage(page, context, title));
|
|
635
654
|
}
|
|
636
655
|
structuredContent.extensionPages = structuredExtensionPages;
|
|
637
656
|
}
|
|
@@ -778,6 +797,17 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
778
797
|
structuredContent.heapSnapshotRetainingPaths =
|
|
779
798
|
retainingPaths;
|
|
780
799
|
}
|
|
800
|
+
const dominators = this.#heapSnapshotOptions.dominators;
|
|
801
|
+
if (dominators) {
|
|
802
|
+
response.push('### Dominator Chain');
|
|
803
|
+
if (dominators.length === 0) {
|
|
804
|
+
response.push('No dominators found.');
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
response.push(HeapSnapshotFormatter.formatDominators(dominators));
|
|
808
|
+
}
|
|
809
|
+
structuredContent.heapSnapshotDominators = dominators;
|
|
810
|
+
}
|
|
781
811
|
}
|
|
782
812
|
if (data.detailedNetworkRequest) {
|
|
783
813
|
response.push(data.detailedNetworkRequest.toStringDetailed());
|
|
@@ -931,11 +961,25 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
931
961
|
this.#textResponseLines = [];
|
|
932
962
|
}
|
|
933
963
|
}
|
|
934
|
-
function
|
|
964
|
+
function truncateTitle(title, maxLength = 50) {
|
|
965
|
+
if (title.length <= maxLength) {
|
|
966
|
+
return title;
|
|
967
|
+
}
|
|
968
|
+
return title.slice(0, maxLength - 3) + '...';
|
|
969
|
+
}
|
|
970
|
+
async function fetchPageTitle(page) {
|
|
971
|
+
return Promise.race([
|
|
972
|
+
page.title().catch(() => ''),
|
|
973
|
+
new Promise(resolve => setTimeout(() => resolve(''), 1000)),
|
|
974
|
+
]);
|
|
975
|
+
}
|
|
976
|
+
function createStructuredPage(page, context, rawTitle) {
|
|
935
977
|
const isolatedContextName = context.getIsolatedContextName(page);
|
|
978
|
+
const title = truncateTitle(rawTitle);
|
|
936
979
|
const entry = {
|
|
937
980
|
id: context.getPageId(page),
|
|
938
981
|
url: page.url(),
|
|
982
|
+
title,
|
|
939
983
|
selected: context.isPageSelected(page),
|
|
940
984
|
};
|
|
941
985
|
if (isolatedContextName) {
|
|
@@ -312,6 +312,54 @@ export const commands = {
|
|
|
312
312
|
},
|
|
313
313
|
},
|
|
314
314
|
},
|
|
315
|
+
get_heapsnapshot_dominators: {
|
|
316
|
+
description: 'Loads a memory heapsnapshot and returns the dominator chain for a specific node ID. This helps to identify which objects are keeping the target node alive. (requires flag: --memoryDebugging=true)',
|
|
317
|
+
category: 'Memory',
|
|
318
|
+
args: {
|
|
319
|
+
filePath: {
|
|
320
|
+
name: 'filePath',
|
|
321
|
+
type: 'string',
|
|
322
|
+
description: 'A path to a .heapsnapshot file to read.',
|
|
323
|
+
required: true,
|
|
324
|
+
},
|
|
325
|
+
nodeId: {
|
|
326
|
+
name: 'nodeId',
|
|
327
|
+
type: 'number',
|
|
328
|
+
description: 'The node ID to get the dominator chain for.',
|
|
329
|
+
required: true,
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
get_heapsnapshot_edges: {
|
|
334
|
+
description: 'Loads a memory heapsnapshot and returns outgoing edges (references) for a specific node ID. (requires flag: --memoryDebugging=true)',
|
|
335
|
+
category: 'Memory',
|
|
336
|
+
args: {
|
|
337
|
+
filePath: {
|
|
338
|
+
name: 'filePath',
|
|
339
|
+
type: 'string',
|
|
340
|
+
description: 'A path to a .heapsnapshot file to read.',
|
|
341
|
+
required: true,
|
|
342
|
+
},
|
|
343
|
+
nodeId: {
|
|
344
|
+
name: 'nodeId',
|
|
345
|
+
type: 'number',
|
|
346
|
+
description: 'The node ID to get outgoing edges for.',
|
|
347
|
+
required: true,
|
|
348
|
+
},
|
|
349
|
+
pageIdx: {
|
|
350
|
+
name: 'pageIdx',
|
|
351
|
+
type: 'number',
|
|
352
|
+
description: 'The page index for pagination.',
|
|
353
|
+
required: false,
|
|
354
|
+
},
|
|
355
|
+
pageSize: {
|
|
356
|
+
name: 'pageSize',
|
|
357
|
+
type: 'number',
|
|
358
|
+
description: 'The page size for pagination.',
|
|
359
|
+
required: false,
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
},
|
|
315
363
|
get_heapsnapshot_retainers: {
|
|
316
364
|
description: 'Loads a memory heapsnapshot and returns retainers for a specific node ID. (requires flag: --memoryDebugging=true)',
|
|
317
365
|
category: 'Memory',
|
|
@@ -185,7 +185,7 @@ export const cliOptions = {
|
|
|
185
185
|
},
|
|
186
186
|
categoryExperimentalWebmcp: {
|
|
187
187
|
type: 'boolean',
|
|
188
|
-
describe: 'Set to true to enable debugging WebMCP tools. Requires Brave with the following flags: `--enable-features=
|
|
188
|
+
describe: 'Set to true to enable debugging WebMCP tools. Requires Brave with the following flags: `--enable-features=WebMCP,DevToolsWebMCPSupport`',
|
|
189
189
|
},
|
|
190
190
|
braveArg: {
|
|
191
191
|
type: 'array',
|
|
@@ -256,6 +256,50 @@ export const cliOptions = {
|
|
|
256
256
|
hidden: true,
|
|
257
257
|
describe: 'Include watchdog PID in Clearcut request headers (for testing).',
|
|
258
258
|
},
|
|
259
|
+
screenshotFormat: {
|
|
260
|
+
type: 'string',
|
|
261
|
+
description: 'Override the default output format used by take_screenshot when the caller does not specify one. JPEG and WebP are ~3-5x smaller than PNG, which helps reduce context size in AI conversations. Unset preserves the existing default ("png").',
|
|
262
|
+
choices: ['jpeg', 'png', 'webp'],
|
|
263
|
+
},
|
|
264
|
+
screenshotQuality: {
|
|
265
|
+
type: 'number',
|
|
266
|
+
description: 'Override the default compression quality (0-100) used by take_screenshot for JPEG and WebP when the caller does not specify one. Lower values mean smaller files. Ignored for PNG. Unset preserves the Puppeteer default.',
|
|
267
|
+
coerce: (value) => {
|
|
268
|
+
if (value === undefined) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (!Number.isInteger(value) || value < 0 || value > 100) {
|
|
272
|
+
throw new Error(`Invalid screenshotQuality ${value}. Expected an integer between 0 and 100.`);
|
|
273
|
+
}
|
|
274
|
+
return value;
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
screenshotMaxWidth: {
|
|
278
|
+
type: 'number',
|
|
279
|
+
description: 'Maximum width in pixels for screenshots. If the captured image is wider, it is downscaled (preserving aspect ratio) before being returned. Reduces context size in AI conversations. Unset means no resize.',
|
|
280
|
+
coerce: (value) => {
|
|
281
|
+
if (value === undefined) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
285
|
+
throw new Error(`Invalid screenshotMaxWidth ${value}. Expected a positive integer.`);
|
|
286
|
+
}
|
|
287
|
+
return value;
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
screenshotMaxHeight: {
|
|
291
|
+
type: 'number',
|
|
292
|
+
description: 'Maximum height in pixels for screenshots. If the captured image is taller, it is downscaled (preserving aspect ratio) before being returned. Can be combined with --screenshot-max-width; the smaller scale factor wins. Unset means no resize.',
|
|
293
|
+
coerce: (value) => {
|
|
294
|
+
if (value === undefined) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
298
|
+
throw new Error(`Invalid screenshotMaxHeight ${value}. Expected a positive integer.`);
|
|
299
|
+
}
|
|
300
|
+
return value;
|
|
301
|
+
},
|
|
302
|
+
},
|
|
259
303
|
slim: {
|
|
260
304
|
type: 'boolean',
|
|
261
305
|
describe: 'Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.',
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { Mutex } from '../Mutex.js';
|
|
7
7
|
import { DevTools } from '../third_party/index.js';
|
|
8
8
|
import { PuppeteerDevToolsConnection } from './DevToolsConnectionAdapter.js';
|
|
9
|
+
import { McpHostBindingAdapter } from './McpHostBindingAdapter.js';
|
|
9
10
|
/**
|
|
10
11
|
* A mock implementation of an issues manager that only implements the methods
|
|
11
12
|
* that are actually used by the IssuesAggregator
|
|
@@ -16,10 +17,8 @@ export class FakeIssuesManager extends DevTools.Common.ObjectWrapper
|
|
|
16
17
|
return [];
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
|
-
export function overrideDevToolsGlobals() {
|
|
20
|
-
|
|
21
|
-
// (installInspectorFrontendHost(new McpHostBindings())). Since v1.2.0 the
|
|
22
|
-
// bundle self-installs them and no longer exports DevTools.Host for callers.
|
|
20
|
+
export function overrideDevToolsGlobals({ loadResource, }) {
|
|
21
|
+
DevTools.Host.InspectorFrontendHost.installInspectorFrontendHost(new McpHostBindingAdapter(loadResource));
|
|
23
22
|
// DevTools CDP errors can get noisy.
|
|
24
23
|
DevTools.ProtocolClient.InspectorBackend.test.suppressRequestErrors = true;
|
|
25
24
|
// Stub out Network emulation commands on the DevTools Agent prototype globally.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
7
|
+
import { DevTools } from '../third_party/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* BaseClass that is noop or throws for methods.
|
|
10
|
+
* the McpHostBindingAdapter should only implement methods
|
|
11
|
+
* that it needs to support.
|
|
12
|
+
*/
|
|
13
|
+
class BaseMcpHostBindingAdapter {
|
|
14
|
+
connectAutomaticFileSystem() { }
|
|
15
|
+
disconnectAutomaticFileSystem() { }
|
|
16
|
+
addFileSystem() { }
|
|
17
|
+
loadCompleted() { }
|
|
18
|
+
indexPath() { }
|
|
19
|
+
setInspectedPageBounds() { }
|
|
20
|
+
showCertificateViewer() { }
|
|
21
|
+
setWhitelistedShortcuts() { }
|
|
22
|
+
setEyeDropperActive() { }
|
|
23
|
+
inspectElementCompleted() { }
|
|
24
|
+
openInNewTab() { }
|
|
25
|
+
openSearchResultsInNewTab() { }
|
|
26
|
+
showItemInFolder() { }
|
|
27
|
+
removeFileSystem() { }
|
|
28
|
+
requestFileSystems() { }
|
|
29
|
+
save() { }
|
|
30
|
+
append() { }
|
|
31
|
+
close() { }
|
|
32
|
+
searchInPath() { }
|
|
33
|
+
stopIndexing() { }
|
|
34
|
+
bringToFront() { }
|
|
35
|
+
closeWindow() { }
|
|
36
|
+
copyText() { }
|
|
37
|
+
inspectedURLChanged() { }
|
|
38
|
+
isolatedFileSystem() {
|
|
39
|
+
throw new Error('Not implemented');
|
|
40
|
+
}
|
|
41
|
+
registerPreference() { }
|
|
42
|
+
getPreferences() { }
|
|
43
|
+
getPreference() { }
|
|
44
|
+
setPreference() { }
|
|
45
|
+
removePreference() { }
|
|
46
|
+
clearPreferences() { }
|
|
47
|
+
getSyncInformation() { }
|
|
48
|
+
getHostConfig() { }
|
|
49
|
+
upgradeDraggedFileSystemPermissions() { }
|
|
50
|
+
platform() {
|
|
51
|
+
throw new Error('Not implemented');
|
|
52
|
+
}
|
|
53
|
+
recordCountHistogram() { }
|
|
54
|
+
recordEnumeratedHistogram() { }
|
|
55
|
+
recordPerformanceHistogram() { }
|
|
56
|
+
recordPerformanceHistogramMedium() { }
|
|
57
|
+
recordUserMetricsAction() { }
|
|
58
|
+
recordNewBadgeUsage() { }
|
|
59
|
+
sendMessageToBackend() { }
|
|
60
|
+
setDevicesDiscoveryConfig() { }
|
|
61
|
+
setDevicesUpdatesEnabled() { }
|
|
62
|
+
openRemotePage() { }
|
|
63
|
+
openNodeFrontend() { }
|
|
64
|
+
setInjectedScriptForOrigin() { }
|
|
65
|
+
setIsDocked() { }
|
|
66
|
+
showSurvey() { }
|
|
67
|
+
canShowSurvey() { }
|
|
68
|
+
zoomFactor() {
|
|
69
|
+
throw new Error('Not implemented');
|
|
70
|
+
}
|
|
71
|
+
zoomIn() { }
|
|
72
|
+
zoomOut() { }
|
|
73
|
+
resetZoom() { }
|
|
74
|
+
showContextMenuAtPoint() { }
|
|
75
|
+
reattach() { }
|
|
76
|
+
readyForTest() { }
|
|
77
|
+
connectionReady() { }
|
|
78
|
+
setOpenNewWindowForPopups() { }
|
|
79
|
+
isHostedMode() {
|
|
80
|
+
throw new Error('Not implemented');
|
|
81
|
+
}
|
|
82
|
+
setAddExtensionCallback() { }
|
|
83
|
+
initialTargetId() {
|
|
84
|
+
throw new Error('Not implemented');
|
|
85
|
+
}
|
|
86
|
+
doAidaConversation(_request, _streamId, cb) {
|
|
87
|
+
cb({
|
|
88
|
+
error: 'Not implemented',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
registerAidaClientEvent(_request, cb) {
|
|
92
|
+
cb({
|
|
93
|
+
error: 'Not implemented',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
aidaCodeComplete(_request, cb) {
|
|
97
|
+
cb({
|
|
98
|
+
error: 'Not implemented',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
dispatchHttpRequest(_request, cb) {
|
|
102
|
+
cb({
|
|
103
|
+
error: 'Not implemented',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
recordImpression() { }
|
|
107
|
+
recordResize() { }
|
|
108
|
+
recordClick() { }
|
|
109
|
+
recordHover() { }
|
|
110
|
+
recordDrag() { }
|
|
111
|
+
recordChange() { }
|
|
112
|
+
recordKeyDown() { }
|
|
113
|
+
recordSettingAccess() { }
|
|
114
|
+
recordFunctionCall() { }
|
|
115
|
+
setChromeFlag() { }
|
|
116
|
+
requestRestart() { }
|
|
117
|
+
loadNetworkResource(_urlString, _headers, _streamId, _callback) { }
|
|
118
|
+
}
|
|
119
|
+
export class McpHostBindingAdapter extends BaseMcpHostBindingAdapter {
|
|
120
|
+
#loadResource;
|
|
121
|
+
constructor(loadResource) {
|
|
122
|
+
super();
|
|
123
|
+
this.#loadResource = loadResource;
|
|
124
|
+
}
|
|
125
|
+
isolatedFileSystem() {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
platform() {
|
|
129
|
+
switch (process.platform) {
|
|
130
|
+
case 'darwin':
|
|
131
|
+
return 'mac';
|
|
132
|
+
case 'win32':
|
|
133
|
+
return 'windows';
|
|
134
|
+
default:
|
|
135
|
+
return 'linux';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
zoomFactor() {
|
|
139
|
+
return 1;
|
|
140
|
+
}
|
|
141
|
+
isHostedMode() {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
initialTargetId() {
|
|
145
|
+
return Promise.resolve(null);
|
|
146
|
+
}
|
|
147
|
+
loadNetworkResource(urlString, _headers, streamId, callback) {
|
|
148
|
+
if (!URL.canParse(urlString)) {
|
|
149
|
+
callback({
|
|
150
|
+
statusCode: 404,
|
|
151
|
+
urlValid: false,
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
this.#loadResource(urlString)
|
|
156
|
+
.then(content => {
|
|
157
|
+
DevTools.Host.ResourceLoader.streamWrite(streamId, content);
|
|
158
|
+
callback({ statusCode: 200 });
|
|
159
|
+
})
|
|
160
|
+
.catch(() => {
|
|
161
|
+
callback({ statusCode: 404 });
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=McpHostBindingAdapter.js.map
|
|
@@ -59,6 +59,14 @@ export class HeapSnapshotFormatter {
|
|
|
59
59
|
}
|
|
60
60
|
return lines.join('\n');
|
|
61
61
|
}
|
|
62
|
+
static formatDominators(dominators) {
|
|
63
|
+
const lines = [];
|
|
64
|
+
lines.push('nodeId,nodeName,selfSize,retainedSize');
|
|
65
|
+
for (const node of dominators) {
|
|
66
|
+
lines.push(`${node.nodeId},${node.nodeName},${DevTools.I18n.ByteUtilities.formatBytesToKb(node.selfSize)},${DevTools.I18n.ByteUtilities.formatBytesToKb(node.retainedSize)}`);
|
|
67
|
+
}
|
|
68
|
+
return lines.join('\n');
|
|
69
|
+
}
|
|
62
70
|
#getSortedAggregates() {
|
|
63
71
|
return Object.values(this.#aggregates).sort((a, b) => b.maxRet - a.maxRet);
|
|
64
72
|
}
|
|
@@ -324,7 +324,7 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH RE
|
|
|
324
324
|
|
|
325
325
|
Name: semver
|
|
326
326
|
URL: git+https://github.com/npm/node-semver.git
|
|
327
|
-
Version: 7.8.
|
|
327
|
+
Version: 7.8.3
|
|
328
328
|
License: ISC
|
|
329
329
|
|
|
330
330
|
The ISC License
|
|
@@ -910,7 +910,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
910
910
|
|
|
911
911
|
Name: @toon-format/toon
|
|
912
912
|
URL: https://toonformat.dev
|
|
913
|
-
Version: 2.
|
|
913
|
+
Version: 2.3.0
|
|
914
914
|
License: MIT
|
|
915
915
|
|
|
916
916
|
MIT License
|
|
@@ -2645,7 +2645,6 @@ var ExperimentName;
|
|
|
2645
2645
|
ExperimentName["ALL"] = "*";
|
|
2646
2646
|
ExperimentName["PROTOCOL_MONITOR"] = "protocol-monitor";
|
|
2647
2647
|
ExperimentName["INSTRUMENTATION_BREAKPOINTS"] = "instrumentation-breakpoints";
|
|
2648
|
-
ExperimentName["USE_SOURCE_MAP_SCOPES"] = "use-source-map-scopes";
|
|
2649
2648
|
ExperimentName["DURABLE_MESSAGES"] = "durable-messages";
|
|
2650
2649
|
ExperimentName["JPEG_XL"] = "jpeg-xl";
|
|
2651
2650
|
ExperimentName["PLUS_BUTTON"] = "plus-button";
|