Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/tool-reference.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- AUTO GENERATED DO NOT EDIT - run 'npm run gen' to update-->

# Chrome DevTools MCP Tool Reference (~6940 cl100k_base tokens)
# Chrome DevTools MCP Tool Reference (~6975 cl100k_base tokens)

- **[Input automation](#input-automation)** (9 tools)
- [`click`](#click)
Expand Down Expand Up @@ -393,6 +393,7 @@ in the DevTools Elements panel (if any).

**Parameters:**

- **diff** (boolean) _(optional)_: Return only the changes since the last snapshot. The cache is reset on navigation.
- **filePath** (string) _(optional)_: The absolute path, or a path relative to the current working directory, to save the snapshot to instead of attaching it to the response.
- **verbose** (boolean) _(optional)_: Whether to include all possible information available in the full a11y tree. Default is false.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"typecheck": "tsc --noEmit",
"format": "eslint --cache --fix . && prettier --write --cache .",
"check-format": "eslint --cache . && prettier --check --cache .;",
"gen": "npm run build && npm run docs:generate && npm run cli:generate && npm run format",
"gen": "npm run bundle && npm run docs:generate && npm run cli:generate && npm run format",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please revert

"docs:generate": "node --experimental-strip-types scripts/generate-docs.ts",
"start": "npm run build && node build/src/index.js",
"start-debug": "DEBUG=mcp:* DEBUG_COLORS=false npm run build && node build/src/index.js",
Expand Down
40 changes: 40 additions & 0 deletions scripts/post-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function main(): void {

// Create i18n mock
const i18nDir = path.join(BUILD_DIR, devtoolsFrontEndCorePath, 'i18n');
fs.mkdirSync(i18nDir, {recursive: true});
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert?

const localesFile = path.join(i18nDir, 'locales.js');
const localesContent = `
export const LOCALES = [
Expand Down Expand Up @@ -94,6 +95,45 @@ export const ExperimentName = {
writeFile(runtimeFile, runtimeContent);

copyDevToolsDescriptionFiles();
copySnapshotFiles();
}

function copySnapshotFiles() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert?

const testsDir = path.join(process.cwd(), 'tests');
const buildTestsDir = path.join(BUILD_DIR, 'tests');

if (!fs.existsSync(buildTestsDir)) {
fs.mkdirSync(buildTestsDir, {recursive: true});
}

const files = fs.readdirSync(testsDir);
for (const file of files) {
if (file.endsWith('.snapshot')) {
fs.copyFileSync(
path.join(testsDir, file),
path.join(buildTestsDir, file),
);
}
}

// Also handle subdirectories if needed, but for now we only have snapshots in the root of tests/
// Wait, let's check tools/
const toolsTestsDir = path.join(testsDir, 'tools');
const buildToolsTestsDir = path.join(buildTestsDir, 'tools');
if (fs.existsSync(toolsTestsDir)) {
if (!fs.existsSync(buildToolsTestsDir)) {
fs.mkdirSync(buildToolsTestsDir, {recursive: true});
}
const toolsFiles = fs.readdirSync(toolsTestsDir);
for (const file of toolsFiles) {
if (file.endsWith('.snapshot')) {
fs.copyFileSync(
path.join(toolsTestsDir, file),
path.join(buildToolsTestsDir, file),
);
}
}
}
}

function copyDevToolsDescriptionFiles() {
Expand Down
1 change: 1 addition & 0 deletions skills/chrome-devtools-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ chrome-devtools take_screenshot # Take a screenshot of the page viewport
chrome-devtools take_screenshot --fullPage true --format "jpeg" --quality 80 # Take a full page screenshot as JPEG with quality
chrome-devtools take_screenshot --uid "id" --filePath "s.png" # Take a screenshot of an element
chrome-devtools take_snapshot # Take a text snapshot of the page from the a11y tree
chrome-devtools take_snapshot --diff true # Return only the changes since the last snapshot
chrome-devtools take_snapshot --verbose true --filePath "s.txt" # Take a verbose snapshot and save to file
```

Expand Down
1 change: 1 addition & 0 deletions skills/chrome-devtools/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ description: Uses Chrome DevTools via MCP for efficient debugging, troubleshooti

### Efficient data retrieval

- Use `diff: true` in `take_snapshot` to receive only the changes since the last snapshot (reset on navigation).
- Use `filePath` parameter for large outputs (screenshots, snapshots, traces)
- Use pagination (`pageIdx`, `pageSize`) and filtering (`types`) to minimize data
- Set `includeSnapshot: false` on input actions unless you need updated page state
Expand Down
52 changes: 48 additions & 4 deletions src/McpContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,9 @@ export class McpContext implements Context {
page: McpPage,
verbose = false,
devtoolsData: DevToolsData | undefined = undefined,
options: {
diff?: boolean;
} = {},
): Promise<void> {
const rootNode = await page.pptrPage.accessibility.snapshot({
includeIframes: true,
Expand All @@ -745,10 +748,19 @@ export class McpContext implements Context {
let idCounter = 0;
const idToNode = new Map<string, TextSnapshotNode>();
const seenUniqueIds = new Set<string>();
const assignIds = (node: SerializedAXNode): TextSnapshotNode => {
const assignIds = (
node: SerializedAXNode,
parentId = 'root',
index = 0,
): TextSnapshotNode => {
let id = '';
// @ts-expect-error untyped loaderId & backendNodeId.
const uniqueBackendId = `${node.loaderId}_${node.backendNodeId}`;
const nodeAny = node as unknown as Record<string, unknown>;
// StaticText nodes often have unstable backendNodeIds in some contexts,
// or we might want to group them by their parent.
const uniqueBackendId = nodeAny.backendNodeId
? `${nodeAny.loaderId}_${nodeAny.backendNodeId}`
: `${nodeAny.loaderId}_${nodeAny.role}_${parentId}_${index}`;

if (uniqueBackendNodeIdToMcpId.has(uniqueBackendId)) {
// Re-use MCP exposed ID if the uniqueId is the same.
id = uniqueBackendNodeIdToMcpId.get(uniqueBackendId)!;
Expand All @@ -763,7 +775,7 @@ export class McpContext implements Context {
...node,
id,
children: node.children
? node.children.map(child => assignIds(child))
? node.children.map((child, i) => assignIds(child, id, i))
: [],
};

Expand All @@ -787,8 +799,40 @@ export class McpContext implements Context {
idToNode,
hasSelectedElement: false,
verbose,
diffReset: !!(options.diff && !page.lastSnapshot),
};

if (options.diff && page.lastSnapshot) {
const lastIdToNode = page.lastSnapshot.idToNode;
const added: string[] = [];
const changed: string[] = [];
const removed: string[] = [];

for (const [id, node] of idToNode) {
const lastNode = lastIdToNode.get(id);
if (!lastNode) {
added.push(id);
} else if (
node.name !== lastNode.name ||
node.value !== lastNode.value ||
node.description !== lastNode.description ||
node.role !== lastNode.role
) {
changed.push(id);
}
}

for (const id of lastIdToNode.keys()) {
if (!idToNode.has(id)) {
removed.push(id);
}
}

snapshot.diff = {added, changed, removed};
}

page.textSnapshot = snapshot;
page.lastSnapshot = snapshot;
const data = devtoolsData ?? (await this.getDevToolsData(page));
if (data?.cdpBackendNodeId) {
snapshot.hasSelectedElement = true;
Expand Down
7 changes: 7 additions & 0 deletions src/McpPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class McpPage implements ContextPage {

// Snapshot
textSnapshot: TextSnapshot | null = null;
lastSnapshot: TextSnapshot | null = null;
uniqueBackendNodeIdToMcpId = new Map<string, string>();

// Emulation
Expand All @@ -45,14 +46,19 @@ export class McpPage implements ContextPage {
// Dialog
#dialog?: Dialog;
#dialogHandler: (dialog: Dialog) => void;
#navigationHandler: () => void;

constructor(page: Page, id: number) {
this.pptrPage = page;
this.id = id;
this.#dialogHandler = (dialog: Dialog): void => {
this.#dialog = dialog;
};
this.#navigationHandler = (): void => {
this.lastSnapshot = null;
};
page.on('dialog', this.#dialogHandler);
page.on('framenavigated', this.#navigationHandler);
}

get dialog(): Dialog | undefined {
Expand Down Expand Up @@ -93,6 +99,7 @@ export class McpPage implements ContextPage {

dispose(): void {
this.pptrPage.off('dialog', this.#dialogHandler);
this.pptrPage.off('framenavigated', this.#navigationHandler);
}

async getElementByUid(uid: string): Promise<ElementHandle<Element>> {
Expand Down
3 changes: 3 additions & 0 deletions src/McpResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ export class McpResponse implements Response {
this.#page,
this.#snapshotParams.verbose,
this.#devToolsData,
{
diff: this.#snapshotParams.diff,
},
);
const textSnapshot = this.#page.textSnapshot;
if (textSnapshot) {
Expand Down
Loading
Loading