Repository: ChromeDevTools/chrome-devtools-mcp Branch: main Commit: 41ff9bfbc12b Files: 214 Total size: 4.5 MB Directory structure: gitextract_cbkm4aqd/ ├── .agent/ │ └── rules/ │ └── coding.md ├── .claude-plugin/ │ ├── marketplace.json │ └── plugin.json ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── 01-bug.yml │ │ ├── 02-feature.yml │ │ ├── 03-task.yml │ │ └── config.yml │ ├── dependabot.yml │ └── workflows/ │ ├── conventional-commit.yml │ ├── pre-release.yml │ ├── presubmit.yml │ ├── publish-to-npm-on-tag.yml │ ├── release-please.yml │ └── run-tests.yml ├── .gitignore ├── .mcp.json ├── .nvmrc ├── .prettierignore ├── .prettierrc.cjs ├── .release-please-manifest.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── docs/ │ ├── cli.md │ ├── debugging-android.md │ ├── design-principles.md │ ├── slim-tool-reference.md │ ├── tool-reference.md │ └── troubleshooting.md ├── eslint.config.mjs ├── gemini-extension.json ├── package.json ├── puppeteer.config.cjs ├── release-please-config.json ├── rollup.config.mjs ├── scripts/ │ ├── append-lighthouse-notices.ts │ ├── count_tokens.ts │ ├── eslint_rules/ │ │ ├── check-license-rule.js │ │ └── local-plugin.js │ ├── eval_gemini.ts │ ├── eval_scenarios/ │ │ ├── console_test.ts │ │ ├── emulation_test.ts │ │ ├── emulation_userAgent_test.ts │ │ ├── emulation_viewport_test.ts │ │ ├── fix_webpage_issues_test.ts │ │ ├── frontend_snapshot_test.ts │ │ ├── input_parallel_test.ts │ │ ├── input_test.ts │ │ ├── isolated_context_test.ts │ │ ├── lighthouse_a11y_test.ts │ │ ├── lighthouse_best_practices_test.ts │ │ ├── navigation_test.ts │ │ ├── network_test.ts │ │ ├── page_focus_keyboard_test.ts │ │ ├── page_id_routing_test.ts │ │ ├── performance_test.ts │ │ ├── select_page_test.ts │ │ └── snapshot_test.ts │ ├── generate-cli.ts │ ├── generate-docs.ts │ ├── post-build.ts │ ├── prepare.ts │ ├── test.mjs │ ├── tsconfig.json │ ├── update-lighthouse.ts │ ├── verify-npm-package.mjs │ └── verify-server-json-version.ts ├── server.json ├── skills/ │ ├── a11y-debugging/ │ │ ├── SKILL.md │ │ └── references/ │ │ └── a11y-snippets.md │ ├── chrome-devtools/ │ │ └── SKILL.md │ ├── chrome-devtools-cli/ │ │ ├── SKILL.md │ │ └── references/ │ │ └── installation.md │ ├── debug-optimize-lcp/ │ │ ├── SKILL.md │ │ └── references/ │ │ ├── elements-and-size.md │ │ ├── lcp-breakdown.md │ │ ├── lcp-snippets.md │ │ └── optimization-strategies.md │ └── troubleshooting/ │ └── SKILL.md ├── src/ │ ├── DevToolsConnectionAdapter.ts │ ├── DevtoolsUtils.ts │ ├── McpContext.ts │ ├── McpPage.ts │ ├── McpResponse.ts │ ├── Mutex.ts │ ├── PageCollector.ts │ ├── SlimMcpResponse.ts │ ├── WaitForHelper.ts │ ├── bin/ │ │ ├── chrome-devtools-cli-options.ts │ │ ├── chrome-devtools-mcp-cli-options.ts │ │ ├── chrome-devtools-mcp-main.ts │ │ ├── chrome-devtools-mcp.ts │ │ ├── chrome-devtools.ts │ │ └── cliDefinitions.ts │ ├── browser.ts │ ├── daemon/ │ │ ├── client.ts │ │ ├── daemon.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── devtools.d.ts │ ├── formatters/ │ │ ├── ConsoleFormatter.ts │ │ ├── IssueFormatter.ts │ │ ├── NetworkFormatter.ts │ │ └── SnapshotFormatter.ts │ ├── index.ts │ ├── issue-descriptions.ts │ ├── logger.ts │ ├── polyfill.ts │ ├── telemetry/ │ │ ├── ClearcutLogger.ts │ │ ├── WatchdogClient.ts │ │ ├── flagUtils.ts │ │ ├── metricUtils.ts │ │ ├── persistence.ts │ │ ├── types.ts │ │ └── watchdog/ │ │ ├── ClearcutSender.ts │ │ └── main.ts │ ├── third_party/ │ │ ├── LIGHTHOUSE_MCP_BUNDLE_THIRD_PARTY_NOTICES │ │ ├── devtools-formatter-worker.ts │ │ ├── index.ts │ │ └── lighthouse-devtools-mcp-bundle.js │ ├── tools/ │ │ ├── ToolDefinition.ts │ │ ├── categories.ts │ │ ├── console.ts │ │ ├── emulation.ts │ │ ├── extensions.ts │ │ ├── input.ts │ │ ├── lighthouse.ts │ │ ├── memory.ts │ │ ├── network.ts │ │ ├── pages.ts │ │ ├── performance.ts │ │ ├── screencast.ts │ │ ├── screenshot.ts │ │ ├── script.ts │ │ ├── slim/ │ │ │ └── tools.ts │ │ ├── snapshot.ts │ │ └── tools.ts │ ├── trace-processing/ │ │ └── parse.ts │ ├── types.ts │ ├── utils/ │ │ ├── ExtensionRegistry.ts │ │ ├── files.ts │ │ ├── keyboard.ts │ │ ├── pagination.ts │ │ ├── string.ts │ │ └── types.ts │ └── version.ts ├── tests/ │ ├── DevtoolsUtils.test.ts │ ├── McpContext.test.js.snapshot │ ├── McpContext.test.ts │ ├── McpResponse.test.js.snapshot │ ├── McpResponse.test.ts │ ├── PageCollector.test.ts │ ├── browser.test.ts │ ├── cli.test.ts │ ├── daemon/ │ │ ├── client.test.ts │ │ └── utils.test.ts │ ├── e2e/ │ │ ├── chrome-devtools.test.ts │ │ └── telemetry.test.ts │ ├── formatters/ │ │ ├── ConsoleFormatter.test.js.snapshot │ │ ├── ConsoleFormatter.test.ts │ │ ├── IssueFormatter.test.js.snapshot │ │ ├── IssueFormatter.test.ts │ │ ├── NetworkFormatter.test.ts │ │ ├── snapshotFormatter.test.js.snapshot │ │ └── snapshotFormatter.test.ts │ ├── index.test.js.snapshot │ ├── index.test.ts │ ├── server.ts │ ├── setup.ts │ ├── snapshot.ts │ ├── telemetry/ │ │ ├── ClearcutLogger.test.ts │ │ ├── WatchdogClient.test.ts │ │ ├── flagUtils.test.ts │ │ ├── metricUtils.test.ts │ │ ├── persistence.test.ts │ │ └── watchdog/ │ │ └── ClearcutSender.test.ts │ ├── third_party_notices.test.js.snapshot │ ├── third_party_notices.test.ts │ ├── tools/ │ │ ├── console.test.js.snapshot │ │ ├── console.test.ts │ │ ├── emulation.test.ts │ │ ├── extensions.test.ts │ │ ├── fixtures/ │ │ │ ├── extension/ │ │ │ │ ├── manifest.json │ │ │ │ └── popup.html │ │ │ ├── extension-side-panel/ │ │ │ │ ├── manifest.json │ │ │ │ ├── sidepanel.html │ │ │ │ └── sw.js │ │ │ └── extension-sw/ │ │ │ ├── manifest.json │ │ │ ├── popup.html │ │ │ └── sw.js │ │ ├── input.test.ts │ │ ├── lighthouse.test.js.snapshot │ │ ├── lighthouse.test.ts │ │ ├── memory.test.ts │ │ ├── network.test.js.snapshot │ │ ├── network.test.ts │ │ ├── pages.test.js.snapshot │ │ ├── pages.test.ts │ │ ├── performance.test.js.snapshot │ │ ├── performance.test.ts │ │ ├── screencast.test.ts │ │ ├── screenshot.test.ts │ │ ├── script.test.ts │ │ ├── slim/ │ │ │ ├── tools.test.js.snapshot │ │ │ └── tools.test.ts │ │ └── snapshot.test.ts │ ├── trace-processing/ │ │ ├── fixtures/ │ │ │ └── load.ts │ │ ├── parse.test.js.snapshot │ │ └── parse.test.ts │ └── utils.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .agent/rules/coding.md ================================================ --- trigger: always_on --- # Instructions - Use only scripts from `package.json` to run commands. - Use `npm run build` to run tsc and test build. - Use `npm run test` to build and run tests, run all tests to verify correctness. - Use `npm run test path-to-test.ts` to build and run a single test file, for example, `npm run test tests/McpContext.test.ts`. - Use `npm run format` to fix formatting and get linting errors. ## Rules for TypeScript - Do not use `any` type. - Do not use `as` keyword for type casting. - Do not use `!` operator for type assertion. - Do not use `// @ts-ignore` comments. - Do not use `// @ts-nocheck` comments. - Do not use `// @ts-expect-error` comments. - Prefer `for..of` instead of `forEach`. ================================================ FILE: .claude-plugin/marketplace.json ================================================ { "name": "chrome-devtools-plugins", "version": "1.0.0", "description": "Bundled plugins for actuating and debugging the Chrome browser.", "owner": { "name": "Chrome DevTools Team", "email": "devtools-dev@chromium.org" }, "plugins": [ { "name": "chrome-devtools-mcp", "source": "./", "description": "Reliable automation, in-depth debugging, and performance analysis in Chrome using Chrome DevTools and Puppeteer" } ] } ================================================ FILE: .claude-plugin/plugin.json ================================================ { "name": "chrome-devtools-mcp", "version": "latest", "description": "Reliable automation, in-depth debugging, and performance analysis in Chrome using Chrome DevTools and Puppeteer", "mcpServers": { "chrome-devtools": { "command": "npx", "args": ["chrome-devtools-mcp@latest"] } } } ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf ================================================ FILE: .github/ISSUE_TEMPLATE/01-bug.yml ================================================ name: Bug report description: File a bug report for chrome-devtools-mcp title: '' type: 'Bug' body: - id: description type: textarea attributes: label: Description of the bug description: > A clear and concise description of what the bug is. placeholder: validations: required: true - id: reproduce type: textarea attributes: label: Reproduction description: > Steps to reproduce the behavior: placeholder: | 1. Use tool '...' 2. Then use tool '...' - id: expectation type: textarea attributes: label: Expectation description: A clear and concise description of what you expected to happen. - id: mcp-configuration type: textarea attributes: label: MCP configuration - id: chrome-devtools-mcp-version type: input attributes: label: Chrome DevTools MCP version validations: required: true - id: chrome-version type: input attributes: label: Chrome version - id: coding-agent-version type: input attributes: label: Coding agent version - id: model-version type: input attributes: label: Model version - id: chat-log type: input attributes: label: Chat log - id: node-version type: input attributes: label: Node version description: > Please verify you have the minimal supported version listed in the README.md - id: operating-system type: dropdown attributes: label: Operating system description: What supported operating system are you running? options: - Windows - Windows Subsystem for Linux (WSL) - macOS - Linux - type: checkboxes id: provide-pr attributes: label: Extra checklist options: - label: I want to provide a PR to fix this bug ================================================ FILE: .github/ISSUE_TEMPLATE/02-feature.yml ================================================ name: Feature description: Suggest an idea for for chrome-devtools-mcp title: '' type: 'Feature' labels: - feature body: - id: description type: textarea attributes: label: Is your feature request related to a problem? Please describe. description: > A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] placeholder: validations: required: true - id: solution type: textarea attributes: label: Describe the solution you'd like description: > A clear and concise description of what you want to happen. placeholder: validations: required: true - id: alternatives type: textarea attributes: label: Describe alternatives you've considered description: > A clear and concise description of any alternative solutions or features you've considered. placeholder: validations: required: true - id: additional-context type: textarea attributes: label: Additional context description: > Add any other context or screenshots about the feature request here. placeholder: ================================================ FILE: .github/ISSUE_TEMPLATE/03-task.yml ================================================ name: Task description: Work tracking for mainainers only! title: '[Task]:' type: 'Task' body: - type: markdown attributes: value: | ### This issue type should be used only by mainainers! Task are to track small non user facing issue or improvements. The issue will be closed if it does not follow those rules. - type: textarea attributes: label: 'Task to do:' id: task validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: npm directory: / schedule: interval: weekly day: 'sunday' time: '02:00' timezone: Europe/Berlin groups: dependencies: dependency-type: production patterns: - '*' dev-dependencies: dependency-type: development exclude-patterns: - 'puppeteer*' - 'chrome-devtools-frontend' - '@modelcontextprotocol/sdk' - 'yargs' - 'debug' - 'core-js' patterns: - '*' # breaks often so better to roll separetely. bundled-devtools: patterns: - 'chrome-devtools-frontend' bundled: patterns: - 'puppeteer*' - '@modelcontextprotocol/sdk' - 'yargs' - 'debug' - 'core-js' - package-ecosystem: github-actions directory: / schedule: interval: weekly day: 'sunday' time: '04:00' timezone: Europe/Berlin groups: all: patterns: - '*' ================================================ FILE: .github/workflows/conventional-commit.yml ================================================ name: 'Conventional Commit' on: merge_group: pull_request_target: types: # Defaults # https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_target - opened - reopened - synchronize # Tracks editing PR title or description, or base branch changes # https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=edited#pull_request - edited jobs: main: name: '[Required] Validate PR title' runs-on: ubuntu-latest permissions: pull-requests: read steps: - if: github.event_name != 'merge_group' uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/pre-release.yml ================================================ name: Pre-release permissions: read-all on: workflow_dispatch: push: branches: - release-please-* jobs: pre-release: name: 'Verify artifacts before release' runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 2 - name: Set up Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: npm node-version-file: '.nvmrc' registry-url: 'https://registry.npmjs.org' # Ensure npm 11.5.1 or later is installed - name: Update npm run: npm install -g npm@latest - name: Install dependencies run: npm ci - name: Build and bundle run: npm run bundle env: NODE_ENV: 'production' - name: Verify server.json run: npm run verify-server-json-version - name: Verify npm package run: npm run verify-npm-package ================================================ FILE: .github/workflows/presubmit.yml ================================================ name: Check code before submitting permissions: read-all on: merge_group: push: branches: - main pull_request: jobs: check-format: name: '[Required] Check correct format' runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 2 - name: Set up Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: npm node-version-file: '.nvmrc' - name: Install dependencies run: npm ci - name: Run format check run: npm run check-format check-docs: name: '[Required] Check docs updated' runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 2 - name: Set up Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: npm node-version-file: '.nvmrc' - name: Install dependencies run: npm ci - name: Generate run: npm run gen - name: Check if autogenerated code and docs are out of date run: | diff_file=$(mktemp doc_diff_XXXXXX) git diff --color > $diff_file if [[ -s $diff_file ]]; then echo "Please update the documentation by running 'npm run gen'. The following was the diff" cat $diff_file rm $diff_file exit 1 fi rm $diff_file ================================================ FILE: .github/workflows/publish-to-npm-on-tag.yml ================================================ name: publish-on-tag on: push: tags: - 'chrome-devtools-mcp-v*' workflow_dispatch: inputs: npm-publish: description: 'Try to publish to NPM' default: false type: boolean mcp-publish: description: 'Try to publish to MCP registry' default: true type: boolean permissions: id-token: write # Required for OIDC contents: read jobs: publish-to-npm: runs-on: ubuntu-latest if: ${{ (github.event_name != 'workflow_dispatch') || (inputs.npm-publish && always()) }} steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 2 - name: Set up Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: npm node-version-file: '.nvmrc' registry-url: 'https://registry.npmjs.org' # Ensure npm 11.5.1 or later is installed - name: Update npm run: npm install -g npm@latest - name: Install dependencies run: npm ci - name: Build and bundle run: npm run bundle env: NODE_ENV: 'production' - name: Publish run: | npm publish --provenance --access public publish-to-mcp-registry: runs-on: ubuntu-latest needs: publish-to-npm if: ${{ (github.event_name != 'workflow_dispatch' && needs.publish-to-npm.result == 'success') || (inputs.mcp-publish && always()) }} steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 2 - name: Set up Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: npm node-version-file: '.nvmrc' registry-url: 'https://registry.npmjs.org' # Ensure npm 11.5.1 or later is installed - name: Update npm run: npm install -g npm@latest - name: Install dependencies run: npm ci - name: Build and bundle run: npm run bundle env: NODE_ENV: 'production' - name: Install MCP Publisher run: | export OS=$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_${OS}.tar.gz" | tar xz mcp-publisher - name: Login to MCP Registry run: ./mcp-publisher login github-oidc - name: Publish to MCP Registry run: ./mcp-publisher publish ================================================ FILE: .github/workflows/release-please.yml ================================================ on: push: branches: - main permissions: read-all name: release-please jobs: release-please: runs-on: ubuntu-latest steps: - uses: googleapis/release-please-action@v4 with: token: ${{ secrets.BROWSER_AUTOMATION_BOT_TOKEN }} target-branch: main config-file: release-please-config.json manifest-file: .release-please-manifest.json ================================================ FILE: .github/workflows/run-tests.yml ================================================ name: Compile and run tests permissions: read-all on: merge_group: push: branches: - main pull_request: jobs: run-tests: name: Tests on ${{ matrix.os }} with node ${{ matrix.node }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: - ubuntu-latest - windows-latest - macos-latest node: - 20 - 22 - 23 - 24 steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 2 - name: Set up Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: npm node-version: 22 # build works only with 22+. - name: Install dependencies shell: bash run: npm ci - name: Build run: npm run bundle env: NODE_OPTIONS: '--max_old_space_size=4096' - name: Set up Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: npm node-version: ${{ matrix.node }} - name: Disable AppArmor if: ${{ matrix.os == 'ubuntu-latest' }} shell: bash run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns - name: Run tests shell: bash # Retry tests if they fail in the merge queue. run: npm run test:no-build -- ${{ github.event_name == 'merge_group' && '--retry' || '' }} # Gating job for branch protection. test-success: name: '[Required] Tests passed' runs-on: ubuntu-latest needs: run-tests if: ${{ !cancelled() }} steps: - if: ${{ needs.run-tests.result != 'success' }} run: 'exit 1' - run: 'exit 0' ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* .pnpm-debug.log* trace.json trace.json.gz # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Snowpack dependency directory (https://snowpack.dev/) web_modules/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional stylelint cache .stylelintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variable files .env .env.development.local .env.test.local .env.production.local .env.local # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache # Next.js build output .next out # Nuxt.js build / generate output .nuxt dist # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and not Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # vuepress v2.x temp and cache directory .temp .cache # vitepress build output **/.vitepress/dist # vitepress cache directory **/.vitepress/cache # Docusaurus cache and generated files .docusaurus # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port # Stores VSCode versions used for testing VSCode extensions .vscode-test # Stores VSCode specific settings .vscode !.vscode/*.template.json !.vscode/extensions.json # yarn v2 .yarn/cache .yarn/unplugged .yarn/build-state.yml .yarn/install-state.gz .pnp.* # Build output directory build/ log.txt .DS_Store ================================================ FILE: .mcp.json ================================================ { "chrome-devtools": { "command": "npx", "args": ["chrome-devtools-mcp@latest"] } } ================================================ FILE: .nvmrc ================================================ v22 ================================================ FILE: .prettierignore ================================================ # Prettier-only ignores. CHANGELOG.md src/third_party/lighthouse-devtools-mcp-bundle.js ================================================ FILE: .prettierrc.cjs ================================================ /** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * @type {import('prettier').Config} */ module.exports = { bracketSpacing: false, singleQuote: true, trailingComma: 'all', arrowParens: 'avoid', singleAttributePerLine: true, htmlWhitespaceSensitivity: 'strict', endOfLine: 'lf', }; ================================================ FILE: .release-please-manifest.json ================================================ { ".": "0.20.2" } ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## [0.20.2](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.20.1...chrome-devtools-mcp-v0.20.2) (2026-03-18) ### 📄 Documentation * add troubleshooting for Claude Code plugin HTTPS clone failures ([#1195](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1195)) ([d082ca4](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d082ca4ecd35a023d09f9c1ff949d5fb0c3fb069)) ## [0.20.1](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.20.0...chrome-devtools-mcp-v0.20.1) (2026-03-16) ### 🛠️ Fixes * update VS Code manual installation powershell command ([#1151](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1151)) ([6c64a5b](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/6c64a5b543714796b25a12dc6f2be7a1e683e8bd)) ### ⚡ Performance * use CDP to find open DevTools pages. ([#1150](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1150)) ([94de19c](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/94de19cdcdae9e31d0962b273ce352dc248eb5a8)) ## [0.20.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.19.0...chrome-devtools-mcp-v0.20.0) (2026-03-11) ### 🎉 Features * experimental `chrome-devtools` CLI ([#1100](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1100)) ([1ac574e](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/1ac574e7154948e86e414e5149fb975a190d5bb0)) ### 📄 Documentation * fix typo ([#1155](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1155)) ([b59cabc](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/b59cabcc1d59802ffd7d9667040188e46192357d)) * fix typos and improve phrasing ([#1130](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1130)) ([70d4f36](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/70d4f365dc619a5743e697c30800f7065bc6227d)) * revise contribution process and add release process ([#1134](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1134)) ([d7d26a1](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d7d26a103b840e2feb7cb9af6a242edda94f1ddf)) * **troubleshooting:** add symptom for missing tools due to read-only mode ([#1148](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1148)) ([57e7d51](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/57e7d51e8ca1e2ee325a9e7a9c64c033acbe6d6a)) * Update troubleshooting for MCP server connection errors ([#1017](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1017)) ([00f9c31](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/00f9c3108ab9caefca57998439052c728298920b)) ### 🏗️ Refactor * move main files ([#1120](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1120)) ([c2d8009](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/c2d8009ff75f76bce1ec4cf79c2467b50d81725e)) ## [0.19.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.18.1...chrome-devtools-mcp-v0.19.0) (2026-03-05) ### 🎉 Features * add pageId routing for parallel multi-agent workflows ([#1022](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1022)) ([caf601a](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/caf601a32832bb87cfac801a6bbeacb87508412f)), closes [#1019](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1019) * Add skill which helps with onboarding of the mcp server ([#1083](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1083)) ([7273f16](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/7273f16ec08f6d5b46a2693b0ad4d559086ded89)) * integrate Lighthouse audits ([#831](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/831)) ([dfdac26](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/dfdac2648e560d756a8711ad3bb1fa470be8e7c9)) ### 🛠️ Fixes * improve error messages around --auto-connect ([#1075](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1075)) ([bcb852d](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/bcb852dd2e440b0005f4a9ad270a1a7998767907)) * improve tool descriptions ([#965](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/965)) ([bdbbc84](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/bdbbc84c125bdd48f4be48aa476bec0323de611c)) * repair broken markdown and extract snippets in a11y-debugging skill ([#1096](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1096)) ([adac7c5](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/adac7c537ee304f324c5e7284fb363396d1773f5)) * simplify emulation and script tools ([#1073](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1073)) ([e51ba47](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/e51ba4720338951e621585b77efc6a0e07678d99)) * simplify focus state management ([#1063](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1063)) ([f763da2](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/f763da24a10e27605c0a5069853ce7c92974eec2)) * tweak lighthouse description ([#1112](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1112)) ([5538180](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/55381804ae7ffa8a1e5933b621a9b8390b3000ff)) ### 📄 Documentation * Adapt a11y skill to utilize Lighthouse ([#1054](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1054)) ([21634e6](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/21634e660c346e469ae62116b1824538f51567dd)) * add feature release checklist to CONTRIBUTING.md ([#1118](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1118)) ([0378457](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/03784577ffb6e238bcb2d637bff9ad759723ea7b)) * fix typo in README regarding slim mode ([#1093](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1093)) ([92f2c7b](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/92f2c7b48b56a6b1d6ac7c9e2f2e92beb26bcf62)) ### 🏗️ Refactor * clean up more of the context getters ([#1062](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1062)) ([9628dab](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/9628dabcb4d39f0b94d152a0fc419e049246a29d)) * consistently use McpPage in tools ([#1057](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1057)) ([302e5a0](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/302e5a04191ba0558e3c79f1486d01d5eb0f6896)) * improve type safety for page scoped tools ([#1051](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1051)) ([5f694c6](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/5f694c60ffd21f8b022554c92b2ad4cbdb457375)) * make cdp resolvers use McpPage ([#1060](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1060)) ([d6c06c5](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d6c06c56a7b8e4968318adc9fc7c820ace9f5bd9)) * move dialog handling to McpPage ([#1059](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1059)) ([40c241b](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/40c241bbfc80d6282953ab325b30a597d3d85ade)) * move server to a separate file ([#1043](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1043)) ([a8bf3e5](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a8bf3e585682c3126dfd378e9f98b5dc7ab6045d)) * remove page passing via context ([#1061](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1061)) ([4cb5a17](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/4cb5a17b57f57d8a367cd423c960ba122b9952e3)) * set defaults to performance trace tool ([#1090](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1090)) ([dfa9b79](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/dfa9b79a4ecc9e67f5b043f2dd97f6889d1fee0b)) * simplify the response texts ([#1095](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1095)) ([cb0079e](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/cb0079efbbd41874f6913772fe3f2a037e9f5f8f)) * type-cast as internal CdpPage interface ([#1064](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1064)) ([2d5e4fa](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/2d5e4fa3579650a384ff21c88c2e6b9cda031e1a)) ## [0.18.1](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.18.0...chrome-devtools-mcp-v0.18.1) (2026-02-25) ### 🛠️ Fixes * remove endsWith for filePath in memory tools ([#1041](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1041)) ([d0622d5](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d0622d52d46ac72a28bc22f93a337fb5007214c7)) ## [0.18.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.17.3...chrome-devtools-mcp-v0.18.0) (2026-02-24) ### 🎉 Features * `--slim` mode for maximum token savings ([#958](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/958)) ([c402b43](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/c402b43697d834994c4fc141305189082da14bee)) * add a new skill for accessibility debugging and auditing with Chrome DevTools MCP. ([#1002](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1002)) ([b0c6d04](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/b0c6d042e4d68763acf989edc8097ce07e85dc7a)) * add experimental screencast recording tools ([#941](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/941)) ([33446d4](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/33446d457e4386fadcfe4ddf6c7a43b2e9098c9a)) * add skill to debug and optimize LCP ([#993](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/993)) ([2cd9b95](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/2cd9b95346226aa52cce18f6ab889a2ae194806c)) * add storage-isolated browser contexts ([#991](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/991)) ([59f6477](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/59f6477a70eb07585e9a510089f1dfc840a012fd)) * add take_memory_snapshot tool ([#1023](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1023)) ([7ffdc5e](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/7ffdc5ee4d9df9f62f03354fa758fb4d022c3b08)) * support any-match text arrays in wait_for ([#1011](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1011)) ([496ab1b](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/496ab1b45f7a283a1432643777e0795a17f33667)) * support type_text ([#1026](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1026)) ([b5d01b5](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/b5d01b5fe65fa20f9b76555b86a749960a5d1738)) ### 🛠️ Fixes * detect X server display on Linux ([#1027](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1027)) ([1746ed9](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/1746ed9ee11c212f78dcbb00af99a0400595e778)) ### ♻️ Chores * cleanup string and structured console formatters ([#1005](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1005)) ([0d78685](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/0d78685a5b37dc68bb11a1088ff8816ecff3bb82)) * extract version in a seprate file ([#1032](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1032)) ([0106865](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/0106865aad6d51b6cb590bf98ccaf7078e8d7436)) * move emulation settings to context ([#1000](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1000)) ([bc3c40e](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/bc3c40e8f961433fb2ae858482d66f9a55fdde32)) * optimize slim tool descriptions and params ([#1028](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1028)) ([ca6635d](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/ca6635d5a5d5e8b7b9944fa8b4e1063e6269a5f2)) * simplify JavaScript code examples, update code block language, and refine descriptions in a11y debugging skill documentation. ([#1009](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1009)) ([5cedcaa](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/5cedcaad2c8a5e488064e21fb56cbd8643345440)) * types for JSON output of IssueFormatter ([#1007](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1007)) ([9ef4479](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/9ef4479bec39c5f2651d6ebb63e9ec0fecf8bf89)) ## [0.17.3](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.17.2...chrome-devtools-mcp-v0.17.3) (2026-02-19) ### 🛠️ Fixes * remove clean from prepare ([#997](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/997)) ([2016b98](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/2016b98217bf5aa8d65c6668b1e46c8a3400276f)) ## [0.17.2](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.17.1...chrome-devtools-mcp-v0.17.2) (2026-02-19) ### 🛠️ Fixes * check that combobox is actually a select element before filling out options ([#979](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/979)) ([d2bc489](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d2bc489e4351551ba62a104433839c4198ecae84)) * handle network request pagination correctly ([#980](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/980)) ([0d9f422](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/0d9f422201538aa847a50417f1ed370e3a6c95b2)) ### 📄 Documentation * Add a note about previously installed server installations ([#982](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/982)) ([c0009f7](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/c0009f7ab2f15bedd1c4ceb609db77bcb3c96f2d)) * update codex doc URL ([#987](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/987)) ([ebbbea7](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/ebbbea7f9d20e4dea902d06e9b86dfe1cc9b221f)) ### ♻️ Chores * **network:** de-duplicate String and JSON formatters ([#985](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/985)) ([1896dbb](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/1896dbb5a7cdc3fc0bcc5e665aee986a1180b014)) * remove text from the status code for Network requests ([#778](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/778)) ([327a388](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/327a3884d8443b8591c06ddb3f9081771ae973c3)) ## [0.17.1](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.17.0...chrome-devtools-mcp-v0.17.1) (2026-02-16) ### 📄 Documentation * Add 'Progressive Complexity' and 'Reference over Value' design principles. ([#939](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/939)) ([8d765c0](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/8d765c0aef7bbcd476c7e7fbe9ea63ee26cf4fa6)) * add Katalon Studio setup instructions to README ([#929](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/929)) ([6cfef24](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/6cfef24ec734ed62221c66bdf03b09ce000f5bfe)) * add MCP config for Claude plugin + docs ([#944](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/944)) ([a781da4](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a781da4434c3490901b28017bc7aa40493ef8dcc)) * estimate tokens using tiktoken ([#959](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/959)) ([fd0a919](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/fd0a9193b37be4c5cda21dc4904093c7b58d61be)) * improve Claude Code installation instructions ([#947](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/947)) ([3ec5b7e](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/3ec5b7e7a2d97c9f0165c5af3317c531a9dc058f)) * Update README with WSL configuration details ([#946](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/946)) ([107c46a](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/107c46a4dbd2ba7c7b9217a75ae2b1871d3c7f0d)) ### ♻️ Chores * rename files to have more consistent style ([#935](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/935)) ([9e1f9ac](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/9e1f9ac69667ddc3e2917e2c30e5ee940a03d853)) ## [0.17.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.16.0...chrome-devtools-mcp-v0.17.0) (2026-02-10) ### 🎉 Features * include Error.cause chain for uncaught errors and logged Errors ([#906](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/906)) ([05b01ec](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/05b01ecaba47cf1ce38564636663222c9cab46de)) * Integrate CrUX data into performance trace summaries ([#733](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/733)) ([b747f9d](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/b747f9d74a12d2119b6531476b2f88ab66be0ff8)) * show message and stack trace in details when console.log'ging Error objects ([#902](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/902)) ([ffa00da](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/ffa00dab1b65b2eac8db215e0289317b8ed9b725)) ### 🛠️ Fixes * console formatter hides frames from ignored scripts ([#927](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/927)) ([8e2380b](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/8e2380b434d9659ffa8a7043d2589261772fa04f)) * limit stack traces to 50 lines ([#923](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/923)) ([caea23a](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/caea23a7cf33c87cd4ce426eb2a10724aba3cc71)) ### 📄 Documentation * add macOS Web Bluetooth troubleshooting note ([#930](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/930)) ([3c9528b](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/3c9528b43d9bbff166fcfcfee321149ff44ddd21)), closes [#917](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/917) ## [0.16.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.15.1...chrome-devtools-mcp-v0.16.0) (2026-02-04) ### 🎉 Features * include source-mapped stack trace for uncaught errors ([#876](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/876)) ([ecef712](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/ecef712e70b47ae81eb3364d0aed801ec1c91a70)) ### 🛠️ Fixes * accidental extra typing in the fill tool ([#886](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/886)) ([3d6e59d](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/3d6e59dda42be3c6fd97446344a28cbbaa5809b3)) * update evaluateScript description formatting ([#880](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/880)) ([24db9dd](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/24db9dd78cd4f054d291322685b4f47601da3f5a)) * use 1-based line/column and fix wasm offsets in stack frames ([#884](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/884)) ([7e1ec81](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/7e1ec81fb63ec8b7c6d77dbdc88beef4240243ba)) ### 📄 Documentation * mention source-mapped stack traces in 'Key features' ([#883](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/883)) ([579d18a](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/579d18a3f4d1d8d05bf267a39de7f2f53e719b17)) * remove outdated --channel=beta note ([#882](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/882)) ([acdb5c9](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/acdb5c9bb3f249c5a9ce1d4a3e84c580af999141)) ## [0.15.1](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.15.0...chrome-devtools-mcp-v0.15.1) (2026-01-30) ### 🛠️ Fixes * disable usage statistics when CI or CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS env is set ([#862](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/862)) ([c0435a2](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/c0435a2d53eb51b7500fc5cce50344520ea164e7)) * respect custom timeouts in navigate tools ([#865](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/865)) ([a0aeb97](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a0aeb97693fd5ca641f45ebcd4ce3b4b08ce21b8)) ## [0.15.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.14.0...chrome-devtools-mcp-v0.15.0) (2026-01-28) ### 🎉 Features * Add ability to inject script to run on page load ([#568](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/568)) ([d845ad4](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d845ad48584a49aa57b11de308beeb17ed0b2e10)) * enable usage statistics by default with opt-out ([#855](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/855)) ([7e279f1](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/7e279f1b67c5cfd4ad033a4147c51fe20a7833f7)) * support testing light and dark mode ([#858](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/858)) ([5a23a8c](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/5a23a8c201d30d40395e283f4434d933826333fa)) ## [0.14.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.13.0...chrome-devtools-mcp-v0.14.0) (2026-01-27) ### 🎉 Features * add a skill for using chrome-devtools-mcp ([#830](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/830)) ([aa0a367](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/aa0a3679f59ab441908d31252afee1cd56102da8)) * add background parameter to new_page tool ([#837](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/837)) ([d756888](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d7568881ba4aa0e2c10dc6148fd0ef941fee10d5)) * allow skipping snapshot generation for input tools ([#821](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/821)) ([4b8e9f2](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/4b8e9f287572e0a95c30b5ca612acf08bf79595b)) * include stack trace in 'get_console_message' tool ([#740](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/740)) ([a3a0021](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a3a00210a30f78045244bc897ee736bdbdc36007)) * support device viewport and user agent emulation ([#798](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/798)) ([a816967](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a8169676f920f88965a2574f53affe15c1278b43)) * support filePath for network request and response bodies ([#795](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/795)) ([6d0e4ca](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/6d0e4cab28a8498c2783c1c0c6436c655de7b336)) ### 🛠️ Fixes * handle beforeunload dialogs in navigations ([#788](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/788)) ([9b21f8b](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/9b21f8b2e972f78f58c6f633851466356330c77d)) * improve error handling for console messages ([#844](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/844)) ([dc43ede](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/dc43ede1f20302bd2feb706e63bcf992b4a66a96)) * improve error reporting when retrieving the element ([#845](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/845)) ([f7dd003](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/f7dd00340a8ac5af7fbe4922f2a1d27d99d933cc)) * improve performance tool description ([#800](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/800)) ([aa9a176](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/aa9a1769568aca2a357f186b2e80b38b2ed76323)) * increase timeouts for long text input ([#787](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/787)) ([a83a338](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a83a33835148905b538b39be93f6115774f91696)) * make request and response handling more robust ([#846](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/846)) ([695817f](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/695817f6d6da5fcb94934fb1c2be8b006522f53b)) * re-use node ids across snapshots ([#814](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/814)) ([a6cd2cd](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a6cd2cd3f2bd823f0e044d7796fd8ff2c100cda3)) ### 📄 Documentation * add a mention of evals into contributing.md ([#773](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/773)) ([9a31ac7](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/9a31ac7abab5890d11fec627bbdcbb8051452453)) * document how to add extensions to gemini-cli ([#834](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/834)) ([0610d11](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/0610d11aa9add484951b76adef557eed5e2bd275)) * update auto-connect docs ([#779](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/779)) ([a106fba](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a106fbadbc1a487ce4c53a9eb783c98e524c0a9e)) * Update README.md to include a link to Android debugging ([#783](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/783)) ([6e52e66](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/6e52e66a7a7ebbf1f2e2080a857f72192036eb0c)) ## [0.13.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.12.1...chrome-devtools-mcp-v0.13.0) (2026-01-14) ### 🎉 Features * Allow opting out of default Chrome launch arguments ([#729](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/729)) ([9a51af2](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/9a51af219fc9216cd463bef9363716283f41f36a)) * support filePath in performance tools ([#686](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/686)) ([68ae2f8](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/68ae2f8253e2ba5c34436e25df114874c537f6df)) ### 🛠️ Fixes * support resize_page when browser window is maximized/fullscreenwindow state ([#748](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/748)) ([4d9ac22](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/4d9ac227ddff6fc4aec44e46673f6e44a8168db9)) * use relative path for plugin source in marketplace ([#724](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/724)) ([5c1ecf8](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/5c1ecf835ac8aad4947d0a8f82c899acd4115b64)) ### 📄 Documentation * add experimental chrome on android guide ([#691](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/691)) ([4a87702](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/4a87702ca6913ed62987f71e080f3d481d13b8d8)) * autoConnect - clarify how the mcp server selects a profile ([#693](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/693)) ([28b8ff8](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/28b8ff816461760c82e9b19b70f288bc7fa2fa38)) * claude code broken link ([#707](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/707)) ([1f532b8](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/1f532b8fafa0fa60aaf94c302bad663fab1c12ea)) * enhance cli docs + sort required vs opt params ([#674](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/674)) ([81cbd99](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/81cbd99f52d013d07bdcf21a0840f61a16bacd33)) * update auto connect docs to mention min Chrome version ([#681](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/681)) ([ab2340f](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/ab2340f40127dcdabde6887a411163ce9d130394)) * Update Claude Code instructions in README.md ([#711](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/711)) ([f81cd2d](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/f81cd2d8dfc35da8c718b227e0ee4c4d7c5daca8)) * update readme to include OpenCode example ([#560](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/560)) ([fbba3c9](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/fbba3c9461cec8113216fa4569e879c85312ea29)) ### ♻️ Chores * change pageIdx to page ids ([#741](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/741)) ([a23c6ba](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a23c6ba8c9e1da90c885e68946635a8cc536a11e)) ## [0.12.1](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.12.0...chrome-devtools-mcp-v0.12.1) (2025-12-12) ### 🛠️ Fixes * catch unexpected error in event handlers ([#672](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/672)) ([ca0f560](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/ca0f5607f18bf04134e85ea1f61d1a839a47827b)) * log unhandledRejection instead of crashing ([#673](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/673)) ([f59b4a2](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/f59b4a2ed8b09e1d64916552ee6db49b978fe9a7)) * make bringToFront optional in select_page ([#668](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/668)) ([ceae17b](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/ceae17be26b0a812f1b013dcebaed9beb510e7b3)) * Update installation badges in README.md for VS Code ([#660](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/660)) ([61ede1c](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/61ede1c0531ea8b028d9a5cbb28fcdc00cc521e0)) ### 📄 Documentation * Add debug instructions ([#670](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/670)) ([a8aae66](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a8aae6652e205b87ac2efa29217b7cbd18dcbbe6)) * explain new auto connection feature ([#664](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/664)) ([a537a8c](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a537a8c8cef4f2a3493e9f7de47345d565b6fc9f)) ## [0.12.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.11.0...chrome-devtools-mcp-v0.12.0) (2025-12-09) ### 🎉 Features * support --auto-connect to a Chrome instance ([#651](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/651)) ([6ab6d85](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/6ab6d85d50226cf12a62563430f552e783f428b2)) * support --user-data-dir with --auto-connect ([#654](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/654)) ([e3c59bc](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/e3c59bcd9c284f3be99cc15e22116b887f04cdab)) ### 🛠️ Fixes * map channel for resolveDefaultUserDataDir ([#658](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/658)) ([6f59b39](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/6f59b3975abda50536f8b890f3245662b22e3657)) ### 📄 Documentation * Add AX design principles ([#643](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/643)) ([90ed192](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/90ed192c558d36faf9f6300be1c1fd5abd464d8a)) * improve autoConnect docs ([#653](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/653)) ([09111cc](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/09111cc16464bed27cd623f3b345d3885db12521)) ## [0.11.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.10.2...chrome-devtools-mcp-v0.11.0) (2025-12-03) ### 🎉 Features * **emulation:** add geolocation emulation tool ([#634](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/634)) ([3991e4c](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/3991e4c2a9c28bf8180f9057ce804d978c39529d)) * integrate DevTools issues into the console tools ([#636](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/636)) ([d892145](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d8921453c77a1c0815059fb9bc72c0cd769a7bd4)) * support --user-data-dir ([#622](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/622)) ([fcaf553](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/fcaf55354c2afbdbae538e27eb4b6d02f2e87985)) ### 🛠️ Fixes * handle error messages that are not instanceof Error ([#618](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/618)) ([a67528a](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a67528a046746c7131d5265f6c94613d607aaf90)) * handle the case when all pages are filtered out ([#616](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/616)) ([bff5c65](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/bff5c6569003fdbc207448d89a8be6a9a8172ca0)) * ignore hash parts of URLs when finding DevTools ([#608](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/608)) ([52533d0](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/52533d0c695354b816807de253f0ec17099aa9d7)) * ignore quality for png ([#589](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/589)) ([2eaf268](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/2eaf2689c3360f88479f4cdab8ddde5899378e33)) * include a note about selected elements missing from the snapshot ([#593](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/593)) ([80e77fd](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/80e77fd9a35a3dc5c451cc5b070b8baa574c686c)) * prevent dropping license notices on some files when publishing ([#604](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/604)) ([94752ff](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/94752ffade847671ebfd15e4013a5b5cdf8377df)) * rename page content to latest page snapshot ([#579](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/579)) ([9cb99ad](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/9cb99ad3e65054f4ea12a39358719f6630a020d0)) * **wait_for:** respect the provided timeout ([#630](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/630)) ([6b0984a](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/6b0984aa7dca6f651afd1fed56246893810781c9)), closes [#624](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/624) ### 📄 Documentation * add Antigravity config ([#580](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/580)) ([6f9182f](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/6f9182f4b60f1f6ff8d321fec35545712828686e)) * add Qoder CLI to the MCP client configuration section in the README. ([#552](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/552)) ([1a16f15](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/1a16f15546e227a0708f89d3084c98d4916db53f)) * add VS Code install badges ([#532](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/532)) ([cc4d065](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/cc4d065dd6081a2a9fbcc3d8ebb1536e5426116e)) * clarify browser-url parameter in README ([#613](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/613)) ([05cf8cb](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/05cf8cb8a6c68506282075bc1522c81f0b84f07b)) * Fix Antigravity docs ([#605](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/605)) ([fae2608](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/fae260888748ece77b368a13ee913153caffcef7)) * update readme to explain agy's browser integration ([#612](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/612)) ([2d89865](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/2d89865ddbff6e77332c6157f687dcc2f0bef892)) ### ♻️ Chores * avoid throwing in resolveCdpElementId ([#606](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/606)) ([eb261fd](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/eb261fd48b6753db246d24b77e1f477dc7a9455e)) ## [0.10.2](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.10.1...chrome-devtools-mcp-v0.10.2) (2025-11-19) ### 📄 Documentation * add Factory CLI configuration to MCP clients ([#523](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/523)) ([016e2fd](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/016e2fd6ee57447103f7385285dd503b5576a860)) ### ♻️ Chores * clear issue aggregator on page navigation ([#565](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/565)) ([c3784d1](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/c3784d1990a926f651951e4eef05520c5c448964)) * disable issues in list_console_messages for now ([#575](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/575)) ([08e9a9f](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/08e9a9f42e6ff1a92c60b3e958b0817c7b785afc)) * simplify issue management ([#564](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/564)) ([3b016f1](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/3b016f1a814b1a69750813548b3f35e79bfb6fef)) ## [0.10.1](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.10.0...chrome-devtools-mcp-v0.10.1) (2025-11-07) ### 🛠️ Fixes * avoid no page selected errors ([#537](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/537)) ([4724bbb](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/4724bbba9327fc162cd1f0372e608f6ebefc59cc)) ## [0.10.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.9.0...chrome-devtools-mcp-v0.10.0) (2025-11-05) ### 🎉 Features * add a press_key tool ([#458](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/458)) ([b427392](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/b4273923928704e718e0a0f8b5cc86758416e994)) * add insightSetId to performance_analyze_insight ([#518](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/518)) ([36504d2](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/36504d29caf637b2d7bf231204c0478b54220c83)) * an option to ignore cache on reload ([#485](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/485)) ([8e56307](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/8e56307d623fe3651262287b30544ed70426b0b8)) * detect network requests inspected in DevTools UI ([#477](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/477)) ([796aed7](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/796aed72b7126ed4332888ffbc06d6cb678265ef)) * fetch DOM node selected in the DevTools Elements panel ([#486](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/486)) ([4a83574](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/4a83574961d8d6b974037db56fc8bdbbb91f79b6)) * support page reload ([#462](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/462)) ([d177087](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d17708798194486b2571092aa67838085da7231e)) * support saving snapshots to file ([#463](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/463)) ([b0ce08a](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/b0ce08ae2ce422813fef3f28c18f2cb6c976d9fc)) ### 🛠️ Fixes * Augment fix to prevent stray OGS frames in NTP from causing hangs. ([#521](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/521)) ([d90abd4](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d90abd4e9e534417622d7f4676e9c3dbeb39ea8d)) * improve get_network_request description ([#500](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/500)) ([2f448e8](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/2f448e84ea8d3a44687c74b3577edf882ef2c19f)) * work around NTP iframes causing hangs ([#504](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/504)) ([cca5ff4](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/cca5ff471c2d2c663e63ade1e2ea58f9a7f5a2cd)) ### 📄 Documentation * add Windsurf to the editor config README ([#493](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/493)) ([63a5d82](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/63a5d824c2d914c9007e2b837fa292f5ba74ceed)) * fix typos in README.md exlcude -> exclude ([#513](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/513)) ([8854a34](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/8854a3400c3a6b84c761bf8ed82769fc2dec7366)) * remove unnecessary replace ([#475](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/475)) ([40e1753](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/40e1753d2e874bb22005dbebdb551da304a80033)) ### ♻️ Chores * connect to DevTools targets by default ([#466](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/466)) ([a41e440](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a41e4407996b8090f8cccc85f6c4696006fc31ec)) * detect DevTools page for each page ([#467](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/467)) ([1560ff2](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/1560ff23cad28ab63c1cf9fb1b961db886bc4a3e)) * merge emulate tools into one ([#494](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/494)) ([c06f452](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/c06f4522ee8f762b59c60c2fd23a0deaaa544766)) ## [0.9.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.8.1...chrome-devtools-mcp-v0.9.0) (2025-10-22) ### 🎉 Features * add claude marketplace and plugin json ([#396](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/396)) ([0498611](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/0498611429f769c6ccae365674003d2bd538c292)) * add filters and pagination to the console messages tool ([#387](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/387)) ([15d942c](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/15d942c4f3335b35f1cba8e8634651688323663d)) * add WebSocket endpoint and custom headers support ([#404](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/404)) ([41d6a10](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/41d6a107baee0d14a1c14573f958d44198de23aa)) * allow configuring tool categories ([#454](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/454)) ([0fe2b8a](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/0fe2b8a2b4d64b9da5f7d1adccc5425fd7cbec34)) * expose previous navigations to the MCP ([#419](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/419)) ([165cf9c](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/165cf9c70b7f91dc116558547a870281f29da710)) * support previous navigation for Console messages ([#452](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/452)) ([6f24362](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/6f243620391f0c608f51d464257cf3222d653e9e)) * support stable id for network requests ([#375](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/375)) ([f4d7b49](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/f4d7b49bb112b4336bef0d90059485f41f71e4f1)) * support verbose snapshots ([#388](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/388)) ([d47aaa9](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d47aaa96ff990c49dd07a481ea1924f85881eafa)) * tool to get a verbose single console message ([#435](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/435)) ([9205593](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/92055933dc44e5d200dda2ee4ae0e365b24281bb)) * use stable id for network request querying ([#382](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/382)) ([579819b](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/579819b5e76f7a34c7c5c0877ac1e5e284beb328)) ### 🛠️ Fixes * allow evaluating in Frames ([#443](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/443)) ([053f1f8](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/053f1f830d051ec415f4b00e645f5a1aff8554a1)) * better wording for evaluate_script ([#392](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/392)) ([2313fda](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/2313fdacad72a1bc5c4d8f1cbdd80fd64ba91771)) * indicate when request and response bodies are not available ([#446](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/446)) ([7d47d6b](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/7d47d6b2f40bf08def29de3ca37b1a4a28ce6777)) * pageerror for non-error types ([#442](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/442)) ([b6b42ec](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/b6b42ecb998dd4f8fbf4a8e7a49f461333a41103)) * retrieve data correctly with fewer than 3 navigations ([#451](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/451)) ([4c65f59](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/4c65f59cf9f62662cf903fbbd19b67a8828d674a)) ### 📄 Documentation * add instructions for Qoder ([#386](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/386)) ([d8df784](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d8df784127afd590eb02e0060378465ae115a7a4)) * add VM-to-host remote debugging workaround ([#399](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/399)) ([9f9dab0](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/9f9dab0787f19c5730b65daf148c382fb2d9e365)) * Update Copilot CLI instructions ([#423](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/423)) ([c7733a8](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/c7733a818050e50830c9a8e3d62bb80892cf9121)) ### ♻️ Chores * bundle all dependencies together ([#450](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/450)) ([914b980](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/914b980113353fd41b301da397aa45975090487a)) * bundle modelcontextprotocol-sdk ([#409](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/409)) ([6c8432b](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/6c8432b6b69d5d56d0dee01968882492033f2dc1)) * bundle puppeteer-core ([#417](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/417)) ([b443033](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/b443033000e46a992ea7fa071af0f9ec304b9ea7)) * bundle zod together with modelcontextprotocol/sdk ([#414](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/414)) ([800e7e8](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/800e7e836433f3f1b2bfafa12ed35a991404d270)) * cleanup data fetching ([#441](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/441)) ([5c871c3](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/5c871c3bd98127996011f269faddd8d8e7163917)) * dispose listeners on page destroyed ([#318](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/318)) ([76d5e94](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/76d5e9416d833299561242ac45c0ce7813e61dbe)) * extract common paginate type ([#415](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/415)) ([29fd602](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/29fd60216ca1394c46a266c6f853f8d65418e861)) * store the last 3 navigations in PageCollector ([#411](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/411)) ([b873822](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/b8738221d8cf8322d5f968ee829f03dc83238a05)) * use different format for reqid ([#380](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/380)) ([78bf66a](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/78bf66a7b1eefc93768f39d6d38fd141104fe812)) ## [0.8.1](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.8.0...chrome-devtools-mcp-v0.8.1) (2025-10-13) ### Bug Fixes * add an option value to the snapshot ([#362](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/362)) ([207137e](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/207137edd6d8af2f49277d88a30d8afa51671631)) * improve navigate_page_history error messages ([#321](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/321)) ([0624029](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/0624029e0f8735345d202d29dde446b8869d9561)) * return the default dialog value correctly ([#366](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/366)) ([f08f808](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/f08f8080d0be1074a48e5c2ab0a6533f01f65928)) * update puppeteer to 24.24.1 ([#370](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/370)) ([477eef4](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/477eef481a2e6241121ee4aaaed34e8342a8b347)) ## [0.8.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.7.1...chrome-devtools-mcp-v0.8.0) (2025-10-10) ### Features * support passing args to Chrome ([#338](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/338)) ([e1b5363](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/e1b536365363e1e1a3aa7661dd84290c794510ad)) ## [0.7.1](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.7.0...chrome-devtools-mcp-v0.7.1) (2025-10-10) ### Bug Fixes * document that console and requests are since the last nav ([#335](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/335)) ([9ad7cbb](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/9ad7cbb2de3d285e46e5f3e7c098b0a7535c7e7a)) ## [0.7.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.6.1...chrome-devtools-mcp-v0.7.0) (2025-10-10) ### Features * Add offline network emulation support to emulate_network command ([#326](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/326)) ([139ce60](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/139ce607814bf25ba541a7264ce96a04b2fac871)) * add request and response body ([#267](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/267)) ([dd3c143](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/dd3c14336ee44d057d06231a5bfd5c5bcf661029)) ### Bug Fixes * ordering of information in performance trace summary ([#334](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/334)) ([2d4484a](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/2d4484a123968754b4840d112b9c1ca59fb29997)) * publishing to MCP registry ([#313](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/313)) ([1faec78](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/1faec78f84569a03f63585fb84df35992bcfe81a)) * use default ProtocolTimeout ([#315](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/315)) ([a525f19](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a525f199458afb266db4540bf0fa8007323f3301)) ## [0.6.1](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.6.0...chrome-devtools-mcp-v0.6.1) (2025-10-07) ### Bug Fixes * change default screen size in headless ([#299](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/299)) ([357db65](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/357db65d18f87b1299a0f6212b7ec982ef187171)) * **cli:** tolerate empty browser URLs ([#298](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/298)) ([098a904](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/098a904b363f3ad81595ed58c25d34dd7d82bcd8)) * guard performance_stop_trace when tracing inactive ([#295](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/295)) ([8200194](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/8200194c8037cc30b8ab815e5ee0d0b2b000bea6)) ## [0.6.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.5.1...chrome-devtools-mcp-v0.6.0) (2025-10-01) ### Features * **screenshot:** add WebP format support with quality parameter ([#220](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/220)) ([03e02a2](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/03e02a2d769fbfc0c98599444dfed5413d15ae6e)) * **screenshot:** adds ability to output screenshot to a specific pat… ([#172](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/172)) ([f030726](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/f03072698ddda8587ce23229d733405f88b7c89e)) * support --accept-insecure-certs CLI ([#231](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/231)) ([efb106d](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/efb106dc94af0057f88c89f810beb65114eeaa4b)) * support --proxy-server CLI ([#230](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/230)) ([dfacc75](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/dfacc75ee9f46137b5194e35fc604b89a00ff53f)) * support initial viewport in the CLI ([#229](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/229)) ([ef61a08](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/ef61a08707056c5078d268a83a2c95d10e224f31)) * support timeouts in wait_for and navigations ([#228](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/228)) ([36e64d5](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/36e64d5ae21e8bb244a18201a23a16932947e938)) ### Bug Fixes * **network:** show only selected request ([#236](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/236)) ([73f0aec](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/73f0aecd8a48b9d1ee354897fe14d785c80e863e)) * PageCollector subscribing multiple times ([#241](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/241)) ([0412878](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/0412878bf51ae46e48a171183bb38cfbbee1038a)) * snapshot does not capture Iframe content ([#217](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/217)) ([ce356f2](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/ce356f256545e805db74664797de5f42e7b92bed)), closes [#186](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/186) ## [0.5.1](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.5.0...chrome-devtools-mcp-v0.5.1) (2025-09-29) ### Bug Fixes * update package.json engines to reflect node20 support ([#210](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/210)) ([b31e647](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/b31e64713e0524f28cbf760fad27b25829ec419d)) ## [0.5.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.4.0...chrome-devtools-mcp-v0.5.0) (2025-09-29) ### Features * **screenshot:** add JPEG quality parameter support ([#184](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/184)) ([139cfd1](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/139cfd135cdb07573fe87d824631fcdb6153186e)) ### Bug Fixes * do not error if the dialog was already handled ([#208](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/208)) ([d9f77f8](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d9f77f85098ffe851308c5de05effb03ac21237b)) * reference to handle_dialog tool ([#209](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/209)) ([205eef5](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/205eef5cdff19ccb7ddbd113bb1450cb87e8f398)) * support node20 ([#52](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/52)) ([13613b4](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/13613b4a33ab7cf2d4fb1f4849bfa6b82f546945)) * update tool reference in an error ([#205](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/205)) ([7765bb3](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/7765bb381ad9d01219547faf879a74978188754a)) ## [0.4.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.3.0...chrome-devtools-mcp-v0.4.0) (2025-09-26) ### Features * add network request filtering by resource type ([#162](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/162)) ([59d81a3](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/59d81a33258a199a3f993c9e02a415f62ef05ce4)) ### Bug Fixes * add core web vitals to performance_start_trace description ([#168](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/168)) ([6cfc977](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/6cfc9774f4ec7944c70842999506b2bc2018a667)) * add data format information to trace summary ([#166](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/166)) ([869dd42](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/869dd4273e42309c1bb57d44e0e5a6a9506ffad7)) * expose --debug-file argument ([#164](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/164)) ([22ec7ee](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/22ec7ee45cc04892000cf6dc32f3fe58d33855c1)) * typo in the disclaimers ([#156](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/156)) ([90f686e](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/90f686e5df3d880c35ec566c837ee5a98824be28)) ## [0.3.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.2.7...chrome-devtools-mcp-v0.3.0) (2025-09-25) ### Features * Add pagination list_network_requests ([#145](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/145)) ([4c909bb](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/4c909bb8d7c4a420cb8e3219ec98abf28f5cc664)) ### Bug Fixes * avoid reporting page close errors as errors ([#127](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/127)) ([44cfc8f](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/44cfc8f945edf9370efe26247f322a59a4a4a7be)) * clarify the node version message ([#135](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/135)) ([0cc907a](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/0cc907a9ad79289a6785e9690c3c6940f0a5de52)) * do not set channel if executablePath is provided ([#150](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/150)) ([03b59f0](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/03b59f0bca024173ad45d7a617994e919d9cbbad)) * **performance:** ImageDelivery insight errors ([#144](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/144)) ([d64ba0d](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d64ba0d9027540eb707381e2577ae3c1fe014346)) * roll latest DevTools to handle Insight errors ([#149](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/149)) ([b2e1e39](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/b2e1e3944c7fa170584ce36c7b8923b0e6d6c6cb)) ## [0.2.7](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.2.6...chrome-devtools-mcp-v0.2.7) (2025-09-24) ### Bug Fixes * validate and report incompatible Node versions ([#113](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/113)) ([adfcecf](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/adfcecf9871938b1ad5d1460e0050b849fb2aa49)) ## [0.2.6](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.2.5...chrome-devtools-mcp-v0.2.6) (2025-09-24) ### Bug Fixes * manually bump server.json versions based on package.json ([#105](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/105)) ([cae1cf1](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/cae1cf13d5a97add3b96f20c425f720a1ceabf94)) ## [0.2.5](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.2.4...chrome-devtools-mcp-v0.2.5) (2025-09-24) ### Bug Fixes * add mcpName to package.json ([#103](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/103)) ([bd0351f](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/bd0351fd36ae35e41e613f0d15df40aeca17ba94)) ## [0.2.4](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.2.3...chrome-devtools-mcp-v0.2.4) (2025-09-24) ### Bug Fixes * forbid closing the last page ([#90](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/90)) ([0ca2434](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/0ca2434a29eb4bc6e570a4ebe21a135d85f4c0f3)) ## [0.2.3](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.2.2...chrome-devtools-mcp-v0.2.3) (2025-09-24) ### Bug Fixes * add a message indicating that no console messages exist ([#91](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/91)) ([1a4ba4d](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/1a4ba4d3e05f51a85747816f8638f31230881437)) * clean up pending promises on action errors ([#84](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/84)) ([4e7001a](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/4e7001ac375ec51f55b29e9faf68aff0dd09fa0f)) ## [0.2.2](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.2.1...chrome-devtools-mcp-v0.2.2) (2025-09-23) ### Bug Fixes * cli version being reported as unknown ([#74](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/74)) ([d6bab91](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d6bab912df55dc2e96a8d7893d1906f1fc608d0a)) * remove unnecessary waiting for navigation ([#83](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/83)) ([924c042](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/924c042492222a555074063841ce765342e3b5b9)) * rework performance parsing & error handling ([#75](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/75)) ([e8fb30c](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/e8fb30c1bfdc2b4ea8c2daf74b24aa82210f99be)) ## [0.2.1](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.2.0...chrome-devtools-mcp-v0.2.1) (2025-09-23) ### Bug Fixes * add 'on the selected page' to performance tools ([#69](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/69)) ([b877f7a](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/b877f7a3053d0cdf2aad1fefc26cf7b913eb95ce)) * **emulation:** correctly report info for selected page ([#63](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/63)) ([1e8662f](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/1e8662f06860aecb5c01ed4ff1515ceb9dac26e4)) * expose timeout when Emulation is enabled ([#73](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/73)) ([0208bfd](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/0208bfdcf6924953879408c18f4c20da544bf4ff)) * fix browserUrl not working ([#53](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/53)) ([a6923b8](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a6923b8d9397d12ee0f9fe67dd62b10088ec6e87)) * increase timeouts in case of Emulation ([#71](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/71)) ([c509c64](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/c509c64576e1be1ddc283653004ef08a117907a2)) * **windows:** work around Chrome not reporting reasons for crash ([#64](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/64)) ([d545741](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/d5457412a4a76726547190fb3a46bb78c9d6645c)) ## [0.2.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.1.0...chrome-devtools-mcp-v0.2.0) (2025-09-17) ### Features * add performance_analyze_insight tool. ([#42](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/42)) ([21e175b](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/21e175b862c624d7a2d07802141187edf2d2e489)) * support script evaluate arguments ([#40](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/40)) ([c663f4d](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/c663f4d7f9c0b868e8b4750f6441525939bfe920)) * use Performance Trace Formatter in trace output ([#36](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/36)) ([0cb6147](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/0cb6147b870e17bc3a624e9c6396d963a3e16b44)) * validate uids ([#37](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/37)) ([014a8bc](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/014a8bc52ecc58080cedeb8023d44f4a55055a05)) ### Bug Fixes * change profile folder name to browser-profile ([#39](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/39)) ([36115d7](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/36115d757abbae0502ffee814f55368d2ca59b9e)) * refresh context based on the browser instance ([#44](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/44)) ([93f4579](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/93f4579dd9aca3beef2bd9f2930ddfcc4069c0e3)) * update puppeteer to fix a11y snapshot issues ([#43](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/43)) ([b58f787](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/b58f787234a34d5fcb01b336f5fb14e1c55ecdd5)) ## [0.1.0](https://github.com/ChromeDevTools/chrome-devtools-mcp/compare/chrome-devtools-mcp-v0.0.2...chrome-devtools-mcp-v0.1.0) (2025-09-16) ### Features * improve tools with awaiting common events ([#10](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/10)) ([dba8b3c](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/dba8b3c5fad0d1bca26aaf172751c51188799927)) * initial version ([31a0bdc](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/31a0bdce266a33eaca9a7daae4611abb78ff5a25)) ### Bug Fixes * define tracing categories ([#21](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/21)) ([c939456](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/c93945657cc96ac7ba213730a750c16e9ab87526)) * detect multiple instances and throw ([#12](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/12)) ([732267d](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/732267db5fea0048ed1fcc530bcdd074df4126be)) * make sure tool calls are processed sequentially ([#22](https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/22)) ([a76b23d](https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/a76b23dccf074a13304b0341178665465a2c3399)) ================================================ FILE: CONTRIBUTING.md ================================================ # How to contribute We'd love to accept your patches and contributions to this project. ## Before you begin ### Sign our Contributor License Agreement Contributions to this project must be accompanied by a [Contributor License Agreement](https://cla.developers.google.com/about) (CLA). You (or your employer) retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as part of the project. If you or your current employer have already signed the Google CLA (even if it was for a different project), you probably don't need to do it again. Visit to see your current agreements or to sign a new one. ### Review our community guidelines This project follows [Google's Open Source Community Guidelines](https://opensource.google/conduct/). ## Development process ### Code reviews All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. ### Conventional commits Please follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) for PR and commit titles. ### Feature release checklist Use `chore:` for commits containing incomplete features that are not available to users yet. Once the feature is ready to be released, create a PR with a `feat:` prefix that enables the feature. The following criteria need to be completed: - Documentation for the feature is up to date. For example, README.md and tools reference are updated. - The feature can be used with Chrome stable or version restrictions are documented otherwise. - Corresponding skills are updated or new skills are added if needed. - The feature fulfills the use case by its own or in conjunction with existing features (we want to avoid features that offer some tools but cannot be used successfully to debug things). ### Release process Releasing `chrome-devtools-mcp` is automated by GitHub Actions. To release a new version, [search for a PR titled `chore(main): release chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp/pulls?q=is%3Apr+is%3Aopen+%22chore%28main%29%3A+release+chrome-devtools-mcp%22) and review, test, and land it. The release PR is automatically opened if there are any changes on the main branch that show up in the changelog. ## Installation Check that you are using node version specified in .nvmrc, then run following commands: ```sh git clone https://github.com/ChromeDevTools/chrome-devtools-mcp.git cd chrome-devtools-mcp npm ci npm run build ``` ### Testing with @modelcontextprotocol/inspector ```sh npx @modelcontextprotocol/inspector node /build/src/bin/chrome-devtools-mcp.js ``` ### Testing with an MCP client Add the MCP server to your client's config. ```json { "mcpServers": { "chrome-devtools": { "command": "node", "args": ["/path-to/build/src/bin/chrome-devtools-mcp.js"] } } } ``` #### Using with VS Code SSH When running the `@modelcontextprotocol/inspector` it spawns 2 services - one on port `6274` and one on `6277`. Usually VS Code automatically detects and forwards `6274` but fails to detect `6277` so you need to manually forward it. ### Debugging To write debug logs to `log.txt` in the working directory, run with the following commands: ```sh npx @modelcontextprotocol/inspector node /build/src/bin/chrome-devtools-mcp.js --log-file=/your/desired/path/log.txt ``` You can use the `DEBUG` environment variable as usual to control categories that are logged. ### Updating documentation When adding a new tool or updating a tool name or description, make sure to run `npm run gen` to generate the tool reference documentation. ### Contributing to Evals We use Gemini to evaluate the MCP server tools in `scripts/eval_scenarios`. Each scenario is a TypeScript file that exports a `scenario` object implementing `TestScenario`. - **prompt**: The prompt to send to the model. - **maxTurns**: Maximum number of conversation turns. - **expectations**: A function that verifies the tool calls made by the model. - **htmlRoute** (Optional): Serve custom HTML content for the test at a specific path. We look to test that the tools are used correctly without too rigid assertions. Avoid asserting exact argument values if they can vary (e.g., natural language reasoning), but ensure the core parameters (like URLs or selectors) were correct. Example: ```ts import {TestScenario} from '../eval_gemini.js'; export const scenario: TestScenario = { prompt: 'Navigate to example.com', maxTurns: 2, expectations: calls => { // Check that at least one call was 'browse_page' const navigation = calls.find(c => c.name === 'browse_page'); if (!navigation) throw new Error('Model did not browse the page'); // Verify essential args if (navigation.args.url !== 'http://example.com') { throw new Error(`Wrong URL: ${navigation.args.url}`); } }, }; ``` ## Restrictions on JSON schema - no .nullable(), no .object() types. - represent complex object as a short formatted string. TODO: implement eslint for schema https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1076 ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Chrome DevTools MCP [![npm chrome-devtools-mcp package](https://img.shields.io/npm/v/chrome-devtools-mcp.svg)](https://npmjs.org/package/chrome-devtools-mcp) `chrome-devtools-mcp` lets your coding agent (such as Gemini, Claude, Cursor or Copilot) control and inspect a live Chrome browser. It acts as a Model-Context-Protocol (MCP) server, giving your AI coding assistant access to the full power of Chrome DevTools for reliable automation, in-depth debugging, and performance analysis. ## [Tool reference](./docs/tool-reference.md) | [Changelog](./CHANGELOG.md) | [Contributing](./CONTRIBUTING.md) | [Troubleshooting](./docs/troubleshooting.md) | [Design Principles](./docs/design-principles.md) ## Key features - **Get performance insights**: Uses [Chrome DevTools](https://github.com/ChromeDevTools/devtools-frontend) to record traces and extract actionable performance insights. - **Advanced browser debugging**: Analyze network requests, take screenshots and check browser console messages (with source-mapped stack traces). - **Reliable automation**. Uses [puppeteer](https://github.com/puppeteer/puppeteer) to automate actions in Chrome and automatically wait for action results. ## Disclaimers `chrome-devtools-mcp` exposes content of the browser instance to the MCP clients allowing them to inspect, debug, and modify any data in the browser or DevTools. Avoid sharing sensitive or personal information that you don't want to share with MCP clients. Performance tools may send trace URLs to the Google CrUX API to fetch real-user experience data. This helps provide a holistic performance picture by presenting field data alongside lab data. This data is collected by the [Chrome User Experience Report (CrUX)](https://developer.chrome.com/docs/crux). To disable this, run with the `--no-performance-crux` flag. ## **Usage statistics** Google collects usage statistics (such as tool invocation success rates, latency, and environment information) to improve the reliability and performance of Chrome DevTools MCP. Data collection is **enabled by default**. You can opt-out by passing the `--no-usage-statistics` flag when starting the server: ```json "args": ["-y", "chrome-devtools-mcp@latest", "--no-usage-statistics"] ``` Google handles this data in accordance with the [Google Privacy Policy](https://policies.google.com/privacy). Google's collection of usage statistics for Chrome DevTools MCP is independent from the Chrome browser's usage statistics. Opting out of Chrome metrics does not automatically opt you out of this tool, and vice-versa. Collection is disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set. ## Requirements - [Node.js](https://nodejs.org/) v20.19 or a newer [latest maintenance LTS](https://github.com/nodejs/Release#release-schedule) version. - [Chrome](https://www.google.com/chrome/) current stable version or newer. - [npm](https://www.npmjs.com/). ## Getting started Add the following config to your MCP client: ```json { "mcpServers": { "chrome-devtools": { "command": "npx", "args": ["-y", "chrome-devtools-mcp@latest"] } } } ``` > [!NOTE] > Using `chrome-devtools-mcp@latest` ensures that your MCP client will always use the latest version of the Chrome DevTools MCP server. If you are interested in doing only basic browser tasks, use the `--slim` mode: ```json { "mcpServers": { "chrome-devtools": { "command": "npx", "args": ["-y", "chrome-devtools-mcp@latest", "--slim", "--headless"] } } } ``` See [Slim tool reference](./docs/slim-tool-reference.md). ### MCP Client configuration
Amp Follow https://ampcode.com/manual#mcp and use the config provided above. You can also install the Chrome DevTools MCP server using the CLI: ```bash amp mcp add chrome-devtools -- npx chrome-devtools-mcp@latest ```
Antigravity To use the Chrome DevTools MCP server follow the instructions from Antigravity's docs to install a custom MCP server. Add the following config to the MCP servers config: ```bash { "mcpServers": { "chrome-devtools": { "command": "npx", "args": [ "chrome-devtools-mcp@latest", "--browser-url=http://127.0.0.1:9222", "-y" ] } } } ``` This will make the Chrome DevTools MCP server automatically connect to the browser that Antigravity is using. If you are not using port 9222, make sure to adjust accordingly. Chrome DevTools MCP will not start the browser instance automatically using this approach because the Chrome DevTools MCP server connects to Antigravity's built-in browser. If the browser is not already running, you have to start it first by clicking the Chrome icon at the top right corner.
Claude Code **Install via CLI (MCP only)** Use the Claude Code CLI to add the Chrome DevTools MCP server (guide): ```bash claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest ``` **Install as a Plugin (MCP + Skills)** > [!NOTE] > If you already had Chrome DevTools MCP installed previously for Claude Code, make sure to remove it first from your installation and configuration files. To install Chrome DevTools MCP with skills, add the marketplace registry in Claude Code: ```sh /plugin marketplace add ChromeDevTools/chrome-devtools-mcp ``` Then, install the plugin: ```sh /plugin install chrome-devtools-mcp ``` Restart Claude Code to have the MCP server and skills load (check with `/skills`). > [!TIP] > If the plugin installation fails with a `Failed to clone repository` error (e.g., HTTPS connectivity issues behind a corporate firewall), see the [troubleshooting guide](./docs/troubleshooting.md#claude-code-plugin-installation-fails-with-failed-to-clone-repository) for workarounds, or use the CLI installation method above instead.
Cline Follow https://docs.cline.bot/mcp/configuring-mcp-servers and use the config provided above.
Codex Follow the configure MCP guide using the standard config from above. You can also install the Chrome DevTools MCP server using the Codex CLI: ```bash codex mcp add chrome-devtools -- npx chrome-devtools-mcp@latest ``` **On Windows 11** Configure the Chrome install location and increase the startup timeout by updating `.codex/config.toml` and adding the following `env` and `startup_timeout_ms` parameters: ``` [mcp_servers.chrome-devtools] command = "cmd" args = [ "/c", "npx", "-y", "chrome-devtools-mcp@latest", ] env = { SystemRoot="C:\\Windows", PROGRAMFILES="C:\\Program Files" } startup_timeout_ms = 20_000 ```
Copilot CLI Start Copilot CLI: ``` copilot ``` Start the dialog to add a new MCP server by running: ``` /mcp add ``` Configure the following fields and press `CTRL+S` to save the configuration: - **Server name:** `chrome-devtools` - **Server Type:** `[1] Local` - **Command:** `npx -y chrome-devtools-mcp@latest`
Copilot / VS Code **Click the button to install:** [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=io.github.ChromeDevTools%2Fchrome-devtools-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22chrome-devtools-mcp%22%5D%2C%22env%22%3A%7B%7D%7D) [Install in VS Code Insiders](https://insiders.vscode.dev/redirect?url=vscode-insiders%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522io.github.ChromeDevTools%252Fchrome-devtools-mcp%2522%252C%2522config%2522%253A%257B%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522chrome-devtools-mcp%2522%255D%252C%2522env%2522%253A%257B%257D%257D%257D) **Or install manually:** Follow the MCP install guide, with the standard config from above. You can also install the Chrome DevTools MCP server using the VS Code CLI: For macOS and Linux: ```bash code --add-mcp '{"name":"io.github.ChromeDevTools/chrome-devtools-mcp","command":"npx","args":["-y","chrome-devtools-mcp"],"env":{}}' ``` For Windows (PowerShell): ```powershell code --add-mcp '{"""name""":"""io.github.ChromeDevTools/chrome-devtools-mcp""","""command""":"""npx""","""args""":["""-y""","""chrome-devtools-mcp"""]}' ```
Cursor **Click the button to install:** [Install in Cursor](https://cursor.com/en/install-mcp?name=chrome-devtools&config=eyJjb21tYW5kIjoibnB4IC15IGNocm9tZS1kZXZ0b29scy1tY3BAbGF0ZXN0In0%3D) **Or install manually:** Go to `Cursor Settings` -> `MCP` -> `New MCP Server`. Use the config provided above.
Factory CLI Use the Factory CLI to add the Chrome DevTools MCP server (guide): ```bash droid mcp add chrome-devtools "npx -y chrome-devtools-mcp@latest" ```
Gemini CLI Install the Chrome DevTools MCP server using the Gemini CLI. **Project wide:** ```bash # Either MCP only: gemini mcp add chrome-devtools npx chrome-devtools-mcp@latest # Or as a Gemini extension (MCP+Skills): gemini extensions install --auto-update https://github.com/ChromeDevTools/chrome-devtools-mcp ``` **Globally:** ```bash gemini mcp add -s user chrome-devtools npx chrome-devtools-mcp@latest ``` Alternatively, follow the MCP guide and use the standard config from above.
Gemini Code Assist Follow the configure MCP guide using the standard config from above.
JetBrains AI Assistant & Junie Go to `Settings | Tools | AI Assistant | Model Context Protocol (MCP)` -> `Add`. Use the config provided above. The same way chrome-devtools-mcp can be configured for JetBrains Junie in `Settings | Tools | Junie | MCP Settings` -> `Add`. Use the config provided above.
Kiro In **Kiro Settings**, go to `Configure MCP` > `Open Workspace or User MCP Config` > Use the configuration snippet provided above. Or, from the IDE **Activity Bar** > `Kiro` > `MCP Servers` > `Click Open MCP Config`. Use the configuration snippet provided above.
Katalon Studio The Chrome DevTools MCP server can be used with Katalon StudioAssist via an MCP proxy. **Step 1:** Install the MCP proxy by following the MCP proxy setup guide. **Step 2:** Start the Chrome DevTools MCP server with the proxy: ```bash mcp-proxy --transport streamablehttp --port 8080 -- npx -y chrome-devtools-mcp@latest ``` **Note:** You may need to pick another port if 8080 is already in use. **Step 3:** In Katalon Studio, add the server to StudioAssist with the following settings: - **Connection URL:** `http://127.0.0.1:8080/mcp` - **Transport type:** `HTTP` Once connected, the Chrome DevTools MCP tools will be available in StudioAssist.
OpenCode Add the following configuration to your `opencode.json` file. If you don't have one, create it at `~/.config/opencode/opencode.json` (guide): ```json { "$schema": "https://opencode.ai/config.json", "mcp": { "chrome-devtools": { "type": "local", "command": ["npx", "-y", "chrome-devtools-mcp@latest"] } } } ```
Qoder In **Qoder Settings**, go to `MCP Server` > `+ Add` > Use the configuration snippet provided above. Alternatively, follow the MCP guide and use the standard config from above.
Qoder CLI Install the Chrome DevTools MCP server using the Qoder CLI (guide): **Project wide:** ```bash qodercli mcp add chrome-devtools -- npx chrome-devtools-mcp@latest ``` **Globally:** ```bash qodercli mcp add -s user chrome-devtools -- npx chrome-devtools-mcp@latest ```
Visual Studio **Click the button to install:** [Install in Visual Studio](https://vs-open.link/mcp-install?%7B%22name%22%3A%22chrome-devtools%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22chrome-devtools-mcp%40latest%22%5D%7D)
Warp Go to `Settings | AI | Manage MCP Servers` -> `+ Add` to [add an MCP Server](https://docs.warp.dev/knowledge-and-collaboration/mcp#adding-an-mcp-server). Use the config provided above.
Windsurf Follow the configure MCP guide using the standard config from above.
### Your first prompt Enter the following prompt in your MCP Client to check if everything is working: ``` Check the performance of https://developers.chrome.com ``` Your MCP client should open the browser and record a performance trace. > [!NOTE] > The MCP server will start the browser automatically once the MCP client uses a tool that requires a running browser instance. Connecting to the Chrome DevTools MCP server on its own will not automatically start the browser. ## Tools If you run into any issues, checkout our [troubleshooting guide](./docs/troubleshooting.md). - **Input automation** (9 tools) - [`click`](docs/tool-reference.md#click) - [`drag`](docs/tool-reference.md#drag) - [`fill`](docs/tool-reference.md#fill) - [`fill_form`](docs/tool-reference.md#fill_form) - [`handle_dialog`](docs/tool-reference.md#handle_dialog) - [`hover`](docs/tool-reference.md#hover) - [`press_key`](docs/tool-reference.md#press_key) - [`type_text`](docs/tool-reference.md#type_text) - [`upload_file`](docs/tool-reference.md#upload_file) - **Navigation automation** (6 tools) - [`close_page`](docs/tool-reference.md#close_page) - [`list_pages`](docs/tool-reference.md#list_pages) - [`navigate_page`](docs/tool-reference.md#navigate_page) - [`new_page`](docs/tool-reference.md#new_page) - [`select_page`](docs/tool-reference.md#select_page) - [`wait_for`](docs/tool-reference.md#wait_for) - **Emulation** (2 tools) - [`emulate`](docs/tool-reference.md#emulate) - [`resize_page`](docs/tool-reference.md#resize_page) - **Performance** (4 tools) - [`performance_analyze_insight`](docs/tool-reference.md#performance_analyze_insight) - [`performance_start_trace`](docs/tool-reference.md#performance_start_trace) - [`performance_stop_trace`](docs/tool-reference.md#performance_stop_trace) - [`take_memory_snapshot`](docs/tool-reference.md#take_memory_snapshot) - **Network** (2 tools) - [`get_network_request`](docs/tool-reference.md#get_network_request) - [`list_network_requests`](docs/tool-reference.md#list_network_requests) - **Debugging** (6 tools) - [`evaluate_script`](docs/tool-reference.md#evaluate_script) - [`get_console_message`](docs/tool-reference.md#get_console_message) - [`lighthouse_audit`](docs/tool-reference.md#lighthouse_audit) - [`list_console_messages`](docs/tool-reference.md#list_console_messages) - [`take_screenshot`](docs/tool-reference.md#take_screenshot) - [`take_snapshot`](docs/tool-reference.md#take_snapshot) ## Configuration The Chrome DevTools MCP server supports the following configuration option: - **`--autoConnect`/ `--auto-connect`** If specified, automatically connects to a browser (Chrome 144+) running locally from the user data directory identified by the channel param (default channel is stable). Requires the remoted debugging server to be started in the Chrome instance via chrome://inspect/#remote-debugging. - **Type:** boolean - **Default:** `false` - **`--browserUrl`/ `--browser-url`, `-u`** 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. - **Type:** string - **`--wsEndpoint`/ `--ws-endpoint`, `-w`** WebSocket endpoint to connect to a running Chrome instance (e.g., ws://127.0.0.1:9222/devtools/browser/). Alternative to --browserUrl. - **Type:** string - **`--wsHeaders`/ `--ws-headers`** Custom headers for WebSocket connection in JSON format (e.g., '{"Authorization":"Bearer token"}'). Only works with --wsEndpoint. - **Type:** string - **`--headless`** Whether to run in headless (no UI) mode. - **Type:** boolean - **Default:** `false` - **`--executablePath`/ `--executable-path`, `-e`** Path to custom Chrome executable. - **Type:** string - **`--isolated`** If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed. Defaults to false. - **Type:** boolean - **`--userDataDir`/ `--user-data-dir`** Path to the user data directory for Chrome. Default is $HOME/.cache/chrome-devtools-mcp/chrome-profile$CHANNEL_SUFFIX_IF_NON_STABLE - **Type:** string - **`--channel`** Specify a different Chrome channel that should be used. The default is the stable channel version. - **Type:** string - **Choices:** `stable`, `canary`, `beta`, `dev` - **`--logFile`/ `--log-file`** Path to a file to write debug logs to. Set the env variable `DEBUG` to `*` to enable verbose logs. Useful for submitting bug reports. - **Type:** string - **`--viewport`** Initial viewport size for the Chrome instances started by the server. For example, `1280x720`. In headless mode, max size is 3840x2160px. - **Type:** string - **`--proxyServer`/ `--proxy-server`** Proxy server configuration for Chrome passed as --proxy-server when launching the browser. See https://www.chromium.org/developers/design-documents/network-settings/ for details. - **Type:** string - **`--acceptInsecureCerts`/ `--accept-insecure-certs`** If enabled, ignores errors relative to self-signed and expired certificates. Use with caution. - **Type:** boolean - **`--experimentalScreencast`/ `--experimental-screencast`** Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH. - **Type:** boolean - **`--chromeArg`/ `--chrome-arg`** Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp. - **Type:** array - **`--ignoreDefaultChromeArg`/ `--ignore-default-chrome-arg`** Explicitly disable default arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp. - **Type:** array - **`--categoryEmulation`/ `--category-emulation`** Set to false to exclude tools related to emulation. - **Type:** boolean - **Default:** `true` - **`--categoryPerformance`/ `--category-performance`** Set to false to exclude tools related to performance. - **Type:** boolean - **Default:** `true` - **`--categoryNetwork`/ `--category-network`** Set to false to exclude tools related to network. - **Type:** boolean - **Default:** `true` - **`--performanceCrux`/ `--performance-crux`** Set to false to disable sending URLs from performance traces to CrUX API to get field performance data. - **Type:** boolean - **Default:** `true` - **`--usageStatistics`/ `--usage-statistics`** Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set. - **Type:** boolean - **Default:** `true` - **`--slim`** Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks. - **Type:** boolean Pass them via the `args` property in the JSON configuration. For example: ```json { "mcpServers": { "chrome-devtools": { "command": "npx", "args": [ "chrome-devtools-mcp@latest", "--channel=canary", "--headless=true", "--isolated=true" ] } } } ``` ### Connecting via WebSocket with custom headers You can connect directly to a Chrome WebSocket endpoint and include custom headers (e.g., for authentication): ```json { "mcpServers": { "chrome-devtools": { "command": "npx", "args": [ "chrome-devtools-mcp@latest", "--wsEndpoint=ws://127.0.0.1:9222/devtools/browser/", "--wsHeaders={\"Authorization\":\"Bearer YOUR_TOKEN\"}" ] } } } ``` To get the WebSocket endpoint from a running Chrome instance, visit `http://127.0.0.1:9222/json/version` and look for the `webSocketDebuggerUrl` field. You can also run `npx chrome-devtools-mcp@latest --help` to see all available configuration options. ## Concepts ### User data directory `chrome-devtools-mcp` starts a Chrome's stable channel instance using the following user data directory: - Linux / macOS: `$HOME/.cache/chrome-devtools-mcp/chrome-profile-$CHANNEL` - Windows: `%HOMEPATH%/.cache/chrome-devtools-mcp/chrome-profile-$CHANNEL` The user data directory is not cleared between runs and shared across all instances of `chrome-devtools-mcp`. Set the `isolated` option to `true` to use a temporary user data dir instead which will be cleared automatically after the browser is closed. ### Connecting to a running Chrome instance By default, the Chrome DevTools MCP server will start a new Chrome instance with a dedicated profile. This might not be ideal in all situations: - If you would like to maintain the same application state when alternating between manual site testing and agent-driven testing. - When the MCP needs to sign into a website. Some accounts may prevent sign-in when the browser is controlled via WebDriver (the default launch mechanism for the Chrome DevTools MCP server). - If you're running your LLM inside a sandboxed environment, but you would like to connect to a Chrome instance that runs outside the sandbox. In these cases, start Chrome first and let the Chrome DevTools MCP server connect to it. There are two ways to do so: - **Automatic connection (available in Chrome 144)**: best for sharing state between manual and agent-driven testing. - **Manual connection via remote debugging port**: best when running inside a sandboxed environment. #### Automatically connecting to a running Chrome instance **Step 1:** Set up remote debugging in Chrome In Chrome (\>= M144), do the following to set up remote debugging: 1. Navigate to `chrome://inspect/#remote-debugging` to enable remote debugging. 2. Follow the dialog UI to allow or disallow incoming debugging connections. **Step 2:** Configure Chrome DevTools MCP server to automatically connect to a running Chrome Instance To connect the `chrome-devtools-mcp` server to the running Chrome instance, use `--autoConnect` command line argument for the MCP server. The following code snippet is an example configuration for gemini-cli: ```json { "mcpServers": { "chrome-devtools": { "command": "npx", "args": ["chrome-devtools-mcp@latest", "--autoConnect"] } } } ``` **Step 3:** Test your setup Make sure your browser is running. Open gemini-cli and run the following prompt: ```none Check the performance of https://developers.chrome.com ``` > [!NOTE] > The autoConnect option requires the user to start Chrome. If the user has multiple active profiles, the MCP server will connect to the default profile (as determined by Chrome). The MCP server has access to all open windows for the selected profile. The Chrome DevTools MCP server will try to connect to your running Chrome instance. It shows a dialog asking for user permission. Clicking **Allow** results in the Chrome DevTools MCP server opening [developers.chrome.com](http://developers.chrome.com) and taking a performance trace. #### Manual connection using port forwarding You can connect to a running Chrome instance by using the `--browser-url` option. This is useful if you are running the MCP server in a sandboxed environment that does not allow starting a new Chrome instance. Here is a step-by-step guide on how to connect to a running Chrome instance: **Step 1: Configure the MCP client** Add the `--browser-url` option to your MCP client configuration. The value of this option should be the URL of the running Chrome instance. `http://127.0.0.1:9222` is a common default. ```json { "mcpServers": { "chrome-devtools": { "command": "npx", "args": [ "chrome-devtools-mcp@latest", "--browser-url=http://127.0.0.1:9222" ] } } } ``` **Step 2: Start the Chrome browser** > [!WARNING] > Enabling the remote debugging port opens up a debugging port on the running browser instance. Any application on your machine can connect to this port and control the browser. Make sure that you are not browsing any sensitive websites while the debugging port is open. Start the Chrome browser with the remote debugging port enabled. Make sure to close any running Chrome instances before starting a new one with the debugging port enabled. The port number you choose must be the same as the one you specified in the `--browser-url` option in your MCP client configuration. For security reasons, [Chrome requires you to use a non-default user data directory](https://developer.chrome.com/blog/remote-debugging-port) when enabling the remote debugging port. You can specify a custom directory using the `--user-data-dir` flag. This ensures that your regular browsing profile and data are not exposed to the debugging session. **macOS** ```bash /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-profile-stable ``` **Linux** ```bash /usr/bin/google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-profile-stable ``` **Windows** ```bash "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --user-data-dir="%TEMP%\chrome-profile-stable" ``` **Step 3: Test your setup** After configuring the MCP client and starting the Chrome browser, you can test your setup by running a simple prompt in your MCP client: ``` Check the performance of https://developers.chrome.com ``` Your MCP client should connect to the running Chrome instance and receive a performance report. If you hit VM-to-host port forwarding issues, see the “Remote debugging between virtual machine (VM) and host fails” section in [`docs/troubleshooting.md`](./docs/troubleshooting.md#remote-debugging-between-virtual-machine-vm-and-host-fails). For more details on remote debugging, see the [Chrome DevTools documentation](https://developer.chrome.com/docs/devtools/remote-debugging/). ### Debugging Chrome on Android Please consult [these instructions](./docs/debugging-android.md). ## Known limitations See [Troubleshooting](./docs/troubleshooting.md). ================================================ FILE: SECURITY.md ================================================ ## Security policy The Chrome DevTools MCP project takes security very seriously. Please use [Chromium’s process to report security issues](https://www.chromium.org/Home/chromium-security/reporting-security-bugs/). ================================================ FILE: docs/cli.md ================================================ # Chrome DevTools CLI The `chrome-devtools-mcp` package includes an **experimental** CLI interface that allows you to interact with the browser directly from your terminal. This is particularly useful for debugging or when you want an agent to generate scripts that automate browser actions. ## Getting started Install the package globally to make the `chrome-devtools` command available: ```sh npm i chrome-devtools-mcp@latest -g chrome-devtools status # check if install worked. ``` ## How it works The CLI acts as a client to a background `chrome-devtools-mcp` daemon (uses Unix sockets on Linux/Mac and named pipes on Windows). - **Automatic Start**: The first time you call a tool (e.g., `list_pages`), the CLI automatically starts the MCP server and the browser in the background if they aren't already running. - **Persistence**: The same background instance is reused for subsequent commands, preserving the browser state (open pages, cookies, etc.). - **Manual Control**: You can explicitly manage the background process using `start`, `stop`, and `status`. The `start` command forwards all subsequent arguments to the underlying MCP server (e.g., `--headless`, `--userDataDir`) but not all args are supported. Run `chrome-devtools start --help` for supported args. Headless and isolated are enabled by default. ```sh # Check if the daemon is running chrome-devtools status # Navigate the current page to a URL chrome-devtools navigate_page "https://google.com" # Take a screenshot and save it to a file chrome-devtools take_screenshot --filePath screenshot.png # Stop the background daemon when finished chrome-devtools stop ``` ## Command Usage The CLI supports all tools available in the [Tool reference](./tool-reference.md). ```sh chrome-devtools [arguments] [flags] ``` - **Required Arguments**: Passed as positional arguments. - **Optional Arguments**: Passed as flags (e.g., `--filePath`, `--fullPage`). ### Examples **New Page and Navigation:** ```sh chrome-devtools new_page "https://example.com" chrome-devtools navigate_page "https://web.dev" --type url ``` **Interaction:** ```sh # Click an element by its UID from a snapshot chrome-devtools click "element-uid-123" # Fill a form field chrome-devtools fill "input-uid-456" "search query" ``` **Analysis:** ```sh # Run a Lighthouse audit (defaults to navigation mode) chrome-devtools lighthouse_audit --mode snapshot ``` ## Output format By default, the CLI outputs a human-readable summary of the tool's result. For programmatic use, you can request raw JSON: ```sh chrome-devtools list_pages --output-format=json ``` ## Troubleshooting If the CLI hangs or fails to connect, try stopping the background process: ```sh chrome-devtools stop ``` For more verbose logs, set the `DEBUG` environment variable: ```sh DEBUG=* chrome-devtools list_pages ``` ## CLI generation Implemented in `scripts/generate-cli.ts`. Some commands are excluded from CLI generation such as `wait_for` and `fill_form`. `chrome-devtools-mcp` args are also filtered in `src/bin/chrome-devtools.ts` because not all args make sense in a CLI interface. ================================================ FILE: docs/debugging-android.md ================================================ # Experimental: Debugging Chrome on Android This is an experimental feature as Puppeteer does not officially support Chrome on Android as a target. The workflow below works for most users. See [Troubleshooting: DevTools is not detecting the Android device for more help](https://developer.chrome.com/docs/devtools/remote-debugging#troubleshooting) for more help. 1. Open the Developer Options screen on your Android. See [Configure on-device developer Options](https://developer.android.com/studio/debug/dev-options.html). 2. Select Enable USB Debugging. 3. Connect your Android device directly to your development machine using a USB cable. 4. On your development machine setup port forwarding from your development machine to your android device: ```shell adb forward tcp:9222 localabstract:chrome_devtools_remote ``` 5. Configure your MCP server to connect to the Chrome ```json "chrome-devtools": { "command": "npx", "args": [ "chrome-devtools-mcp@latest", "--wsEndpoint=ws://127.0.0.1:9222/devtools/browser/" ], "trust": true } ``` 6. Test your setup by running the following prompt in your coding agent: ```none Check the performance of developers.chrome.com ``` The Chrome DevTools MCP server should now control Chrome on your Android device. ================================================ FILE: docs/design-principles.md ================================================ # Design Principles These are rough guidelines to follow when shipping features for the MCP server. Apply them with nuance. - **Agent-Agnostic API**: Use standards like MCP. Don't lock in to one LLM. Interoperability is key. - **Token-Optimized**: Return semantic summaries. "LCP was 3.2s" is better than 50k lines of JSON. Files are the right location for large amounts of data. - **Small, Deterministic Blocks**: Give agents composable tools (Click, Screenshot), not magic buttons. - **Self-Healing Errors**: Return actionable errors that include context and potential fixes. - **Human-Agent Collaboration**: Output must be readable by machines (structured) AND humans (summaries). - **Progressive Complexity**: Tools should be simple by default (high-level actions) but offer advanced optional arguments for power users. - **Reference over Value**: for heavy assets (screenshots, traces, videos), return a file path or resource URI, never the raw data stream. Some MCP clients support a built-in handling of heavy assets e.g. directly displaying images. This _could_ be an exception. ================================================ FILE: docs/slim-tool-reference.md ================================================ # Chrome DevTools MCP Slim Tool Reference (~359 cl100k_base tokens) - **[Navigation automation](#navigation-automation)** (1 tools) - [`navigate`](#navigate) - **[Debugging](#debugging)** (2 tools) - [`evaluate`](#evaluate) - [`screenshot`](#screenshot) ## Navigation automation ### `navigate` **Description:** Loads a URL **Parameters:** - **url** (string) **(required)**: URL to [`navigate`](#navigate) to --- ## Debugging ### `evaluate` **Description:** Evaluates a JavaScript script **Parameters:** - **script** (string) **(required)**: JS script to run on the page --- ### `screenshot` **Description:** Takes a [`screenshot`](#screenshot) **Parameters:** None --- ================================================ FILE: docs/tool-reference.md ================================================ # Chrome DevTools MCP Tool Reference (~6940 cl100k_base tokens) - **[Input automation](#input-automation)** (9 tools) - [`click`](#click) - [`drag`](#drag) - [`fill`](#fill) - [`fill_form`](#fill_form) - [`handle_dialog`](#handle_dialog) - [`hover`](#hover) - [`press_key`](#press_key) - [`type_text`](#type_text) - [`upload_file`](#upload_file) - **[Navigation automation](#navigation-automation)** (6 tools) - [`close_page`](#close_page) - [`list_pages`](#list_pages) - [`navigate_page`](#navigate_page) - [`new_page`](#new_page) - [`select_page`](#select_page) - [`wait_for`](#wait_for) - **[Emulation](#emulation)** (2 tools) - [`emulate`](#emulate) - [`resize_page`](#resize_page) - **[Performance](#performance)** (4 tools) - [`performance_analyze_insight`](#performance_analyze_insight) - [`performance_start_trace`](#performance_start_trace) - [`performance_stop_trace`](#performance_stop_trace) - [`take_memory_snapshot`](#take_memory_snapshot) - **[Network](#network)** (2 tools) - [`get_network_request`](#get_network_request) - [`list_network_requests`](#list_network_requests) - **[Debugging](#debugging)** (6 tools) - [`evaluate_script`](#evaluate_script) - [`get_console_message`](#get_console_message) - [`lighthouse_audit`](#lighthouse_audit) - [`list_console_messages`](#list_console_messages) - [`take_screenshot`](#take_screenshot) - [`take_snapshot`](#take_snapshot) ## Input automation ### `click` **Description:** Clicks on the provided element **Parameters:** - **uid** (string) **(required)**: The uid of an element on the page from the page content snapshot - **dblClick** (boolean) _(optional)_: Set to true for double clicks. Default is false. - **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false. --- ### `drag` **Description:** [`Drag`](#drag) an element onto another element **Parameters:** - **from_uid** (string) **(required)**: The uid of the element to [`drag`](#drag) - **to_uid** (string) **(required)**: The uid of the element to drop into - **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false. --- ### `fill` **Description:** Type text into a input, text area or select an option from a <select> element. **Parameters:** - **uid** (string) **(required)**: The uid of an element on the page from the page content snapshot - **value** (string) **(required)**: The value to [`fill`](#fill) in - **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false. --- ### `fill_form` **Description:** [`Fill`](#fill) out multiple form elements at once **Parameters:** - **elements** (array) **(required)**: Elements from snapshot to [`fill`](#fill) out. - **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false. --- ### `handle_dialog` **Description:** If a browser dialog was opened, use this command to handle it **Parameters:** - **action** (enum: "accept", "dismiss") **(required)**: Whether to dismiss or accept the dialog - **promptText** (string) _(optional)_: Optional prompt text to enter into the dialog. --- ### `hover` **Description:** [`Hover`](#hover) over the provided element **Parameters:** - **uid** (string) **(required)**: The uid of an element on the page from the page content snapshot - **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false. --- ### `press_key` **Description:** Press a key or key combination. Use this when other input methods like [`fill`](#fill)() cannot be used (e.g., keyboard shortcuts, navigation keys, or special key combinations). **Parameters:** - **key** (string) **(required)**: A key or a combination (e.g., "Enter", "Control+A", "Control++", "Control+Shift+R"). Modifiers: Control, Shift, Alt, Meta - **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false. --- ### `type_text` **Description:** Type text using keyboard into a previously focused input **Parameters:** - **text** (string) **(required)**: The text to type - **submitKey** (string) _(optional)_: Optional key to press after typing. E.g., "Enter", "Tab", "Escape" --- ### `upload_file` **Description:** Upload a file through a provided element. **Parameters:** - **filePath** (string) **(required)**: The local path of the file to upload - **uid** (string) **(required)**: The uid of the file input element or an element that will open file chooser on the page from the page content snapshot - **includeSnapshot** (boolean) _(optional)_: Whether to include a snapshot in the response. Default is false. --- ## Navigation automation ### `close_page` **Description:** Closes the page by its index. The last open page cannot be closed. **Parameters:** - **pageId** (number) **(required)**: The ID of the page to close. Call [`list_pages`](#list_pages) to list pages. --- ### `list_pages` **Description:** Get a list of pages open in the browser. **Parameters:** None --- ### `navigate_page` **Description:** Go to a URL, or back, forward, or reload. Use project URL if not specified otherwise. **Parameters:** - **handleBeforeUnload** (enum: "accept", "decline") _(optional)_: Whether to auto accept or beforeunload dialogs triggered by this navigation. Default is accept. - **ignoreCache** (boolean) _(optional)_: Whether to ignore cache on reload. - **initScript** (string) _(optional)_: A JavaScript script to be executed on each new document before any other scripts for the next navigation. - **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used. - **type** (enum: "url", "back", "forward", "reload") _(optional)_: Navigate the page by URL, back or forward in history, or reload. - **url** (string) _(optional)_: Target URL (only type=url) --- ### `new_page` **Description:** Open a new tab and load a URL. Use project URL if not specified otherwise. **Parameters:** - **url** (string) **(required)**: URL to load in a new page. - **background** (boolean) _(optional)_: Whether to open the page in the background without bringing it to the front. Default is false (foreground). - **isolatedContext** (string) _(optional)_: If specified, the page is created in an isolated browser context with the given name. Pages in the same browser context share cookies and storage. Pages in different browser contexts are fully isolated. - **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used. --- ### `select_page` **Description:** Select a page as a context for future tool calls. **Parameters:** - **pageId** (number) **(required)**: The ID of the page to select. Call [`list_pages`](#list_pages) to get available pages. - **bringToFront** (boolean) _(optional)_: Whether to focus the page and bring it to the top. --- ### `wait_for` **Description:** Wait for the specified text to appear on the selected page. **Parameters:** - **text** (array) **(required)**: Non-empty list of texts. Resolves when any value appears on the page. - **timeout** (integer) _(optional)_: Maximum wait time in milliseconds. If set to 0, the default timeout will be used. --- ## Emulation ### `emulate` **Description:** Emulates various features on the selected page. **Parameters:** - **colorScheme** (enum: "dark", "light", "auto") _(optional)_: [`Emulate`](#emulate) the dark or the light mode. Set to "auto" to reset to the default. - **cpuThrottlingRate** (number) _(optional)_: Represents the CPU slowdown factor. Omit or set the rate to 1 to disable throttling - **geolocation** (string) _(optional)_: Geolocation (`<latitude>x<longitude>`) to [`emulate`](#emulate). Latitude between -90 and 90. Longitude between -180 and 180. Omit clear the geolocation override. - **networkConditions** (enum: "Offline", "Slow 3G", "Fast 3G", "Slow 4G", "Fast 4G") _(optional)_: Throttle network. Omit to disable throttling. - **userAgent** (string) _(optional)_: User agent to [`emulate`](#emulate). Set to empty string to clear the user agent override. - **viewport** (string) _(optional)_: [`Emulate`](#emulate) device viewports '<width>x<height>x<devicePixelRatio>[,mobile][,touch][,landscape]'. 'touch' and 'mobile' to [`emulate`](#emulate) mobile devices. 'landscape' to [`emulate`](#emulate) landscape mode. --- ### `resize_page` **Description:** Resizes the selected page's window so that the page has specified dimension **Parameters:** - **height** (number) **(required)**: Page height - **width** (number) **(required)**: Page width --- ## Performance ### `performance_analyze_insight` **Description:** Provides more detailed information on a specific Performance Insight of an insight set that was highlighted in the results of a trace recording. **Parameters:** - **insightName** (string) **(required)**: The name of the Insight you want more information on. For example: "DocumentLatency" or "LCPBreakdown" - **insightSetId** (string) **(required)**: The id for the specific insight set. Only use the ids given in the "Available insight sets" list. --- ### `performance_start_trace` **Description:** Start a performance trace on the selected webpage. Use to find frontend performance issues, Core Web Vitals (LCP, INP, CLS), and improve page load speed. **Parameters:** - **autoStop** (boolean) _(optional)_: Determines if the trace recording should be automatically stopped. - **filePath** (string) _(optional)_: The absolute file path, or a file path relative to the current working directory, to save the raw trace data. For example, trace.json.gz (compressed) or trace.json (uncompressed). - **reload** (boolean) _(optional)_: Determines if, once tracing has started, the current selected page should be automatically reloaded. Navigate the page to the right URL using the [`navigate_page`](#navigate_page) tool BEFORE starting the trace if reload or autoStop is set to true. --- ### `performance_stop_trace` **Description:** Stop the active performance trace recording on the selected webpage. **Parameters:** - **filePath** (string) _(optional)_: The absolute file path, or a file path relative to the current working directory, to save the raw trace data. For example, trace.json.gz (compressed) or trace.json (uncompressed). --- ### `take_memory_snapshot` **Description:** Capture a memory heapsnapshot of the currently selected page to memory leak debugging **Parameters:** - **filePath** (string) **(required)**: A path to a .heapsnapshot file to save the heapsnapshot to. --- ## Network ### `get_network_request` **Description:** Gets a network request by an optional reqid, if omitted returns the currently selected request in the DevTools Network panel. **Parameters:** - **reqid** (number) _(optional)_: The reqid of the network request. If omitted returns the currently selected request in the DevTools Network panel. - **requestFilePath** (string) _(optional)_: The absolute or relative path to save the request body to. If omitted, the body is returned inline. - **responseFilePath** (string) _(optional)_: The absolute or relative path to save the response body to. If omitted, the body is returned inline. --- ### `list_network_requests` **Description:** List all requests for the currently selected page since the last navigation. **Parameters:** - **includePreservedRequests** (boolean) _(optional)_: Set to true to return the preserved requests over the last 3 navigations. - **pageIdx** (integer) _(optional)_: Page number to return (0-based). When omitted, returns the first page. - **pageSize** (integer) _(optional)_: Maximum number of requests to return. When omitted, returns all requests. - **resourceTypes** (array) _(optional)_: Filter requests to only return requests of the specified resource types. When omitted or empty, returns all requests. --- ## Debugging ### `evaluate_script` **Description:** Evaluate a JavaScript function inside the currently selected page. Returns the response as JSON, so returned values have to be JSON-serializable. **Parameters:** - **function** (string) **(required)**: A JavaScript function declaration to be executed by the tool in the currently selected page. Example without arguments: `() => { return document.title }` or `async () => { return await fetch("example.com") }`. Example with arguments: `(el) => { return el.innerText; }` - **args** (array) _(optional)_: An optional list of arguments to pass to the function. --- ### `get_console_message` **Description:** Gets a console message by its ID. You can get all messages by calling [`list_console_messages`](#list_console_messages). **Parameters:** - **msgid** (number) **(required)**: The msgid of a console message on the page from the listed console messages --- ### `lighthouse_audit` **Description:** Get Lighthouse score and reports for accessibility, SEO and best practices. This excludes performance. For performance audits, run [`performance_start_trace`](#performance_start_trace) **Parameters:** - **device** (enum: "desktop", "mobile") _(optional)_: Device to [`emulate`](#emulate). - **mode** (enum: "navigation", "snapshot") _(optional)_: "navigation" reloads & audits. "snapshot" analyzes current state. - **outputDirPath** (string) _(optional)_: Directory for reports. If omitted, uses temporary files. --- ### `list_console_messages` **Description:** List all console messages for the currently selected page since the last navigation. **Parameters:** - **includePreservedMessages** (boolean) _(optional)_: Set to true to return the preserved messages over the last 3 navigations. - **pageIdx** (integer) _(optional)_: Page number to return (0-based). When omitted, returns the first page. - **pageSize** (integer) _(optional)_: Maximum number of messages to return. When omitted, returns all requests. - **types** (array) _(optional)_: Filter messages to only return messages of the specified resource types. When omitted or empty, returns all messages. --- ### `take_screenshot` **Description:** Take a screenshot of the page or element. **Parameters:** - **filePath** (string) _(optional)_: The absolute path, or a path relative to the current working directory, to save the screenshot to instead of attaching it to the response. - **format** (enum: "png", "jpeg", "webp") _(optional)_: Type of format to save the screenshot as. Default is "png" - **fullPage** (boolean) _(optional)_: If set to true takes a screenshot of the full page instead of the currently visible viewport. Incompatible with uid. - **quality** (number) _(optional)_: Compression quality for JPEG and WebP formats (0-100). Higher values mean better quality but larger file sizes. Ignored for PNG format. - **uid** (string) _(optional)_: The uid of an element on the page from the page content snapshot. If omitted takes a pages screenshot. --- ### `take_snapshot` **Description:** Take a text snapshot of the currently selected page based on the a11y tree. The snapshot lists page elements along with a unique identifier (uid). Always use the latest snapshot. Prefer taking a snapshot over taking a screenshot. The snapshot indicates the element selected in the DevTools Elements panel (if any). **Parameters:** - **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. --- ================================================ FILE: docs/troubleshooting.md ================================================ # Troubleshooting ## General tips - Run `npx chrome-devtools-mcp@latest --help` to test if the MCP server runs on your machine. - Make sure that your MCP client uses the same npm and node version as your terminal. - When configuring your MCP client, try using the `--yes` argument to `npx` to auto-accept installation prompt. - Find a specific error in the output of the `chrome-devtools-mcp` server. Usually, if your client is an IDE, logs would be in the Output pane. - Search the [GitHub repository issues and discussions](https://github.com/ChromeDevTools/chrome-devtools-mcp) for help or existing similar problems. ## Debugging Start the MCP server with debugging enabled and a log file: - `DEBUG=* npx chrome-devtools-mcp@latest --log-file=/path/to/chrome-devtools-mcp.log` Using `.mcp.json` to debug while using a client: ```json { "mcpServers": { "chrome-devtools": { "type": "stdio", "command": "npx", "args": [ "chrome-devtools-mcp@latest", "--log-file", "/path/to/chrome-devtools-mcp.log" ], "env": { "DEBUG": "*" } } } } ``` ## Specific problems ### `Error [ERR_MODULE_NOT_FOUND]: Cannot find module ...` This usually indicates either a non-supported Node version is in use or that the `npm`/`npx` cache is corrupted. Try clearing the cache, uninstalling `chrome-devtools-mcp` and installing it again. Clear the cache by running: ```sh rm -rf ~/.npm/_npx # NOTE: this might remove other installed npx executables. npm cache clean --force ``` ### `Target closed` error This indicates that the browser could not be started. Make sure that no Chrome instances are running or close them. Make sure you have the latest stable Chrome installed and that [your system is able to run Chrome](https://support.google.com/chrome/a/answer/7100626?hl=en). ### Chrome crashes on macOS when using Web Bluetooth On macOS, Chrome launched by an MCP client application (such as Claude Desktop) may crash when a Web Bluetooth prompt appears. This is caused by a macOS privacy permission violation (TCC). To resolve this, grant Bluetooth permission to the MCP client application in `System Settings > Privacy & Security > Bluetooth`. After granting permission, restart the client application and start a new MCP session. ### Remote debugging between virtual machine (VM) and host fails When attempting to connect to Chrome running on a host machine from within a virtual machine (VM), Chrome may reject the connection due to 'Host' header validation. You can bypass this restriction by creating an SSH tunnel from the VM to the host. In the VM, run: ```sh ssh -N -L 127.0.0.1:9222:127.0.0.1:9222 @ ``` Point the MCP connection inside the VM to `http://127.0.0.1:9222`. This allows DevTools to reach the host browser without triggering the Host validation error. ### Operating system sandboxes Some MCP clients allow sandboxing the MCP server using macOS Seatbelt or Linux containers. If sandboxes are enabled, `chrome-devtools-mcp` is not able to start Chrome that requires permissions to create its own sandboxes. As a workaround, either disable sandboxing for `chrome-devtools-mcp` in your MCP client or use `--browser-url` to connect to a Chrome instance that you start manually outside of the MCP client sandbox. ### WSL By default, `chrome-devtools-mcp` in WSL requires Chrome to be installed within the Linux environment. While it normally attempts to launch Chrome on the Windows side, this currently fails due to a [known WSL issue](https://github.com/microsoft/WSL/issues/14201). Ensure you are using a [Linux distribution compatible with Chrome](https://support.google.com/chrome/a/answer/7100626). Possible workarounds include: - **Install Google Chrome in WSL:** - `wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb` - `sudo dpkg -i google-chrome-stable_current_amd64.deb` - **Use Mirrored networking:** 1. Configure [Mirrored networking for WSL](https://learn.microsoft.com/en-us/windows/wsl/networking). 2. Start Chrome on the Windows side with: `chrome.exe --remote-debugging-port=9222 --user-data-dir=C:\path\to\dir` 3. Start `chrome-devtools-mcp` with: `npx chrome-devtools-mcp --browser-url http://127.0.0.1:9222` - **Use Powershell or Git Bash** instead of WSL. ### Windows 10: Error during discovery for MCP server 'chrome-devtools': MCP error -32000: Connection closed - **Solution 1** Call using `cmd` (For more info https://github.com/modelcontextprotocol/servers/issues/1082#issuecomment-2791786310) ```json "mcpServers": { "chrome-devtools": { "command": "cmd", "args": ["/c", "npx", "-y", "chrome-devtools-mcp@latest"] } } ``` > **The Key Change:** On Windows, running a Node.js package via `npx` often requires the `cmd /c` prefix to be executed correctly from within another process like VSCode's extension host. Therefore, `"command": "npx"` was replaced with `"command": "cmd"`, and the actual `npx` command was moved into the `"args"` array, preceded by `"/c"`. This fix allows Windows to interpret the command correctly and launch the server. - **Solution 2** Instead of another layer of shell you can write the absolute path to `npx`: > Note: The path below is an example. You must adjust it to match the actual location of `npx` on your machine. Depending on your setup, the file extension might be `.cmd`, `.bat`, or `.exe` rather than `.ps1`. Also, ensure you use double backslashes (`\\`) as path delimiters, as required by the JSON format. ```json "mcpServers": { "chrome-devtools": { "command": "C:\\nvm4w\\nodejs\\npx.ps1", "args": ["-y", "chrome-devtools-mcp@latest"] } } ``` ### Claude Code plugin installation fails with `Failed to clone repository` When installing `chrome-devtools-mcp` as a Claude Code plugin (either from the official marketplace or via `/plugin marketplace add`), the installation may fail with a timeout error if your environment cannot reach `github.com` on port 443 (HTTPS): ``` Failed to download/cache plugin chrome-devtools-mcp: Failed to clone repository: Cloning into '...'... fatal: unable to access 'https://github.com/ChromeDevTools/chrome-devtools-mcp.git/': Failed to connect to github.com port 443 ``` This can happen in environments with restricted outbound HTTPS connectivity, corporate firewalls, or proxy configurations that block HTTPS git operations. **Workaround 1: Use SSH instead of HTTPS** If you have SSH access to GitHub configured, you can redirect all GitHub HTTPS URLs to use SSH by running: ```sh git config --global url."git@github.com:".insteadOf "https://github.com/" ``` Then retry the plugin installation. This tells git to use your SSH key for all GitHub operations instead of HTTPS. **Workaround 2: Install via CLI instead** If the plugin marketplace approach fails, you can install `chrome-devtools-mcp` as an MCP server directly without cloning the repository: ```sh claude mcp add chrome-devtools --scope user npx chrome-devtools-mcp@latest ``` This bypasses the git clone entirely and uses npm/npx to fetch the package. Note that this method installs only the MCP server without the bundled skills. ### Connection timeouts with `--autoConnect` If you are using the `--autoConnect` flag and tools like `list_pages`, `new_page`, or `navigate_page` fail with a timeout (e.g., `ProtocolError: Network.enable timed out` or `The socket connection was closed unexpectedly`), this usually means the MCP server cannot handshake with the running Chrome instance correctly. Ensure: 1. Chrome 144+ is **already** running. 2. Remote debugging is enabled in Chrome via `chrome://inspect/#remote-debugging`. 3. You have allowed the remote debugging connection prompt in the browser. 4. There is no other MCP server or tool trying to connect to the same debugging port. ================================================ FILE: eslint.config.mjs ================================================ /** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import js from '@eslint/js'; import stylisticPlugin from '@stylistic/eslint-plugin'; import {defineConfig, globalIgnores} from 'eslint/config'; import importPlugin from 'eslint-plugin-import'; import globals from 'globals'; import tseslint from 'typescript-eslint'; import localPlugin from './scripts/eslint_rules/local-plugin.js'; export default defineConfig([ globalIgnores([ '**/node_modules', '**/build/', 'tests/tools/fixtures/', 'src/third_party/lighthouse-devtools-mcp-bundle.js', ]), importPlugin.flatConfigs.typescript, { languageOptions: { ecmaVersion: 'latest', sourceType: 'module', globals: { ...globals.node, }, parserOptions: { projectService: { allowDefaultProject: [ '.prettierrc.cjs', 'puppeteer.config.cjs', 'eslint.config.mjs', 'rollup.config.mjs', ], }, }, parser: tseslint.parser, }, plugins: { js, '@local': localPlugin, '@typescript-eslint': tseslint.plugin, '@stylistic': stylisticPlugin, }, settings: { 'import/resolver': { typescript: true, }, }, extends: ['js/recommended'], }, tseslint.configs.recommended, tseslint.configs.stylistic, { name: 'TypeScript rules', rules: { '@local/check-license': 'error', curly: ['error', 'all'], 'no-undef': 'off', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', }, ], '@typescript-eslint/no-explicit-any': [ 'error', { ignoreRestArgs: true, }, ], // This optimizes the dependency tracking for type-only files. '@typescript-eslint/consistent-type-imports': 'error', // So type-only exports get elided. '@typescript-eslint/consistent-type-exports': 'error', // Prefer interfaces over types for shape like. '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], '@typescript-eslint/array-type': [ 'error', { default: 'array-simple', }, ], '@typescript-eslint/no-floating-promises': 'error', 'import/order': [ 'error', { 'newlines-between': 'always', alphabetize: { order: 'asc', caseInsensitive: true, }, }, ], 'import/no-cycle': [ 'error', { maxDepth: Infinity, }, ], 'import/enforce-node-protocol-usage': ['error', 'always'], '@stylistic/function-call-spacing': 'error', '@stylistic/semi': 'error', 'no-restricted-imports': [ 'error', { patterns: [ { regex: '.*chrome-devtools-frontend/(?!mcp/mcp.js$).*', message: 'Import only the devtools-frontend code exported via node_modules/chrome-devtools-frontend/mcp/mcp.js', }, ], }, ], }, }, { name: 'Tests', files: ['**/*.test.ts'], rules: { // With the Node.js test runner, `describe` and `it` are technically // promises, but we don't need to await them. '@typescript-eslint/no-floating-promises': 'off', }, }, ]); ================================================ FILE: gemini-extension.json ================================================ { "name": "chrome-devtools-mcp", "version": "latest", "mcpServers": { "chrome-devtools": { "command": "npx", "args": ["chrome-devtools-mcp@latest"] } } } ================================================ FILE: package.json ================================================ { "name": "chrome-devtools-mcp", "version": "0.20.2", "description": "MCP server for Chrome DevTools", "type": "module", "bin": { "chrome-devtools-mcp": "./build/src/bin/chrome-devtools-mcp.js", "chrome-devtools": "./build/src/bin/chrome-devtools.js" }, "main": "./build/src/index.js", "scripts": { "cli:generate": "node --experimental-strip-types scripts/generate-cli.ts", "clean": "node -e \"require('fs').rmSync('build', {recursive: true, force: true})\"", "bundle": "npm run clean && npm run build && rollup -c rollup.config.mjs && node -e \"require('fs').rmSync('build/node_modules', {recursive: true, force: true})\" && node --experimental-strip-types scripts/append-lighthouse-notices.ts", "build": "tsc && node --experimental-strip-types --no-warnings=ExperimentalWarning scripts/post-build.ts", "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", "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", "test": "npm run build && node scripts/test.mjs", "test:no-build": "node scripts/test.mjs", "test:only": "npm run build && node scripts/test.mjs --test-only", "test:update-snapshots": "npm run build && node scripts/test.mjs --test-update-snapshots", "prepare": "node --experimental-strip-types scripts/prepare.ts", "verify-server-json-version": "node --experimental-strip-types scripts/verify-server-json-version.ts", "update-lighthouse": "node --experimental-strip-types scripts/update-lighthouse.ts", "verify-npm-package": "node scripts/verify-npm-package.mjs", "eval": "npm run build && node --experimental-strip-types scripts/eval_gemini.ts", "count-tokens": "node --experimental-strip-types scripts/count_tokens.ts" }, "files": [ "build/src", "LICENSE", "!*.tsbuildinfo" ], "repository": "ChromeDevTools/chrome-devtools-mcp", "author": "Google LLC", "license": "Apache-2.0", "bugs": { "url": "https://github.com/ChromeDevTools/chrome-devtools-mcp/issues" }, "homepage": "https://github.com/ChromeDevTools/chrome-devtools-mcp#readme", "mcpName": "io.github.ChromeDevTools/chrome-devtools-mcp", "devDependencies": { "@eslint/js": "^9.35.0", "@google/genai": "^1.37.0", "@modelcontextprotocol/sdk": "1.27.1", "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.3", "@stylistic/eslint-plugin": "^5.4.0", "@types/debug": "^4.1.12", "@types/filesystem": "^0.0.36", "@types/node": "^25.0.0", "@types/sinon": "^21.0.0", "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.43.0", "@typescript-eslint/parser": "^8.43.0", "chrome-devtools-frontend": "1.0.1599001", "core-js": "3.48.0", "debug": "4.4.3", "eslint": "^9.35.0", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import": "^2.32.0", "globals": "^17.0.0", "lighthouse": "13.0.3", "prettier": "^3.6.2", "puppeteer": "24.39.1", "rollup": "4.59.0", "rollup-plugin-cleanup": "^3.2.1", "rollup-plugin-license": "^3.6.0", "sinon": "^21.0.0", "tiktoken": "^1.0.22", "typescript": "^5.9.2", "typescript-eslint": "^8.43.0", "yargs": "18.0.0" }, "engines": { "node": "^20.19.0 || ^22.12.0 || >=23" } } ================================================ FILE: puppeteer.config.cjs ================================================ /** * @license * Copyright 2025 Google Inc. * SPDX-License-Identifier: Apache-2.0 */ /** * @type {import("puppeteer").Configuration} */ module.exports = { chrome: { skipDownload: false, }, ['chrome-headless-shell']: { skipDownload: true, }, firefox: { skipDownload: true, }, }; ================================================ FILE: release-please-config.json ================================================ { "changelog-sections": [ {"type": "feat", "section": "🎉 Features", "hidden": false}, {"type": "fix", "section": "🛠️ Fixes", "hidden": false}, {"type": "docs", "section": "📄 Documentation", "hidden": false}, {"type": "perf", "section": "⚡ Performance", "hidden": false}, {"type": "refactor", "section": "🏗️ Refactor", "hidden": false}, {"type": "chore", "section": "♻️ Chores", "hidden": true}, {"type": "test", "section": "♻️ Chores", "hidden": true}, {"type": "build", "section": "⚙️ Automation", "hidden": true}, {"type": "ci", "section": "⚙️ Automation", "hidden": true} ], "packages": { ".": { "extra-files": [ { "type": "generic", "path": "src/version.ts" }, { "type": "json", "path": "server.json", "jsonpath": "version" }, { "type": "json", "path": "server.json", "jsonpath": "packages[0].version" } ] } } } ================================================ FILE: rollup.config.mjs ================================================ /** * Copyright 2021 Google LLC. * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileoverview taken from {@link https://github.com/GoogleChromeLabs/chromium-bidi/blob/main/rollup.config.mjs | chromium-bidi} * and modified to specific requirement. */ import fs from 'node:fs'; import path from 'node:path'; import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; import {nodeResolve} from '@rollup/plugin-node-resolve'; import cleanup from 'rollup-plugin-cleanup'; import license from 'rollup-plugin-license'; const isProduction = process.env.NODE_ENV === 'production'; const allowedLicenses = [ 'MIT', 'Apache 2.0', 'Apache-2.0', 'BSD-3-Clause', 'BSD-2-Clause', 'ISC', '0BSD', ]; const thirdPartyDir = './build/src/third_party'; const {devDependencies = {}} = JSON.parse( fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'), ); // special case for puppeteer, from which we only bundle puppeteer-core devDependencies['puppeteer-core'] = devDependencies['puppeteer']; const aggregatedStats = { bundlesProcessed: 0, totalBundles: 0, bundledPackages: new Set(), }; const projectNodeModulesPath = path.join(process.cwd(), 'node_modules') + path.sep; function getPackageName(modulePath) { // Handle rollup's virtual module paths (paths starting with 0x00) const absolutePathStart = modulePath.indexOf(projectNodeModulesPath); if (absolutePathStart < 0) { return null; } const relativePath = modulePath.slice( projectNodeModulesPath.length + absolutePathStart, ); const segments = relativePath.split(path.sep); // handle scoped packages if (segments[0].startsWith('@') && segments[1]) { return `${segments[0]}/${segments[1]}`; } return segments[0]; } /** * @returns {import('rollup').Plugin} */ function listBundledDeps() { aggregatedStats.totalBundles++; return { name: 'gather-bundled-dependencies', generateBundle(options, bundle) { for (const chunk of Object.values(bundle)) { if (chunk.type === 'chunk' && chunk.modules) { // chunk.modules is an object where keys are the absolute file paths Object.keys(chunk.modules).forEach(modulePath => { const packageName = getPackageName(modulePath); if (packageName) { aggregatedStats.bundledPackages.add(packageName); } }); } } aggregatedStats.bundlesProcessed++; // Only write the file when the last bundle is finished if (aggregatedStats.bundlesProcessed === aggregatedStats.totalBundles) { const outputPath = path.join(thirdPartyDir, 'bundled-packages.json'); const bundledDevDeps = Object.fromEntries( Object.entries(devDependencies).filter( ([name]) => aggregatedStats.bundledPackages.has(name) || name === 'chrome-devtools-frontend' || name === 'lighthouse', ), ); fs.writeFileSync(outputPath, JSON.stringify(bundledDevDeps, null, 2)); } }, }; } const seenDependencies = new Map(); /** * @param {string} wrapperIndexName * @param {import('rollup').OutputOptions} [extraOutputOptions={}] * @param {import('rollup').ExternalOption} [external=[]] * @returns {import('rollup').RollupOptions} */ const bundleDependency = ( wrapperIndexName, extraOutputOptions = {}, external = [], ) => ({ input: path.join(thirdPartyDir, wrapperIndexName), output: { ...extraOutputOptions, file: path.join(thirdPartyDir, wrapperIndexName), sourcemap: !isProduction, format: 'esm', }, plugins: [ cleanup({ // Keep license comments. Other comments are removed due to // http://b/390559299 and // https://github.com/microsoft/TypeScript/issues/60811. comments: [/Copyright/i], }), license({ thirdParty: { allow: { test: dependency => { return allowedLicenses.includes(dependency.license); }, failOnUnlicensed: true, failOnViolation: true, }, output: { file: path.join(thirdPartyDir, 'THIRD_PARTY_NOTICES'), template(dependencies) { for (const dependency of dependencies) { const key = `${dependency.name}:${dependency.version}`; seenDependencies.set(key, dependency); } const stringifiedDependencies = Array.from( seenDependencies.values(), ).map(dependency => { let arr = []; arr.push(`Name: ${dependency.name ?? 'N/A'}`); let url = dependency.homepage ?? dependency.repository; if (url !== null && typeof url !== 'string') { url = url.url; } arr.push(`URL: ${url ?? 'N/A'}`); arr.push(`Version: ${dependency.version ?? 'N/A'}`); arr.push(`License: ${dependency.license ?? 'N/A'}`); if (dependency.licenseText !== null) { arr.push(''); arr.push(dependency.licenseText.replaceAll('\r', '')); } return arr.join('\n'); }); // Manual license handling for chrome-devtools-frontend third_party const tsConfig = JSON.parse( fs.readFileSync( path.join(process.cwd(), 'tsconfig.json'), 'utf-8', ), ); const thirdPartyDirectories = tsConfig.include.filter(location => location.includes( 'node_modules/chrome-devtools-frontend/front_end/third_party', ), ); const manualLicenses = []; // Add chrome-devtools-frontend main license const cdtfLicensePath = path.join( process.cwd(), 'node_modules/chrome-devtools-frontend/LICENSE', ); if (fs.existsSync(cdtfLicensePath)) { manualLicenses.push( [ 'Name: chrome-devtools-frontend', 'License: Apache-2.0', '', fs.readFileSync(cdtfLicensePath, 'utf-8'), ].join('\n'), ); } // Add chrome-devtools-frontend main license const lighthouseLicensePath = path.join( process.cwd(), 'node_modules/lighthouse/LICENSE', ); if (fs.existsSync(lighthouseLicensePath)) { manualLicenses.push( [ 'Name: lighthouse', 'License: Apache-2.0', '', fs.readFileSync(lighthouseLicensePath, 'utf-8'), ].join('\n'), ); } for (const thirdPartyDir of thirdPartyDirectories) { const fullPath = path.join(process.cwd(), thirdPartyDir); const licenseFile = path.join(fullPath, 'LICENSE'); if (fs.existsSync(licenseFile)) { const name = path.basename(thirdPartyDir); manualLicenses.push( [ `Name: ${name}`, `License:`, '', fs.readFileSync(licenseFile, 'utf-8').replaceAll('\r', ''), ].join('\n'), ); } } if (manualLicenses.length > 0) { stringifiedDependencies.push(...manualLicenses); } const divider = '\n\n-------------------- DEPENDENCY DIVIDER --------------------\n\n'; return stringifiedDependencies.join(divider); }, }, }, }), listBundledDeps(), commonjs(), json(), nodeResolve(), ], external, }); export default [ bundleDependency( 'index.js', { inlineDynamicImports: true, }, (source, importer, _isResolved) => { if ( source === 'yargs' && importer && importer.includes('puppeteer-core') ) { return true; } const existingExternals = [ './bidi.js', '../bidi/bidi.js', './lighthouse-devtools-mcp-bundle.js', ]; if (existingExternals.includes(source)) { return true; } return false; }, ), bundleDependency( 'devtools-formatter-worker.js', { inlineDynamicImports: true, }, (_source, _importer, _isResolved) => false, ), ]; ================================================ FILE: scripts/append-lighthouse-notices.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import fs from 'node:fs'; import path from 'node:path'; const ROOT_DIR = process.cwd(); const TARGET_DIR = path.join(ROOT_DIR, 'build/src/third_party'); const SOURCE_DIR = path.join(ROOT_DIR, 'src/third_party'); function main() { const lighthouseNotices = fs.readFileSync( path.join(SOURCE_DIR, 'LIGHTHOUSE_MCP_BUNDLE_THIRD_PARTY_NOTICES'), 'utf8', ); const bundledNotices = fs.readFileSync( path.join(TARGET_DIR, 'THIRD_PARTY_NOTICES'), 'utf8', ); fs.writeFileSync( path.join(TARGET_DIR, 'THIRD_PARTY_NOTICES'), bundledNotices + '\n\n-------------------- DEPENDENCY DIVIDER --------------------\n\n' + lighthouseNotices, ); console.log('Done.'); } main(); ================================================ FILE: scripts/count_tokens.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import {readFileSync} from 'node:fs'; import {parseArgs} from 'node:util'; import {GoogleGenAI} from '@google/genai'; const ai = new GoogleGenAI({apiKey: process.env.GEMINI_API_KEY}); const {values, positionals} = parseArgs({ options: { model: { type: 'string', default: 'gemini-2.5-flash', }, file: { type: 'string', short: 'f', }, }, allowPositionals: true, }); let contents = positionals[0]; if (values.file) { contents = readFileSync(values.file, 'utf8'); } if (!contents) { console.error('Usage: npm run count-tokens -- [-f ] []'); process.exit(1); } const response = await ai.models.countTokens({ model: values.model, contents, }); console.log(`Input: ${values.file || positionals[0]}`); console.log(`Tokens: ${response.totalTokens}`); ================================================ FILE: scripts/eslint_rules/check-license-rule.js ================================================ /** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ const currentYear = new Date().getFullYear(); const licenseHeader = ` /** * @license * Copyright ${currentYear} Google LLC * SPDX-License-Identifier: Apache-2.0 */ `; export default { name: 'check-license', meta: { type: 'layout', docs: { description: 'Validate existence of license header', }, fixable: 'code', schema: [], messages: { licenseRule: 'Add license header.', emptyLine: 'Add empty line after license header.', }, }, defaultOptions: [], create(context) { const sourceCode = context.getSourceCode(); const comments = sourceCode.getAllComments(); let insertAfter = [0, 0]; let header = null; // Check only the first 2 comments for (let index = 0; index < 2; index++) { const comment = comments[index]; if (!comment) { break; } // Shebang comments should be at the top if ( comment.type === 'Shebang' || (comment.type === 'Line' && comment.value.startsWith('#!')) ) { insertAfter = comment.range; continue; } if (comment.type === 'Block') { header = comment; break; } } return { Program(node) { if (context.getFilename().endsWith('.json')) { return; } if ( header && (header.value.includes('@license') || header.value.includes('License') || header.value.includes('Copyright')) ) { const nextToken = sourceCode.getTokenAfter(header, { includeComments: true, }); if ( nextToken && nextToken.loc.start.line === header.loc.end.line + 1 ) { context.report({ node: node, loc: header.loc, messageId: 'emptyLine', fix(fixer) { return fixer.insertTextAfter(header, '\n'); }, }); } return; } // Add header license if (!header || !header.value.includes('@license')) { context.report({ node: node, messageId: 'licenseRule', fix(fixer) { return fixer.insertTextAfterRange(insertAfter, licenseHeader); }, }); } }, }; }, }; ================================================ FILE: scripts/eslint_rules/local-plugin.js ================================================ /** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import checkLicenseRule from './check-license-rule.js'; export default {rules: {'check-license': checkLicenseRule}}; ================================================ FILE: scripts/eval_gemini.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import fs from 'node:fs'; import path from 'node:path'; import {pathToFileURL} from 'node:url'; import {parseArgs} from 'node:util'; import {GoogleGenAI, mcpToTool} from '@google/genai'; import {Client} from '@modelcontextprotocol/sdk/client/index.js'; import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js'; import {TestServer} from '../build/tests/server.js'; const ROOT_DIR = path.resolve(import.meta.dirname, '..'); const SCENARIOS_DIR = path.join(import.meta.dirname, 'eval_scenarios'); const SKILL_PATH = path.join(ROOT_DIR, 'skills', 'chrome-devtools', 'SKILL.md'); // Define schema for our test scenarios export interface CapturedFunctionCall { name: string; args: Record; } export interface TestScenario { prompt: string; maxTurns: number; expectations: (calls: CapturedFunctionCall[]) => void; htmlRoute?: { path: string; htmlContent: string; }; /** Extra CLI flags passed to the MCP server (e.g. '--experimental-page-id-routing'). */ serverArgs?: string[]; } async function loadScenario(scenarioPath: string): Promise { const module = await import(pathToFileURL(scenarioPath).href); if (!module.scenario) { throw new Error( `Scenario file ${scenarioPath} does not export a 'scenario' object.`, ); } return module.scenario; } async function runSingleScenario( scenarioPath: string, apiKey: string, server: TestServer, modelId: string, debug: boolean, includeSkill: boolean, ): Promise { const debugLog = (...args: unknown[]) => { if (debug) { console.log(...args); } }; const absolutePath = path.resolve(scenarioPath); debugLog( `\n### Running Scenario: ${path.relative(ROOT_DIR, absolutePath)} ###`, ); let client: Client | undefined; let transport: StdioClientTransport | undefined; try { const loadedScenario = await loadScenario(absolutePath); const scenario = {...loadedScenario}; // Prepend skill content if requested if (includeSkill) { if (!fs.existsSync(SKILL_PATH)) { throw new Error( `Skill file not found at ${SKILL_PATH}. Please ensure the skill file exists.`, ); } const skillContent = fs.readFileSync(SKILL_PATH, 'utf-8'); scenario.prompt = `${skillContent}\n\n---\n\n${scenario.prompt}`; } // Append random queryid to avoid caching issues and test distinct runs const randomId = Math.floor(Math.random() * 1000000); scenario.prompt = `${scenario.prompt}\nqueryid=${randomId}`; if (scenario.htmlRoute) { server.addHtmlRoute( scenario.htmlRoute.path, scenario.htmlRoute.htmlContent, ); scenario.prompt = scenario.prompt.replace( '', server.getRoute(scenario.htmlRoute.path), ); } // Path to the compiled MCP server const serverPath = path.join(ROOT_DIR, 'build/src/index.js'); if (!fs.existsSync(serverPath)) { throw new Error( `MCP server not found at ${serverPath}. Please run 'npm run build' first.`, ); } // Environment variables const env: Record = {}; Object.entries(process.env).forEach(([key, value]) => { if (value !== undefined) { env[key] = value; } }); env['CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS'] = 'true'; const args = [serverPath]; if (!debug) { args.push('--headless'); } if (scenario.serverArgs) { args.push(...scenario.serverArgs); } transport = new StdioClientTransport({ command: 'node', args, env, stderr: debug ? 'inherit' : 'ignore', }); client = new Client( {name: 'gemini-eval-client', version: '1.0.0'}, {capabilities: {}}, ); await client.connect(transport); const allCalls: CapturedFunctionCall[] = []; const originalCallTool = client.callTool.bind(client); client.callTool = async (request, schema) => { // NOTE: request.name is the original name as the MCP client sees it. // mcpToTool handles the conversion from Gemini sanitized name to original name. debugLog( `Executing tool: ${request.name} with args: ${JSON.stringify(request.arguments)}`, ); allCalls.push({ name: request.name, args: (request.arguments as Record) || {}, }); const response = await originalCallTool(request, schema); debugLog(`Tool response: ${JSON.stringify(response)}`); return response; }; const ai = new GoogleGenAI({apiKey}); debugLog(`\n--- Prompt ---\n${scenario.prompt}`); const result = await ai.models.generateContent({ model: modelId, contents: scenario.prompt, config: { tools: [mcpToTool(client)], automaticFunctionCalling: { maximumRemoteCalls: scenario.maxTurns, }, }, }); debugLog(`\n--- Response ---\n${result.text}`); debugLog('\nVerifying expectations...'); scenario.expectations(allCalls); } finally { try { await client?.close(); } catch (e) { console.error('Error closing client:', e); } } } async function main() { const apiKey = process.env.GEMINI_API_KEY; if (!apiKey) { throw new Error('GEMINI_API_KEY environment variable is required.'); } const {values, positionals} = parseArgs({ options: { model: { type: 'string', default: 'gemini-2.5-flash', }, debug: { type: 'boolean', default: false, }, repeat: { type: 'boolean', default: false, }, 'include-skill': { type: 'boolean', default: false, }, }, allowPositionals: true, }); const modelId = values.model; const debug = values.debug; const repeat = values.repeat; const includeSkill = values['include-skill']; const scenarioFiles = positionals.length > 0 ? positionals.map(p => path.resolve(p)) : fs .readdirSync(SCENARIOS_DIR) .filter(file => file.endsWith('.ts') || file.endsWith('.js')) .map(file => path.join(SCENARIOS_DIR, file)); const server = new TestServer(TestServer.randomPort()); await server.start(); let successCount = 0; let failureCount = 0; try { for (const scenarioPath of scenarioFiles) { for (let i = 1; i <= (repeat ? 3 : 1); i++) { try { if (debug) { console.log( `Running scenario: ${path.relative(ROOT_DIR, scenarioPath)} (Run ${i}/3)`, ); } await runSingleScenario( scenarioPath, apiKey, server, modelId, debug, includeSkill, ); console.log(`✔ ${path.relative(ROOT_DIR, scenarioPath)} (Run ${i})`); successCount++; } catch (e) { console.error( `✖ ${path.relative(ROOT_DIR, scenarioPath)} (Run ${i})`, ); console.error(e); failureCount++; } finally { server.restore(); } } } } finally { await server.stop(); } console.log(`\nSummary: ${successCount} passed, ${failureCount} failed`); if (failureCount > 0) { process.exit(1); } } main().catch(error => { console.error('Fatal error:', error); process.exit(1); }); ================================================ FILE: scripts/eval_scenarios/console_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Navigate to and check the console messages.', maxTurns: 2, htmlRoute: { path: '/console_test.html', htmlContent: ` `, }, expectations: calls => { assert.strictEqual(calls.length, 2); assert.ok( calls[0].name === 'navigate_page' || calls[0].name === 'new_page', 'First call should be navigation', ); assert.strictEqual( calls[1].name, 'list_console_messages', 'Second call should be list_console_messages', ); }, }; ================================================ FILE: scripts/eval_scenarios/emulation_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Emulate offline network conditions.', maxTurns: 2, expectations: calls => { assert.strictEqual(calls.length, 1); assert.strictEqual(calls[0].name, 'emulate'); assert.strictEqual(calls[0].args.networkConditions, 'Offline'); }, }; ================================================ FILE: scripts/eval_scenarios/emulation_userAgent_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Emulate iPhone 14 user agent', maxTurns: 2, expectations: calls => { assert.strictEqual(calls.length, 1); assert.strictEqual(calls[0].name, 'emulate'); assert.deepStrictEqual( calls[0].args.userAgent, 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1', ); }, }; ================================================ FILE: scripts/eval_scenarios/emulation_viewport_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import {KnownDevices} from 'puppeteer'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Emulate iPhone 14 viewport', maxTurns: 2, expectations: calls => { assert.strictEqual(calls.length, 1); assert.strictEqual(calls[0].name, 'emulate'); assert.deepStrictEqual( { ...(calls[0].args.viewport as object), // models might not send defaults. isLandscape: KnownDevices['iPhone 14'].viewport.isLandscape ?? false, }, { ...KnownDevices['iPhone 14'].viewport, height: 844, // Puppeteer is wrong about the expected height. }, ); }, }; ================================================ FILE: scripts/eval_scenarios/fix_webpage_issues_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 * * Eval scenario: user asks to fix issues with their webpage (no URL given). * When no URL is provided, the model should pick the current frontend and run * and inspect it. Verifies the MCP server is invoked and the model opens the * frontend and inspects it (snapshot, console, or network). * * Note: Tools like performance_start_trace, take_snapshot, list_console_messages, * and list_network_requests do not require a URL in the prompt—they operate on * the currently selected page. Only navigate_page/new_page need a URL to open * a page; the eval runner injects the test URL when htmlRoute is set. */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; const INSPECTION_TOOLS = [ 'take_snapshot', 'list_console_messages', 'list_network_requests', ]; export const scenario: TestScenario = { prompt: 'Can you fix issues with my webpage?', maxTurns: 4, htmlRoute: { path: '/fix_issues_test.html', htmlContent: `

Test Page

Some content

`, }, expectations: calls => { const NAVIGATION_TOOLS = ['navigate_page', 'new_page']; assert.ok( calls.length >= 2, 'Expected at least navigation and one inspection', ); const navigationIndex = calls.findIndex(c => NAVIGATION_TOOLS.includes(c.name), ); assert.ok( navigationIndex !== -1, `Expected a navigation call (${NAVIGATION_TOOLS.join(' or ')}), got: ${calls.map(c => c.name).join(', ')}`, ); const afterNavigation = calls.slice(navigationIndex + 1); const inspectionCalls = afterNavigation.filter(c => INSPECTION_TOOLS.includes(c.name), ); assert.ok( inspectionCalls.length >= 1, `Expected at least one inspection tool (${INSPECTION_TOOLS.join(', ')}) after navigation, got: ${calls.map(c => c.name).join(', ')}`, ); }, }; ================================================ FILE: scripts/eval_scenarios/frontend_snapshot_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 * * Eval scenario using "website"/"webpage" wording to verify the model invokes * the right tools when users ask to open a site and read its content. */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Open the website at and tell me what content is on the page.', maxTurns: 3, htmlRoute: { path: '/frontend_snapshot.html', htmlContent: '

Frontend Test

This is a test webpage.

', }, expectations: calls => { assert.strictEqual(calls.length, 2); assert.ok( calls[0].name === 'navigate_page' || calls[0].name === 'new_page', 'First call should be navigation', ); assert.strictEqual( calls[1].name, 'take_snapshot', 'Second call should be take_snapshot to read page content', ); }, }; ================================================ FILE: scripts/eval_scenarios/input_parallel_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Go to , fill the input with "hello world" and click the button five times in parallel.', maxTurns: 10, htmlRoute: { path: '/input_test.html', htmlContent: ` `, }, expectations: calls => { assert.strictEqual(calls.length, 8); assert.ok( calls[0].name === 'navigate_page' || calls[0].name === 'new_page', ); assert.ok(calls[1].name === 'take_snapshot'); assert.ok(calls[2].name === 'fill'); for (let i = 3; i < 8; i++) { assert.ok(calls[i].name === 'click'); assert.strictEqual(Boolean(calls[i].args.includeSnapshot), false); } }, }; ================================================ FILE: scripts/eval_scenarios/input_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Go to , fill the input with "hello world" and click the button.', maxTurns: 4, htmlRoute: { path: '/input_test.html', htmlContent: ` `, }, expectations: calls => { assert.strictEqual(calls.length, 4); assert.ok( calls[0].name === 'navigate_page' || calls[0].name === 'new_page', ); assert.ok(calls[1].name === 'take_snapshot'); assert.ok(calls[2].name === 'fill'); assert.ok(calls[3].name === 'click'); }, }; ================================================ FILE: scripts/eval_scenarios/isolated_context_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Create a new page in an isolated context called contextB. Take a screenshot there.', maxTurns: 3, htmlRoute: { path: '/test.html', htmlContent: `

test

`, }, expectations: calls => { console.log(JSON.stringify(calls, null, 2)); assert.strictEqual(calls.length, 2); assert.ok(calls[0].name === 'new_page', 'First call should be navigation'); assert.deepStrictEqual(calls[0].args.isolatedContext, 'contextB'); assert.ok( calls[1].name === 'take_screenshot', 'Second call should be a screenshot', ); }, }; ================================================ FILE: scripts/eval_scenarios/lighthouse_a11y_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Check a11y issues on the current page', maxTurns: 1, expectations: calls => { assert.strictEqual(calls.length, 1); assert.ok( calls[0].name === 'lighthouse_audit', 'First call should be lighthouse_audit', ); }, }; ================================================ FILE: scripts/eval_scenarios/lighthouse_best_practices_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Check for best practices on the current page', maxTurns: 1, expectations: calls => { assert.strictEqual(calls.length, 1); assert.ok( calls[0].name === 'lighthouse_audit', 'First call should be lighthouse_audit', ); }, }; ================================================ FILE: scripts/eval_scenarios/navigation_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Navigate to https://developers.chrome.com and tell me if it worked.', maxTurns: 1, expectations: calls => { assert.deepStrictEqual(calls, [ { name: 'navigate_page', args: {url: 'https://developers.chrome.com'}, }, ]); }, }; ================================================ FILE: scripts/eval_scenarios/network_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Navigate to and list all network requests.', maxTurns: 2, htmlRoute: { path: '/network_test.html', htmlContent: `

Network Test

`, }, expectations: calls => { assert.strictEqual(calls.length, 2); assert.ok( calls[0].name === 'navigate_page' || calls[0].name === 'new_page', 'First call should be navigation', ); assert.strictEqual( calls[1].name, 'list_network_requests', 'Second call should be list_network_requests', ); }, }; ================================================ FILE: scripts/eval_scenarios/page_focus_keyboard_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { serverArgs: ['--experimental-page-id-routing'], prompt: `Open two pages in the same isolated context "session": - Page 1 at data:text/html, - Page 2 at data:text/html,

Other

Now use the press_key tool to type "a" on Page 1 without selecting it first. You must use press_key, not fill or type_text. If you encounter any errors, recover from them.`, maxTurns: 10, expectations: calls => { // Should open 2 pages in the same context. const newPages = calls.filter(c => c.name === 'new_page'); assert.strictEqual(newPages.length, 2, 'Should open 2 pages'); assert.strictEqual(newPages[0].args.isolatedContext, 'session'); assert.strictEqual(newPages[1].args.isolatedContext, 'session'); // Should attempt press_key at least once. const pressKeys = calls.filter(c => c.name === 'press_key'); assert.ok(pressKeys.length >= 1, 'Should attempt press_key at least once'); const selectPages = calls.filter(c => c.name === 'select_page'); if (selectPages.length > 0) { const firstPressKeyIndex = calls.indexOf(pressKeys[0]); const firstSelectPageIndex = calls.indexOf(selectPages[0]); if (firstPressKeyIndex < firstSelectPageIndex) { // Error path: press_key was attempted first and failed. // Verify recovery: must have a second press_key after select_page. assert.ok( pressKeys.length >= 2, 'Should retry press_key after error recovery', ); const lastPressKeyIndex = calls.lastIndexOf(pressKeys.at(-1)!); assert.ok( firstSelectPageIndex < lastPressKeyIndex, 'select_page should precede the successful press_key', ); } else { // Proactive path: model selected page first. assert.ok( firstSelectPageIndex < firstPressKeyIndex, 'select_page should precede press_key', ); } } // If no select_page was called, the model found another recovery path. // This is acceptable as long as press_key was attempted. }, }; ================================================ FILE: scripts/eval_scenarios/page_id_routing_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { serverArgs: ['--experimental-page-id-routing'], prompt: `Open two new pages in isolated contexts: - Page A (isolatedContext "contextA") at data:text/html, - Page B (isolatedContext "contextB") at data:text/html, Then take a snapshot of Page A, take a snapshot of Page B, and then click the button on Page A.`, maxTurns: 12, expectations: calls => { // Should have 2 new_page calls with isolatedContext. const newPages = calls.filter(c => c.name === 'new_page'); assert.strictEqual(newPages.length, 2, 'Should open 2 pages'); for (const np of newPages) { assert.strictEqual( typeof np.args.isolatedContext, 'string', 'new_page should use isolatedContext', ); } // Should have at least 2 take_snapshot calls (one per page). // The model may use pageId directly or select_page before each snapshot. const snapshots = calls.filter(c => c.name === 'take_snapshot'); assert.ok(snapshots.length >= 2, 'Should take at least 2 snapshots'); // Should have a click call (resolving uid from Page A's snapshot // even though Page B was snapshotted after). const clicks = calls.filter(c => c.name === 'click'); assert.ok(clicks.length >= 1, 'Should click the button on Page A'); }, }; ================================================ FILE: scripts/eval_scenarios/performance_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Check the performance of https://developers.chrome.com', maxTurns: 2, expectations: calls => { assert.strictEqual(calls.length, 2); assert.ok( calls[0].name === 'navigate_page' || calls[0].name === 'new_page', ); assert.ok(calls[1].name === 'performance_start_trace'); }, }; ================================================ FILE: scripts/eval_scenarios/select_page_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Open new page and then open new page https://developers.chrome.com. Select the page.', maxTurns: 3, htmlRoute: { path: '/test.html', htmlContent: `

test

`, }, expectations: calls => { assert.strictEqual(calls.length, 3); assert.ok(calls[0].name === 'new_page', 'First call should be navigation'); assert.ok(calls[1].name === 'new_page', 'Second call should be navigation'); assert.ok( calls[2].name === 'select_page', 'Third call should be select_page', ); assert.strictEqual( calls[2].args.pageId, 2, 'PageId has to be set to 2. about:blank is 1, is 2, https://developers.chrome.com is 3.', ); assert.strictEqual( calls[2].args.bringToFront, undefined, 'bringToFront should use the default value.', ); }, }; ================================================ FILE: scripts/eval_scenarios/snapshot_test.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import assert from 'node:assert'; import type {TestScenario} from '../eval_gemini.ts'; export const scenario: TestScenario = { prompt: 'Read the content of ', maxTurns: 3, htmlRoute: { path: '/test.html', htmlContent: '

Hello World

This is a test.

', }, expectations: calls => { assert.strictEqual(calls.length, 2); assert.ok( calls[0].name === 'navigate_page' || calls[0].name === 'new_page', ); assert.ok(calls[1].name === 'take_snapshot'); }, }; ================================================ FILE: scripts/generate-cli.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import fs from 'node:fs'; import path from 'node:path'; import {Client} from '@modelcontextprotocol/sdk/client/index.js'; import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js'; import {parseArguments} from '../build/src/bin/chrome-devtools-mcp-cli-options.js'; import {labels} from '../build/src/tools/categories.js'; import {createTools} from '../build/src/tools/tools.js'; const OUTPUT_PATH = path.join( import.meta.dirname, '../src/bin/cliDefinitions.ts', ); async function fetchTools() { console.log('Connecting to chrome-devtools-mcp to fetch tools...'); // Use the local build of the server const serverPath = path.join( import.meta.dirname, '../build/src/bin/chrome-devtools-mcp.js', ); const transport = new StdioClientTransport({ command: 'node', args: [serverPath], env: {...process.env, CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: 'true'}, }); const client = new Client( { name: 'chrome-devtools-cli-generator', version: '0.1.0', }, { capabilities: {}, }, ); await client.connect(transport); try { const toolsResponse = await client.listTools(); if (!toolsResponse.tools?.length) { throw new Error(`No tools were fetched`); } const tools = toolsResponse.tools || []; console.log(`Fetched ${tools.length} tools`); return tools; } finally { await client.close(); } } interface CliOption { name: string; type: string; description: string; required: boolean; default?: unknown; enum?: unknown[]; } interface JsonSchema { type?: string | string[]; description?: string; properties?: Record; required?: string[]; default?: unknown; enum?: unknown[]; } function schemaToCLIOptions(schema: JsonSchema): CliOption[] { if (!schema || !schema.properties) { return []; } const required = schema.required || []; const properties = schema.properties; return Object.entries(properties).map(([name, prop]) => { const isRequired = required.includes(name); const description = prop.description || ''; if (typeof prop.type !== 'string') { throw new Error( `Property ${name} has a complex type not supported by CLI.`, ); } return { name, type: prop.type, description, required: isRequired, default: prop.default, enum: prop.enum, }; }); } async function generateCli() { const tools = await fetchTools(); // Sort tools by name const sortedTools = tools .sort((a, b) => a.name.localeCompare(b.name)) .filter(tool => { // Skipping fill_form because it is not relevant in shell scripts // and CLI does not handle array/JSON args well. if (tool.name === 'fill_form') { return false; } // Skipping wait_for because CLI does not handle array/JSON args well // and shell scripts have many mechanisms for waiting. if (tool.name === 'wait_for') { return false; } return true; }); const staticTools = createTools(parseArguments()); const toolNameToCategory = new Map(); for (const tool of staticTools) { toolNameToCategory.set( tool.name, labels[tool.annotations.category as keyof typeof labels], ); } const commands: Record< string, {description: string; category: string; args: Record} > = {}; for (const tool of sortedTools) { const options = schemaToCLIOptions(tool.inputSchema); const args: Record = {}; for (const opt of options) { args[opt.name] = opt; } const category = toolNameToCategory.get(tool.name); if (!category) { throw new Error(`Tool ${tool.name} has no category.`); } if (!tool.description) { throw new Error(`Tool ${tool.name} is missing descripttion`); } commands[tool.name] = { description: tool.description, category, args, }; } const lines: string[] = []; lines.push(`/** * @license * Copyright ${new Date().getFullYear()} Google LLC * SPDX-License-Identifier: Apache-2.0 */ // NOTE: do not edit manually. Auto-generated by 'npm run cli:generate'. export interface ArgDef { name: string; type: string; description: string; required: boolean; default?: string | number | boolean; enum?: ReadonlyArray; } export type Commands = Record< string, { description: string; category: string; args: Record } >; export const commands: Commands = ${JSON.stringify(commands, null, 2)} as const; `); fs.mkdirSync(path.dirname(OUTPUT_PATH), {recursive: true}); fs.writeFileSync(OUTPUT_PATH, lines.join('')); console.log(`Generated CLI at ${OUTPUT_PATH}`); } generateCli().catch(err => { console.error('Error during generation:', err); process.exit(1); }); ================================================ FILE: scripts/generate-docs.ts ================================================ /** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import fs from 'node:fs'; import {Client} from '@modelcontextprotocol/sdk/client/index.js'; import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js'; import type {Tool} from '@modelcontextprotocol/sdk/types.js'; import {get_encoding} from 'tiktoken'; import {cliOptions} from '../build/src/bin/chrome-devtools-mcp-cli-options.js'; import type {ParsedArguments} from '../build/src/bin/chrome-devtools-mcp-cli-options.js'; import {ToolCategory, labels} from '../build/src/tools/categories.js'; import {createTools} from '../build/src/tools/tools.js'; const OUTPUT_PATH = './docs/tool-reference.md'; const SLIM_OUTPUT_PATH = './docs/slim-tool-reference.md'; const README_PATH = './README.md'; async function measureServer(args: string[]) { // 1. Connect to your actual MCP server const transport = new StdioClientTransport({ command: 'node', args: ['./build/src/bin/chrome-devtools-mcp.js', ...args], // Point to your built MCP server }); const client = new Client( {name: 'measurer', version: '1.0.0'}, {capabilities: {}}, ); await client.connect(transport); // 2. Fetch all tools const toolsList = await client.listTools(); // 3. Serialize exactly how an LLM would see it (JSON) const jsonString = JSON.stringify(toolsList.tools, null, 2); // 4. Count tokens (using cl100k_base which is standard for GPT-4/Claude-3.5 approximation) const enc = get_encoding('cl100k_base'); const tokenCount = enc.encode(jsonString).length; console.log(`--- Measurement Results ---`); console.log(`Total Tools: ${toolsList.tools.length}`); console.log(`JSON Character Count: ${jsonString.length}`); console.log(`Estimated Token Count: ~${tokenCount}`); // Clean up enc.free(); await client.close(); return { tokenCount, }; } // Extend the MCP Tool type to include our annotations interface ToolWithAnnotations extends Tool { annotations?: { title?: string; category?: typeof ToolCategory; conditions?: string[]; }; } interface ZodCheck { kind: string; } interface ZodDef { typeName: string; checks?: ZodCheck[]; values?: string[]; type?: ZodSchema; innerType?: ZodSchema; schema?: ZodSchema; defaultValue?: () => unknown; } interface ZodSchema { _def: ZodDef; description?: string; } interface TypeInfo { type: string; enum?: string[]; items?: TypeInfo; description?: string; default?: unknown; } function escapeHtmlTags(text: string): string { return text .replace(/&(?![a-zA-Z]+;)/g, '&') .replace(/<([a-zA-Z][^>]*)>/g, '<$1>'); } function addCrossLinks(text: string, tools: ToolWithAnnotations[]): string { let result = text; // Create a set of all tool names for efficient lookup const toolNames = new Set(tools.map(tool => tool.name)); // Sort tool names by length (descending) to match longer names first const sortedToolNames = Array.from(toolNames).sort( (a, b) => b.length - a.length, ); for (const toolName of sortedToolNames) { // Create regex to match tool name (case insensitive, word boundaries) const regex = new RegExp(`\\b${toolName}\\b`, 'gi'); result = result.replace(regex, match => { // Only create link if the match isn't already inside a link if (result.indexOf(`[${match}]`) !== -1) { return match; // Already linked } const anchorLink = toolName.toLowerCase(); return `[\`${match}\`](#${anchorLink})`; }); } return result; } function generateToolsTOC( categories: Record, sortedCategories: string[], ): string { let toc = ''; for (const category of sortedCategories) { const categoryTools = categories[category]; const categoryName = labels[category]; toc += `- **${categoryName}** (${categoryTools.length} tools)\n`; // Sort tools within category for TOC categoryTools.sort((a: Tool, b: Tool) => a.name.localeCompare(b.name)); for (const tool of categoryTools) { const anchorLink = tool.name.toLowerCase(); toc += ` - [\`${tool.name}\`](docs/tool-reference.md#${anchorLink})\n`; } } return toc; } function updateReadmeWithToolsTOC(toolsTOC: string): void { const readmeContent = fs.readFileSync(README_PATH, 'utf8'); const beginMarker = ''; const endMarker = ''; const beginIndex = readmeContent.indexOf(beginMarker); const endIndex = readmeContent.indexOf(endMarker); if (beginIndex === -1 || endIndex === -1) { console.warn('Could not find auto-generated tools markers in README.md'); return; } const before = readmeContent.substring(0, beginIndex + beginMarker.length); const after = readmeContent.substring(endIndex); const updatedContent = before + '\n\n' + toolsTOC + '\n' + after; fs.writeFileSync(README_PATH, updatedContent); console.log('Updated README.md with tools table of contents'); } function generateConfigOptionsMarkdown(): string { let markdown = ''; for (const [optionName, optionConfig] of Object.entries(cliOptions)) { // Skip hidden options if (optionConfig.hidden) { continue; } const aliasText = optionConfig.alias ? `, \`-${optionConfig.alias}\`` : ''; const description = optionConfig.description || optionConfig.describe || ''; // Convert camelCase to dash-case const dashCaseName = optionName .replace(/([a-z])([A-Z])/g, '$1-$2') .toLowerCase(); const nameDisplay = dashCaseName !== optionName ? `\`--${optionName}\`/ \`--${dashCaseName}\`` : `\`--${optionName}\``; // Start with option name and description markdown += `- **${nameDisplay}${aliasText}**\n`; markdown += ` ${description}\n`; // Add type information markdown += ` - **Type:** ${optionConfig.type}\n`; // Add choices if available if (optionConfig.choices) { markdown += ` - **Choices:** ${optionConfig.choices.map(c => `\`${c}\``).join(', ')}\n`; } // Add default if available if (optionConfig.default !== undefined) { markdown += ` - **Default:** \`${optionConfig.default}\`\n`; } markdown += '\n'; } return markdown.trim(); } function updateReadmeWithOptionsMarkdown(optionsMarkdown: string): void { const readmeContent = fs.readFileSync(README_PATH, 'utf8'); const beginMarker = ''; const endMarker = ''; const beginIndex = readmeContent.indexOf(beginMarker); const endIndex = readmeContent.indexOf(endMarker); if (beginIndex === -1 || endIndex === -1) { console.warn('Could not find auto-generated options markers in README.md'); return; } const before = readmeContent.substring(0, beginIndex + beginMarker.length); const after = readmeContent.substring(endIndex); const updatedContent = before + '\n\n' + optionsMarkdown + '\n\n' + after; fs.writeFileSync(README_PATH, updatedContent); console.log('Updated README.md with options markdown'); } // Helper to convert Zod schema to JSON schema-like object for docs function getZodTypeInfo(schema: ZodSchema): TypeInfo { let description = schema.description; let def = schema._def; let defaultValue: unknown; // Unwrap optional/default/effects while ( def.typeName === 'ZodOptional' || def.typeName === 'ZodDefault' || def.typeName === 'ZodEffects' ) { if (def.typeName === 'ZodDefault' && def.defaultValue) { defaultValue = def.defaultValue(); } const next = def.innerType || def.schema; if (!next) { break; } schema = next; def = schema._def; if (!description && schema.description) { description = schema.description; } } const result: TypeInfo = {type: 'unknown'}; if (description) { result.description = description; } if (defaultValue !== undefined) { result.default = defaultValue; } switch (def.typeName) { case 'ZodString': result.type = 'string'; break; case 'ZodNumber': result.type = def.checks?.some((c: ZodCheck) => c.kind === 'int') ? 'integer' : 'number'; break; case 'ZodBoolean': result.type = 'boolean'; break; case 'ZodEnum': result.type = 'string'; result.enum = def.values; break; case 'ZodArray': result.type = 'array'; if (def.type) { result.items = getZodTypeInfo(def.type); } break; default: result.type = 'unknown'; } return result; } function isRequired(schema: ZodSchema): boolean { let def = schema._def; while (def.typeName === 'ZodEffects') { if (!def.schema) { break; } schema = def.schema; def = schema._def; } return def.typeName !== 'ZodOptional' && def.typeName !== 'ZodDefault'; } async function generateReference( title: string, outputPath: string, toolsWithAnnotations: ToolWithAnnotations[], categories: Record, sortedCategories: string[], serverArgs: string[], ) { console.log(`Found ${toolsWithAnnotations.length} tools`); // Generate markdown documentation let markdown = ` # ${title} (~${(await measureServer(serverArgs)).tokenCount} cl100k_base tokens) `; // Generate table of contents for (const category of sortedCategories) { const categoryTools = categories[category]; const categoryName = labels[category]; const anchorName = categoryName.toLowerCase().replace(/\s+/g, '-'); markdown += `- **[${categoryName}](#${anchorName})** (${categoryTools.length} tools)\n`; // Sort tools within category for TOC categoryTools.sort((a: Tool, b: Tool) => a.name.localeCompare(b.name)); for (const tool of categoryTools) { // Generate proper markdown anchor link: backticks are removed, keep underscores, lowercase const anchorLink = tool.name.toLowerCase(); markdown += ` - [\`${tool.name}\`](#${anchorLink})\n`; } } markdown += '\n'; for (const category of sortedCategories) { const categoryTools = categories[category]; const categoryName = labels[category]; markdown += `## ${categoryName}\n\n`; // Sort tools within category categoryTools.sort((a: Tool, b: Tool) => a.name.localeCompare(b.name)); for (const tool of categoryTools) { markdown += `### \`${tool.name}\`\n\n`; if (tool.description) { // Escape HTML tags but preserve JS function syntax let escapedDescription = escapeHtmlTags(tool.description); // Add cross-links to mentioned tools escapedDescription = addCrossLinks( escapedDescription, toolsWithAnnotations, ); markdown += `**Description:** ${escapedDescription}\n\n`; } // Handle input schema if ( tool.inputSchema && tool.inputSchema.properties && Object.keys(tool.inputSchema.properties).length > 0 ) { const properties = tool.inputSchema.properties; const required = tool.inputSchema.required || []; markdown += '**Parameters:**\n\n'; const propertyNames = Object.keys(properties).sort((a, b) => { const aRequired = required.includes(a); const bRequired = required.includes(b); if (aRequired && !bRequired) { return -1; } if (!aRequired && bRequired) { return 1; } return a.localeCompare(b); }); for (const propName of propertyNames) { const prop = properties[propName] as TypeInfo; const isRequired = required.includes(propName); const requiredText = isRequired ? ' **(required)**' : ' _(optional)_'; let typeInfo = prop.type || 'unknown'; if (prop.enum) { typeInfo = `enum: ${prop.enum.map((v: string) => `"${v}"`).join(', ')}`; } markdown += `- **${propName}** (${typeInfo})${requiredText}`; if (prop.description) { let escapedParamDesc = escapeHtmlTags(prop.description); // Add cross-links to mentioned tools escapedParamDesc = addCrossLinks( escapedParamDesc, toolsWithAnnotations, ); markdown += `: ${escapedParamDesc}`; } markdown += '\n'; } markdown += '\n'; } else { markdown += '**Parameters:** None\n\n'; } markdown += '---\n\n'; } } // Write the documentation to file fs.writeFileSync(outputPath, markdown.trim() + '\n'); console.log( `Generated documentation for ${toolsWithAnnotations.length} tools in ${outputPath}`, ); } // eslint-disable-next-line @typescript-eslint/no-explicit-any function getToolsAndCategories(tools: any) { // Convert ToolDefinitions to ToolWithAnnotations const toolsWithAnnotations: ToolWithAnnotations[] = tools .filter(tool => { if (!tool.annotations.conditions) { return true; } // Only include unconditional tools. return tool.annotations.conditions.length === 0; }) .map(tool => { const properties: Record = {}; const required: string[] = []; for (const [key, schema] of Object.entries( tool.schema as unknown as Record, )) { const info = getZodTypeInfo(schema); properties[key] = info; if (isRequired(schema)) { required.push(key); } } return { name: tool.name, description: tool.description, inputSchema: { type: 'object', properties, required, }, annotations: tool.annotations, }; }); // Group tools by category (based on annotations) const categories: Record = {}; toolsWithAnnotations.forEach((tool: ToolWithAnnotations) => { const category = tool.annotations?.category || 'Uncategorized'; if (!categories[category]) { categories[category] = []; } categories[category].push(tool); }); // Sort categories using the enum order const categoryOrder = Object.values(ToolCategory); const sortedCategories = Object.keys(categories).sort((a, b) => { const aIndex = categoryOrder.indexOf(a); const bIndex = categoryOrder.indexOf(b); // Put known categories first, unknown categories last if (aIndex === -1 && bIndex === -1) { return a.localeCompare(b); } if (aIndex === -1) { return 1; } if (bIndex === -1) { return -1; } return aIndex - bIndex; }); return {toolsWithAnnotations, categories, sortedCategories}; } async function generateToolDocumentation(): Promise { try { console.log('Generating tool documentation from definitions...'); { const {toolsWithAnnotations, categories, sortedCategories} = getToolsAndCategories(createTools({slim: false} as ParsedArguments)); await generateReference( 'Chrome DevTools MCP Tool Reference', OUTPUT_PATH, toolsWithAnnotations, categories, sortedCategories, [], ); // Generate tools TOC and update README const toolsTOC = generateToolsTOC(categories, sortedCategories); updateReadmeWithToolsTOC(toolsTOC); } { const {toolsWithAnnotations, categories, sortedCategories} = getToolsAndCategories(createTools({slim: true} as ParsedArguments)); await generateReference( 'Chrome DevTools MCP Slim Tool Reference', SLIM_OUTPUT_PATH, toolsWithAnnotations, categories, sortedCategories, ['--slim'], ); } // Generate and update configuration options const optionsMarkdown = generateConfigOptionsMarkdown(); updateReadmeWithOptionsMarkdown(optionsMarkdown); process.exit(0); } catch (error) { console.error('Error generating documentation:', error); process.exit(1); } } // Run the documentation generator generateToolDocumentation().catch(console.error); ================================================ FILE: scripts/post-build.ts ================================================ /** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import * as fs from 'node:fs'; import * as path from 'node:path'; const BUILD_DIR = path.join(process.cwd(), 'build'); /** * Writes content to a file. * @param filePath The path to the file. * @param content The content to write. */ function writeFile(filePath: string, content: string): void { fs.writeFileSync(filePath, content, 'utf-8'); } function main(): void { const devtoolsThirdPartyPath = 'node_modules/chrome-devtools-frontend/front_end/third_party'; const devtoolsFrontEndCorePath = 'node_modules/chrome-devtools-frontend/front_end/core'; // Create i18n mock const i18nDir = path.join(BUILD_DIR, devtoolsFrontEndCorePath, 'i18n'); const localesFile = path.join(i18nDir, 'locales.js'); const localesContent = ` export const LOCALES = [ 'en-US', ]; export const BUNDLED_LOCALES = [ 'en-US', ]; export const DEFAULT_LOCALE = 'en-US'; export const REMOTE_FETCH_PATTERN = '@HOST@/remote/serve_file/@VERSION@/core/i18n/locales/@LOCALE@.json'; export const LOCAL_FETCH_PATTERN = './locales/@LOCALE@.json';`; writeFile(localesFile, localesContent); // Create codemirror.next mock. const codeMirrorDir = path.join( BUILD_DIR, devtoolsThirdPartyPath, 'codemirror.next', ); fs.mkdirSync(codeMirrorDir, {recursive: true}); const codeMirrorFile = path.join(codeMirrorDir, 'codemirror.next.js'); const codeMirrorContent = `export default {}`; writeFile(codeMirrorFile, codeMirrorContent); // Create root mock const rootDir = path.join(BUILD_DIR, devtoolsFrontEndCorePath, 'root'); fs.mkdirSync(rootDir, {recursive: true}); const runtimeFile = path.join(rootDir, 'Runtime.js'); const runtimeContent = ` export function getChromeVersion() { return ''; }; export const hostConfig = {}; export const Runtime = { isDescriptorEnabled: () => true, queryParam: () => null, } export const experiments = { isEnabled: () => false, } export const ExperimentName = { ALL: '*', CAPTURE_NODE_CREATION_STACKS: 'capture-node-creation-stacks', LIVE_HEAP_PROFILE: 'live-heap-profile', PROTOCOL_MONITOR: 'protocol-monitor', SAMPLING_HEAP_PROFILER_TIMELINE: 'sampling-heap-profiler-timeline', SHOW_OPTION_TO_EXPOSE_INTERNALS_IN_HEAP_SNAPSHOT: 'show-option-to-expose-internals-in-heap-snapshot', TIMELINE_INVALIDATION_TRACKING: 'timeline-invalidation-tracking', TIMELINE_SHOW_ALL_EVENTS: 'timeline-show-all-events', TIMELINE_V8_RUNTIME_CALL_STATS: 'timeline-v8-runtime-call-stats', APCA: 'apca', FONT_EDITOR: 'font-editor', FULL_ACCESSIBILITY_TREE: 'full-accessibility-tree', CONTRAST_ISSUES: 'contrast-issues', EXPERIMENTAL_COOKIE_FEATURES: 'experimental-cookie-features', INSTRUMENTATION_BREAKPOINTS: 'instrumentation-breakpoints', AUTHORED_DEPLOYED_GROUPING: 'authored-deployed-grouping', JUST_MY_CODE: 'just-my-code', USE_SOURCE_MAP_SCOPES: 'use-source-map-scopes', TIMELINE_SHOW_POST_MESSAGE_EVENTS: 'timeline-show-postmessage-events', TIMELINE_DEBUG_MODE: 'timeline-debug-mode', } `; writeFile(runtimeFile, runtimeContent); copyDevToolsDescriptionFiles(); } function copyDevToolsDescriptionFiles() { const devtoolsIssuesDescriptionPath = 'node_modules/chrome-devtools-frontend/front_end/models/issues_manager/descriptions'; const sourceDir = path.join(process.cwd(), devtoolsIssuesDescriptionPath); const destDir = path.join( BUILD_DIR, 'src', 'third_party', 'issue-descriptions', ); fs.cpSync(sourceDir, destDir, {recursive: true}); } main(); ================================================ FILE: scripts/prepare.ts ================================================ /** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import {readFileSync, writeFileSync} from 'node:fs'; import {rm} from 'node:fs/promises'; import {resolve} from 'node:path'; const projectRoot = process.cwd(); const filesToRemove = [ 'node_modules/chrome-devtools-frontend/package.json', 'node_modules/chrome-devtools-frontend/front_end/models/trace/lantern/testing', 'node_modules/chrome-devtools-frontend/front_end/third_party/intl-messageformat/package/package.json', ]; /** * Removes the conflicting global HTMLElementEventMap declaration from * @paulirish/trace_engine/models/trace/ModelImpl.d.ts to avoid TS2717 error * when both chrome-devtools-frontend and @paulirish/trace_engine declare * the same property. */ function removeConflictingGlobalDeclaration(): void { const filePath = resolve( projectRoot, 'node_modules/@paulirish/trace_engine/models/trace/ModelImpl.d.ts', ); console.log( 'Removing conflicting global declaration from @paulirish/trace_engine...', ); const content = readFileSync(filePath, 'utf-8'); // Remove the declare global block using regex // Matches: declare global { ... interface HTMLElementEventMap { ... } ... } const newContent = content.replace( /declare global\s*\{\s*interface HTMLElementEventMap\s*\{[^}]*\[ModelUpdateEvent\.eventName\]:\s*ModelUpdateEvent;\s*\}\s*\}/s, '', ); writeFileSync(filePath, newContent, 'utf-8'); console.log('Successfully removed conflicting global declaration.'); } async function main() { console.log('Running prepare script to clean up chrome-devtools-frontend...'); for (const file of filesToRemove) { const fullPath = resolve(projectRoot, file); console.log(`Removing: ${file}`); try { await rm(fullPath, {recursive: true, force: true}); } catch (error) { console.error(`Failed to remove ${file}:`, error); process.exit(1); } } console.log('Clean up of chrome-devtools-frontend complete.'); removeConflictingGlobalDeclaration(); } void main(); ================================================ FILE: scripts/test.mjs ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ // Note: can be converted to ts file once node 20 support is dropped. // Node 20 does not support --experimental-strip-types flag. import {spawn, execSync} from 'node:child_process'; import path from 'node:path'; import process from 'node:process'; const args = process.argv.slice(2); const userArgs = args.filter(arg => !arg.startsWith('-')); const flags = args.filter(arg => arg.startsWith('-')); const files = []; let shouldRetry = false; const retryIndex = flags.indexOf('--retry'); if (retryIndex !== -1) { shouldRetry = true; flags.splice(retryIndex, 1); } if (userArgs.length > 0) { for (const arg of userArgs) { // Map .ts files to build/ .js files let testPath = arg; if (testPath.endsWith('.ts')) { testPath = testPath.replace(/\.ts$/, '.js'); if (!testPath.startsWith('build/')) { testPath = path.join('build', testPath); } } files.push(testPath); } } else { const isNode20 = process.version.startsWith('v20.'); if (isNode20) { files.push('build/tests'); } else { files.push('build/tests/**/*.test.js'); } } const nodeArgs = [ '--import', './build/tests/setup.js', '--no-warnings=ExperimentalWarning', '--test-reporter', (process.env['NODE_TEST_REPORTER'] ?? process.env['CI']) ? 'spec' : 'dot', '--test-force-exit', '--test-concurrency=1', '--test', '--test-timeout=60000', ...flags, ...files, ]; function installChrome(version) { try { return execSync( `npx puppeteer browsers install chrome@${version} --format "{{path}}"`, ) .toString() .trim(); } catch (e) { console.error(`Failed to install Chrome ${version}:`, e); process.exit(1); } } async function runTests(attempt) { if (attempt > 1) { console.log(`\nRun attempt ${attempt}...\n`); } return new Promise(resolve => { const child = spawn('node', nodeArgs, { stdio: 'inherit', env: { ...process.env, CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: true, CHROME_DEVTOOLS_MCP_CRASH_ON_UNCAUGHT: true, }, }); child.on('close', code => { resolve(code); }); }); } const chromePath = installChrome('146.0.7680.31'); process.env.CHROME_M146_EXECUTABLE_PATH = chromePath; const maxAttempts = shouldRetry ? 3 : 1; let exitCode = 1; for (let i = 1; i <= maxAttempts; i++) { exitCode = await runTests(i); if (exitCode === 0) { break; } } process.exit(exitCode ?? 1); ================================================ FILE: scripts/tsconfig.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "target": "esnext", "module": "nodenext", "moduleResolution": "nodenext", "outDir": "./ignored", "rootDir": ".", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noImplicitOverride": true, "noFallthroughCasesInSwitch": true, "incremental": true, "allowJs": true, "allowImportingTsExtensions": true, "noEmit": true, "useUnknownInCatchVariables": false }, "include": ["./**/*.ts", "./**/*.js", "./**/*.mjs"] } ================================================ FILE: scripts/update-lighthouse.ts ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import {execSync} from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; const ROOT_DIR = process.cwd(); const LIGHTHOUSE_DIR = path.resolve(ROOT_DIR, '../lighthouse'); const DEST_DIR = path.join(ROOT_DIR, 'src/third_party'); function main() { if (!fs.existsSync(LIGHTHOUSE_DIR)) { console.error(`Lighthouse directory not found at ${LIGHTHOUSE_DIR}`); process.exit(1); } console.log('Running yarn in lighthouse directory...'); execSync('yarn', {cwd: LIGHTHOUSE_DIR, stdio: 'inherit'}); console.log('Building lighthouse-devtools-mcp bundle...'); execSync('yarn build-devtools-mcp', {cwd: LIGHTHOUSE_DIR, stdio: 'inherit'}); const bundlePath = path.join( LIGHTHOUSE_DIR, 'dist', 'lighthouse-devtools-mcp-bundle.js', ); console.log(`Copying bundle from ${bundlePath} to ${DEST_DIR}...`); fs.copyFileSync( bundlePath, path.join(DEST_DIR, 'lighthouse-devtools-mcp-bundle.js'), ); const noticesPath = path.join( LIGHTHOUSE_DIR, 'dist', 'LIGHTHOUSE_MCP_BUNDLE_THIRD_PARTY_NOTICES', ); console.log(`Copying notices from ${noticesPath} to ${DEST_DIR}...`); fs.copyFileSync( noticesPath, path.join(DEST_DIR, 'LIGHTHOUSE_MCP_BUNDLE_THIRD_PARTY_NOTICES'), ); console.log('Done.'); } main(); ================================================ FILE: scripts/verify-npm-package.mjs ================================================ /** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import {execSync} from 'node:child_process'; // Checks that the select build files are present using `npm publish --dry-run`. function verifyPackageContents() { try { const output = execSync('npm publish --dry-run --json --silent', { encoding: 'utf8', }); // skip non-JSON output from prepare. const data = JSON.parse(output.substring(output.indexOf('{'))); const files = data.files.map(f => f.path); // Check some important files. const requiredPaths = [ 'build/src/index.js', 'build/src/third_party/index.js', ]; for (const requiredPath of requiredPaths) { const hasBuildFolder = files.some(path => path.startsWith(requiredPath)); if (!hasBuildFolder) { console.error( `Assertion Failed: "${requiredPath}" not found in tarball.`, ); process.exit(1); } } console.log( `npm publish --dry-run contained ${JSON.stringify(requiredPaths)}`, ); } catch (err) { console.error('failed to parse npm publish output', err); process.exit(1); } } verifyPackageContents(); ================================================ FILE: scripts/verify-server-json-version.ts ================================================ /** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import {execSync} from 'node:child_process'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; const serverJsonFilePath = path.join(process.cwd(), 'server.json'); const serverJson = JSON.parse(fs.readFileSync(serverJsonFilePath, 'utf-8')); const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-verify-')); try { const osName = os.platform(); const arch = os.arch(); let platform = ''; if (osName === 'darwin') { platform = 'darwin'; } else if (osName === 'linux') { platform = 'linux'; } // mcp-publisher does not support windows else { throw new Error(`Unsupported platform: ${osName}`); } let archName = ''; if (arch === 'x64') { archName = 'amd64'; } else if (arch === 'arm64') { archName = 'arm64'; } else { throw new Error(`Unsupported architecture: ${arch}`); } const osArch = `${platform}_${archName}`; const binName = 'mcp-publisher'; const downloadUrl = `https://github.com/modelcontextprotocol/registry/releases/latest/download/${binName}_${osArch}.tar.gz`; console.log(`Downloading ${binName} from ${downloadUrl}`); const downloadCmd = `curl -L "${downloadUrl}" | tar xz -C "${tmpDir}" ${binName}`; execSync(downloadCmd, {stdio: 'inherit'}); const publisherPath = path.join(tmpDir, binName); fs.chmodSync(publisherPath, 0o755); console.log(`Downloaded to ${publisherPath}`); // Create the new server.json in the temporary directory execSync(`${publisherPath} init`, {cwd: tmpDir, stdio: 'inherit'}); const newServerJsonPath = path.join(tmpDir, 'server.json'); const newServerJson = JSON.parse(fs.readFileSync(newServerJsonPath, 'utf-8')); const propertyToVerify = ['$schema']; const diffProps = []; for (const prop of propertyToVerify) { if (serverJson[prop] !== newServerJson[prop]) { diffProps.push(prop); } } if (diffProps.length) { throw new Error( `The following props in ${serverJsonFilePath} did not match the latest init value:\n${diffProps.map( prop => `- "${prop}": expected "${newServerJson[prop]}", got "${serverJson[prop]}"`, )}`, ); } } finally { fs.rmSync(tmpDir, {recursive: true, force: true}); } ================================================ FILE: server.json ================================================ { "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", "name": "io.github.ChromeDevTools/chrome-devtools-mcp", "title": "Chrome DevTools MCP", "description": "MCP server for Chrome DevTools", "repository": { "url": "https://github.com/ChromeDevTools/chrome-devtools-mcp", "source": "github" }, "version": "0.20.2", "packages": [ { "registryType": "npm", "registryBaseUrl": "https://registry.npmjs.org", "identifier": "chrome-devtools-mcp", "version": "0.20.2", "transport": { "type": "stdio" }, "environmentVariables": [] } ] } ================================================ FILE: skills/a11y-debugging/SKILL.md ================================================ --- name: a11y-debugging description: Uses Chrome DevTools MCP for accessibility (a11y) debugging and auditing based on web.dev guidelines. Use when testing semantic HTML, ARIA labels, focus states, keyboard navigation, tap targets, and color contrast. --- ## Core Concepts **Accessibility Tree vs DOM**: Visually hiding an element (e.g., `CSS opacity: 0`) behaves differently for screen readers than `display: none` or `aria-hidden="true"`. The `take_snapshot` tool returns the accessibility tree of the page, which represents what assistive technologies "see", making it the most reliable source of truth for semantic structure. **Reading web.dev documentation**: If you need to research specific accessibility guidelines (like `https://web.dev/articles/accessible-tap-targets`), you can append `.md.txt` to the URL (e.g., `https://web.dev/articles/accessible-tap-targets.md.txt`) to fetch the clean, raw markdown version. This is much easier to read! ## Workflow Patterns ### 1. Automated Audit (Lighthouse) Start by running a Lighthouse accessibility audit to get a comprehensive baseline. This tool provides a high-level score and lists specific failing elements with remediation advice. 1. Run the audit: - Set `mode` to `"navigation"` to refresh the page and capture load issues. - Set `outputDirPath` (e.g., `/tmp/lh-report`) to save the full JSON report. 2. **Analyze the Summary**: - Check `scores` (0-1 scale). A score < 1 indicates violations. - Review `audits.failed` count. 3. **Review the Report (CRITICAL)**: - **Parsing**: Do not read the entire file line-by-line. Use a CLI tool like `jq` or a Node.js one-liner to filter for failures: ```bash # Extract failing audits with their details node -e "const r=require('./report.json'); Object.values(r.audits).filter(a=>a.score!==null && a.score<1).forEach(a=>console.log(JSON.stringify({id:a.id, title:a.title, items:a.details?.items})))" ``` - This efficiently extracts the `selector` and `snippet` of failing elements without loading the full report into context. ### 2. Browser Issues & Audits Chrome automatically checks for common accessibility problems. Use `list_console_messages` to check for these native audits: - `types`: `["issue"]` - `includePreservedMessages`: `true` (to catch issues that occurred during page load) This often reveals missing labels, invalid ARIA attributes, and other critical errors without manual investigation. ### 3. Semantics & Structure The accessibility tree exposes the heading hierarchy and semantic landmarks. 1. Navigate to the page. 2. Use `take_snapshot` to capture the accessibility tree. 3. **Check Heading Levels**: Ensure heading levels (`h1`, `h2`, `h3`, etc.) are logical and do not skip levels. The snapshot will include heading roles. 4. **Content Reordering**: Verify that the DOM order (which drives the accessibility tree) matches the visual reading order. Use `take_screenshot` to inspect the visual layout and compare it against the snapshot structure to catch CSS floats or absolute positioning that jumbles the logical flow. ### 4. Labels, Forms & Text Alternatives 1. Locate buttons, inputs, and images in the `take_snapshot` output. 2. Ensure interactive elements have an accessible name (e.g., a button should not just say `""` if it only contains an icon). 3. **Orphaned Inputs**: Verify that all form inputs have associated labels. Use `evaluate_script` with the **"Find Orphaned Form Inputs" snippet** found in [references/a11y-snippets.md](references/a11y-snippets.md). 4. Check images for `alt` text. ### 5. Focus & Keyboard Navigation Testing "keyboard traps" and proper focus management without visual feedback relies on tracking the focused element. 1. Use the `press_key` tool with `"Tab"` or `"Shift+Tab"` to move focus. 2. Use `take_snapshot` to capture the updated accessibility tree. 3. Locate the element marked as focused in the snapshot to verify focus moved to the expected interactive element. 4. If a modal opens, focus must move into the modal and "trap" within it until closed. ### 6. Tap Targets and Visuals According to web.dev, tap targets should be at least 48x48 pixels with sufficient spacing. Since the accessibility tree doesn't show sizes, use `evaluate_script` with the **"Measure Tap Target Size" snippet** found in [references/a11y-snippets.md](references/a11y-snippets.md). _Pass the element's `uid` from the snapshot as an argument to `evaluate_script`._ ### 7. Color Contrast To verify color contrast ratios, start by checking for native accessibility issues: 1. Call `list_console_messages` with `types: ["issue"]`. 2. Look for "Low Contrast" issues in the output. If native audits do not report issues (which may happen in some headless environments) or if you need to check a specific element manually, use `evaluate_script` with the **"Check Color Contrast" snippet** found in [references/a11y-snippets.md](references/a11y-snippets.md). ### 8. Global Page Checks Verify document-level accessibility settings often missed in component testing using the **"Global Page Checks" snippet** found in [references/a11y-snippets.md](references/a11y-snippets.md). ## Troubleshooting If standard a11y queries fail or the `evaluate_script` snippets return unexpected results: - **Visual Inspection**: If automated scripts cannot determine contrast (e.g., text over gradient images or complex backgrounds), use `take_screenshot` to capture the element. While models cannot measure exact contrast ratios from images, they can visually assess legibility and identify obvious issues. ================================================ FILE: skills/a11y-debugging/references/a11y-snippets.md ================================================ # Accessibility Debugging Snippets Use these JavaScript snippets with the `evaluate_script` tool. ## 1. Find Orphaned Form Inputs Finds form inputs that lack an associated label (no `label[for]`, `aria-label`, `aria-labelledby`, or wrapping `