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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,12 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
- [`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)
- **Extensions** (5 tools)
- [`install_extension`](docs/tool-reference.md#install_extension)
- [`list_extensions`](docs/tool-reference.md#list_extensions)
- [`reload_extension`](docs/tool-reference.md#reload_extension)
- [`trigger_extension_action`](docs/tool-reference.md#trigger_extension_action)
- [`uninstall_extension`](docs/tool-reference.md#uninstall_extension)
- **Memory** (1 tools)
- [`take_memory_snapshot`](docs/tool-reference.md#take_memory_snapshot)

Expand Down Expand Up @@ -612,6 +618,11 @@ The Chrome DevTools MCP server supports the following configuration option:
- **Type:** boolean
- **Default:** `true`

- **`--categoryExtensions`/ `--category-extensions`**
Set to true to include tools related to extensions. Note: This feature is currently only supported with a pipe connection. autoConnect, browserUrl, and wsEndpoint are not supported with this feature until 149 will be released.
- **Type:** boolean
- **Default:** `false`

- **`--performanceCrux`/ `--performance-crux`**
Set to false to disable sending URLs from performance traces to CrUX API to get field performance data.
- **Type:** boolean
Expand Down
58 changes: 58 additions & 0 deletions docs/tool-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
- [`list_console_messages`](#list_console_messages)
- [`take_screenshot`](#take_screenshot)
- [`take_snapshot`](#take_snapshot)
- **[Extensions](#extensions)** (5 tools)
Comment thread
nroscino marked this conversation as resolved.
- [`install_extension`](#install_extension)
- [`list_extensions`](#list_extensions)
- [`reload_extension`](#reload_extension)
- [`trigger_extension_action`](#trigger_extension_action)
- [`uninstall_extension`](#uninstall_extension)
- **[Memory](#memory)** (1 tools)
- [`take_memory_snapshot`](#take_memory_snapshot)

Expand Down Expand Up @@ -390,6 +396,58 @@ in the DevTools Elements panel (if any).

---

## Extensions

> NOTE: Extensions are not active by default. Use the '--category-extensions' flag

### `install_extension`

**Description:** Installs a Chrome extension from the given path.

**Parameters:**

- **path** (string) **(required)**: Absolute path to the unpacked extension folder.

---

### `list_extensions`

**Description:** Lists all the Chrome extensions installed in the browser. This includes their name, ID, version, and enabled status.

**Parameters:** None

---

### `reload_extension`

**Description:** Reloads an unpacked Chrome extension by its ID.

**Parameters:**

- **id** (string) **(required)**: ID of the extension to reload.

---

### `trigger_extension_action`

**Description:** Triggers the default action of an extension by its ID.

**Parameters:**

- **id** (string) **(required)**: ID of the extension to trigger the action for.

---

### `uninstall_extension`

**Description:** Uninstalls a Chrome extension by its ID.

**Parameters:**

- **id** (string) **(required)**: ID of the extension to uninstall.

---

## Memory

### `take_memory_snapshot`
Expand Down
12 changes: 11 additions & 1 deletion scripts/generate-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ 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 {
ToolCategory,
OFF_BY_DEFAULT_CATEGORIES,
labels,
} from '../build/src/tools/categories.js';
import {createTools} from '../build/src/tools/tools.js';

const OUTPUT_PATH = './docs/tool-reference.md';
Expand Down Expand Up @@ -351,6 +355,12 @@ async function generateReference(

markdown += `## ${categoryName}\n\n`;

if (OFF_BY_DEFAULT_CATEGORIES.includes(category)) {
const flagName = `--category-${category}`;

markdown += `> NOTE: ${categoryName} are not active by default. Use the '${flagName}' flag\n\n`;
}

// Sort tools within category
categoryTools.sort((a: Tool, b: Tool) => a.name.localeCompare(b.name));

Expand Down
11 changes: 9 additions & 2 deletions skills/chrome-devtools/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ description: Uses Chrome DevTools via MCP for efficient debugging, troubleshooti

## Core Concepts

**Browser lifecycle**: Browser starts automatically on first tool call using a persistent Chrome profile. Configure via CLI args in the MCP server configuration: `npx chrome-devtools-mcp@latest --help`.

**Browser lifecycle**: Browser starts automatically on first tool call using a persistent Chrome profile. Configure via CLI args in the MCP server configuration: `npx chrome-devtools-mcp@latest --help`. To enable extensions, use `--categoryExtensions`.
**Page selection**: Tools operate on the currently selected page. Use `list_pages` to see available pages, then `select_page` to switch context.

**Element interaction**: Use `take_snapshot` to get page structure with element `uid`s. Each element has a unique `uid` for interaction. If an element isn't found, take a fresh snapshot - the element may have been removed or the page changed.
Expand Down Expand Up @@ -36,6 +35,14 @@ description: Uses Chrome DevTools via MCP for efficient debugging, troubleshooti

You can send multiple tool calls in parallel, but maintain correct order: navigate → wait → snapshot → interact.

### Testing an extension

1. **Install**: Use `install_extension` with the path to the unpacked extension.
2. **Identify**: Get the extension ID from the response or by calling `list_extensions`.
3. **Trigger Action**: Use `trigger_extension_action` to open the popup or side panel if applicable.
4. **Verify Service Worker**: Use `evaluate_script` with `serviceWorkerId` to check extension state or trigger background actions.
5. **Verify Page Behavior**: Navigate to a page where the extension operates and use `take_snapshot` to check if content scripts injected elements or modified the page correctly.

## Troubleshooting

If `chrome-devtools-mcp` is insufficient, guide users to use Chrome DevTools UI:
Expand Down
7 changes: 7 additions & 0 deletions skills/troubleshooting/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ If the server starts successfully but only a limited subset of tools (like `list

All tools in `chrome-devtools-mcp` are annotated with `readOnlyHint: true` (for safe, non-modifying tools) or `readOnlyHint: false` (for tools that modify browser state, like `emulate`, `click`, `navigate_page`). To access the full suite of tools, the user must disable read-only mode in their MCP client (e.g., by exiting "Plan Mode" in Gemini CLI or adjusting their client's tool safety settings).

#### Symptom: Extension tools are missing or extensions fail to load

If the tools related to extensions (like `install_extension`) are not available, or if the extensions you load are not functioning:

1. **Check for the `--categoryExtensions` flag**: Ensure this flag is passed in the MCP server configuration to enable the extension category tools.
2. **Make sure the MCP server in configured to launch Chrome instead of connecting to an instance**: Chrome before 149 is not able to load extensions when connecting to an existing instance (`--auto-connect`, `--browserUrl`).

#### Other Common Errors

Identify other error messages from the failed tool call or the MCP initialization logs:
Expand Down
12 changes: 6 additions & 6 deletions src/bin/chrome-devtools-mcp-cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const cliOptions = {
type: 'boolean',
description:
'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 remote debugging server to be started in the Chrome instance via chrome://inspect/#remote-debugging.',
conflicts: ['isolated', 'executablePath', 'categoryExtensions'],
conflicts: ['isolated', 'executablePath'],
default: false,
coerce: (value: boolean | undefined) => {
if (!value) {
Expand All @@ -26,7 +26,7 @@ export const cliOptions = {
description:
'Connect to a running, debuggable Chrome instance (e.g. `http://127.0.0.1:9222`). For more details see: https://github.com/ChromeDevTools/chrome-devtools-mcp#connecting-to-a-running-chrome-instance.',
alias: 'u',
conflicts: ['wsEndpoint', 'categoryExtensions'],
conflicts: ['wsEndpoint'],
coerce: (url: string | undefined) => {
if (!url) {
return;
Expand All @@ -44,7 +44,7 @@ export const cliOptions = {
description:
'WebSocket endpoint to connect to a running Chrome instance (e.g., ws://127.0.0.1:9222/devtools/browser/<id>). Alternative to --browserUrl.',
alias: 'w',
conflicts: ['browserUrl', 'categoryExtensions'],
conflicts: ['browserUrl'],
coerce: (url: string | undefined) => {
if (!url) {
return;
Expand Down Expand Up @@ -222,10 +222,10 @@ export const cliOptions = {
},
categoryExtensions: {
type: 'boolean',
hidden: true,
conflicts: ['browserUrl', 'autoConnect', 'wsEndpoint'],
hidden: false,
Comment thread
nroscino marked this conversation as resolved.
default: false,
describe:
'Set to true to include tools related to extensions. Note: This feature is only supported with a pipe connection. autoConnect is not supported.',
'Set to true to include tools related to extensions. Note: This feature is currently only supported with a pipe connection. autoConnect, browserUrl, and wsEndpoint are not supported with this feature until 149 will be released.',
},
categoryInPageTools: {
type: 'boolean',
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export async function createMcpServer(
}
if (
tool.annotations.category === ToolCategory.EXTENSIONS &&
!serverArgs.categoryExtensions
serverArgs.categoryExtensions === false
) {
return;
}
Expand Down
2 changes: 2 additions & 0 deletions src/tools/categories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ export const labels = {
[ToolCategory.IN_PAGE]: 'In-page tools',
[ToolCategory.MEMORY]: 'Memory',
};

export const OFF_BY_DEFAULT_CATEGORIES = [ToolCategory.EXTENSIONS];
99 changes: 51 additions & 48 deletions src/tools/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,58 +37,61 @@ const FILTERABLE_MESSAGE_TYPES: [
'issue',
];

export const listConsoleMessages = definePageTool({
name: 'list_console_messages',
description:
'List all console messages for the currently selected page since the last navigation.',
annotations: {
category: ToolCategory.DEBUGGING,
readOnlyHint: true,
},
schema: {
pageSize: zod
.number()
.int()
.positive()
.optional()
.describe(
'Maximum number of messages to return. When omitted, returns all messages.',
),
pageIdx: zod
.number()
.int()
.min(0)
.optional()
.describe(
'Page number to return (0-based). When omitted, returns the first page.',
),
types: zod
.array(zod.enum(FILTERABLE_MESSAGE_TYPES))
.optional()
.describe(
'Filter messages to only return messages of the specified resource types. When omitted or empty, returns all messages.',
),
includePreservedMessages: zod
.boolean()
.default(false)
.optional()
.describe(
'Set to true to return the preserved messages over the last 3 navigations.',
),
},
handler: async (request, response) => {
response.setIncludeConsoleData(true, {
pageSize: request.params.pageSize,
pageIdx: request.params.pageIdx,
types: request.params.types,
includePreservedMessages: request.params.includePreservedMessages,
});
},
const LIST_CONSOLE_MESSAGES_TOOL_NAME = 'list_console_messages';

export const listConsoleMessages = definePageTool(cliArgs => {
return {
name: LIST_CONSOLE_MESSAGES_TOOL_NAME,
description: `List all console messages for the currently selected page since the last navigation.${cliArgs?.categoryExtensions ? ' This includes console messages originating from extensions content scripts.' : ''}`,
annotations: {
category: ToolCategory.DEBUGGING,
readOnlyHint: true,
},
schema: {
pageSize: zod
.number()
.int()
.positive()
.optional()
.describe(
'Maximum number of messages to return. When omitted, returns all messages.',
),
pageIdx: zod
.number()
.int()
.min(0)
.optional()
.describe(
'Page number to return (0-based). When omitted, returns the first page.',
),
types: zod
.array(zod.enum(FILTERABLE_MESSAGE_TYPES))
.optional()
.describe(
'Filter messages to only return messages of the specified resource types. When omitted or empty, returns all messages.',
),
includePreservedMessages: zod
.boolean()
.default(false)
.optional()
.describe(
'Set to true to return the preserved messages over the last 3 navigations.',
),
},
handler: async (request, response) => {
response.setIncludeConsoleData(true, {
pageSize: request.params.pageSize,
pageIdx: request.params.pageIdx,
types: request.params.types,
includePreservedMessages: request.params.includePreservedMessages,
});
},
};
});

export const getConsoleMessage = definePageTool({
name: 'get_console_message',
description: `Gets a console message by its ID. You can get all messages by calling ${listConsoleMessages.name}.`,
description: `Gets a console message by its ID. You can get all messages by calling ${LIST_CONSOLE_MESSAGES_TOOL_NAME}.`,
annotations: {
category: ToolCategory.DEBUGGING,
readOnlyHint: true,
Expand Down
15 changes: 4 additions & 11 deletions src/tools/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,12 @@ import {zod} from '../third_party/index.js';
import {ToolCategory} from './categories.js';
import {defineTool} from './ToolDefinition.js';

const EXTENSIONS_CONDITION = 'experimentalExtensionSupport';

export const installExtension = defineTool({
name: 'install_extension',
description: 'Installs a Chrome extension from the given path.',
annotations: {
category: ToolCategory.EXTENSIONS,
readOnlyHint: false,
conditions: [EXTENSIONS_CONDITION],
},
schema: {
path: zod
Expand All @@ -37,7 +34,6 @@ export const uninstallExtension = defineTool({
annotations: {
category: ToolCategory.EXTENSIONS,
readOnlyHint: false,
conditions: [EXTENSIONS_CONDITION],
},
schema: {
id: zod.string().describe('ID of the extension to uninstall.'),
Expand All @@ -52,11 +48,10 @@ export const uninstallExtension = defineTool({
export const listExtensions = defineTool({
name: 'list_extensions',
description:
'Lists all extensions via this server, including their name, ID, version, and enabled status.',
'Lists all the Chrome extensions installed in the browser. This includes their name, ID, version, and enabled status.',
annotations: {
category: ToolCategory.EXTENSIONS,
readOnlyHint: true,
conditions: [EXTENSIONS_CONDITION],
},
schema: {},
handler: async (_request, response, _context) => {
Expand All @@ -70,7 +65,6 @@ export const reloadExtension = defineTool({
annotations: {
category: ToolCategory.EXTENSIONS,
readOnlyHint: false,
conditions: [EXTENSIONS_CONDITION],
},
schema: {
id: zod.string().describe('ID of the extension to reload.'),
Expand All @@ -88,18 +82,17 @@ export const reloadExtension = defineTool({

export const triggerExtensionAction = defineTool({
name: 'trigger_extension_action',
description: 'Triggers an action in a Chrome extension.',
description: 'Triggers the default action of an extension by its ID.',
annotations: {
category: ToolCategory.EXTENSIONS,
readOnlyHint: false,
conditions: [EXTENSIONS_CONDITION],
},
schema: {
id: zod.string().describe('ID of the extension.'),
id: zod.string().describe('ID of the extension to trigger the action for.'),
},
handler: async (request, response, context) => {
const {id} = request.params;
await context.triggerExtensionAction(id);
response.appendResponseLine(`Extension action triggered. Id: ${id}`);
response.appendResponseLine(`Extension action triggered for ID ${id}`);
},
});
Loading
Loading