@mcp-b/chrome-devtools-mcp 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  [![28 Tools](https://img.shields.io/badge/MCP_Tools-28-green?style=flat-square)](./docs/tool-reference.md)
9
9
  [![Chrome](https://img.shields.io/badge/Chrome-DevTools-4285F4?style=flat-square&logo=googlechrome)](https://developer.chrome.com/docs/devtools/)
10
10
 
11
- 📖 **[WebMCP Documentation](https://docs.mcp-b.ai)** | 🚀 **[Quick Start](https://docs.mcp-b.ai/quickstart)** | 🔌 **[Connecting Agents](https://docs.mcp-b.ai/connecting-agents)**
11
+ 📖 **[WebMCP Documentation](https://docs.mcp-b.ai)** | 🚀 **[Quick Start](https://docs.mcp-b.ai/quickstart)** | 🔌 **[Connecting Agents](https://docs.mcp-b.ai/connecting-agents)** | 🎯 **[Chrome DevTools Quickstart](https://github.com/WebMCP-org/chrome-devtools-quickstart)**
12
12
 
13
13
  **@mcp-b/chrome-devtools-mcp** lets AI coding agents like Claude, Gemini, Cursor, and Copilot control and inspect a live Chrome browser via the Model Context Protocol (MCP). Get performance insights, debug network requests, take screenshots, and interact with website-specific MCP tools through WebMCP integration.
14
14
 
@@ -27,6 +27,21 @@
27
27
  | **Reliable Automation** | Puppeteer-based with automatic waiting for action results |
28
28
  | **Works with All MCP Clients** | Claude, Cursor, Copilot, Gemini CLI, VS Code, Windsurf, and more |
29
29
 
30
+ ### Token Efficiency
31
+
32
+ WebMCP tools are dramatically more efficient than screenshot-based workflows:
33
+
34
+ ![Token usage comparison: WebMCP tools vs screenshots](https://raw.githubusercontent.com/WebMCP-org/chrome-devtools-quickstart/main/assets/benchmark.png)
35
+
36
+ | Task | Screenshot-Based | WebMCP Tools | Savings |
37
+ |------|-----------------|--------------|---------|
38
+ | Simple task (set counter) | 3,801 tokens | 433 tokens | **89% fewer tokens** |
39
+ | Complex task (calendar event) | 11,390 tokens | 2,583 tokens | **77% fewer tokens** |
40
+
41
+ Screenshots are expensive (~2,000 tokens each). WebMCP tool responses are compact JSON (20-100 tokens typically).
42
+
43
+ > **Try it yourself:** Clone the [Chrome DevTools Quickstart](https://github.com/WebMCP-org/chrome-devtools-quickstart) and run the benchmarks.
44
+
30
45
  ## What's Different from Chrome DevTools MCP?
31
46
 
32
47
  This fork adds **WebMCP integration** - the ability to call MCP tools that are registered directly on webpages. This unlocks a powerful new workflow:
@@ -47,28 +62,9 @@ The key addition is the `list_webmcp_tools` and `call_webmcp_tool` tools that le
47
62
 
48
63
  One of the most powerful use cases for this package is **AI-driven tool development** - essentially test-driven development for AI agents. Here's how it works:
49
64
 
50
- ```
51
- ┌─────────────────────────────────────────────────────────────────────┐
52
- │ AI Development Feedback Loop │
53
- ├─────────────────────────────────────────────────────────────────────┤
54
- │ │
55
- │ 1. AI writes WebMCP tool code ──────────────────────────┐ │
56
- │ │ │
57
- │ 2. Dev server hot-reloads ◄─────────────────────────────┘ │
58
- │ │
59
- │ 3. AI opens browser via Chrome DevTools MCP │
60
- │ │ │
61
- │ ▼ │
62
- │ 4. AI calls list_webmcp_tools to see the new tool │
63
- │ │ │
64
- │ ▼ │
65
- │ 5. AI calls call_webmcp_tool to test it │
66
- │ │ │
67
- │ ▼ │
68
- │ 6. AI sees results, iterates if needed ───────► Back to step 1 │
69
- │ │
70
- └─────────────────────────────────────────────────────────────────────┘
71
- ```
65
+ ![WebMCP: AI-Driven Development Workflow](https://raw.githubusercontent.com/WebMCP-org/chrome-devtools-quickstart/main/assets/image.png)
66
+
67
+ > **Want to try it yourself?** Check out the [Chrome DevTools Quickstart](https://github.com/WebMCP-org/chrome-devtools-quickstart) - a minimal example you can clone and run in 3 steps.
72
68
 
73
69
  ### Example: Building a Search Tool
74
70
 
@@ -134,6 +130,12 @@ This creates a tight feedback loop where your AI assistant can:
134
130
 
135
131
  This is like **TDD for AI** - the AI can build and verify its own tools in real-time.
136
132
 
133
+ ### Demo: Tool Execution Result
134
+
135
+ Here's what it looks like when an AI agent successfully discovers and calls WebMCP tools:
136
+
137
+ ![Demo: AI agent executing WebMCP tools](https://raw.githubusercontent.com/WebMCP-org/chrome-devtools-quickstart/main/assets/demo-result.png)
138
+
137
139
  ## [Tool reference](./docs/tool-reference.md) | [Changelog](./CHANGELOG.md) | [Contributing](./CONTRIBUTING.md) | [Troubleshooting](./docs/troubleshooting.md) | [Design Principles](./docs/design-principles.md)
138
140
 
139
141
  ## Key features
@@ -165,6 +167,8 @@ MCP clients.
165
167
 
166
168
  ## Getting started
167
169
 
170
+ > **New to WebMCP?** Try the [Chrome DevTools Quickstart](https://github.com/WebMCP-org/chrome-devtools-quickstart) - clone, run, and see AI-driven tool development in action in under 5 minutes.
171
+
168
172
  Add the following config to your MCP client:
169
173
 
170
174
  ```json
@@ -43,18 +43,28 @@ export class PageCollector {
43
43
  this.#browser.off('targetdestroyed', this.#onTargetDestroyed);
44
44
  }
45
45
  #onTargetCreated = async (target) => {
46
- const page = await target.page();
47
- if (!page) {
48
- return;
46
+ try {
47
+ const page = await target.page();
48
+ if (!page) {
49
+ return;
50
+ }
51
+ this.addPage(page);
52
+ }
53
+ catch (err) {
54
+ logger('Error getting a page for a target onTargetCreated', err);
49
55
  }
50
- this.addPage(page);
51
56
  };
52
57
  #onTargetDestroyed = async (target) => {
53
- const page = await target.page();
54
- if (!page) {
55
- return;
58
+ try {
59
+ const page = await target.page();
60
+ if (!page) {
61
+ return;
62
+ }
63
+ this.cleanupPageDestroyed(page);
64
+ }
65
+ catch (err) {
66
+ logger('Error getting a page for a target onTargetDestroyed', err);
56
67
  }
57
- this.cleanupPageDestroyed(page);
58
68
  };
59
69
  addPage(page) {
60
70
  this.#initializePage(page);
@@ -32,6 +32,7 @@ function makeTargetFilter() {
32
32
  };
33
33
  }
34
34
  export async function ensureBrowserConnected(options) {
35
+ const { channel } = options;
35
36
  if (browser?.connected) {
36
37
  return browser;
37
38
  }
@@ -49,11 +50,56 @@ export async function ensureBrowserConnected(options) {
49
50
  else if (options.browserURL) {
50
51
  connectOptions.browserURL = options.browserURL;
51
52
  }
53
+ else if (channel || options.userDataDir) {
54
+ const userDataDir = options.userDataDir;
55
+ if (userDataDir) {
56
+ // TODO: re-expose this logic via Puppeteer.
57
+ const portPath = path.join(userDataDir, 'DevToolsActivePort');
58
+ try {
59
+ const fileContent = await fs.promises.readFile(portPath, 'utf8');
60
+ const [rawPort, rawPath] = fileContent
61
+ .split('\n')
62
+ .map(line => {
63
+ return line.trim();
64
+ })
65
+ .filter(line => {
66
+ return !!line;
67
+ });
68
+ if (!rawPort || !rawPath) {
69
+ throw new Error(`Invalid DevToolsActivePort '${fileContent}' found`);
70
+ }
71
+ const port = parseInt(rawPort, 10);
72
+ if (isNaN(port) || port <= 0 || port > 65535) {
73
+ throw new Error(`Invalid port '${rawPort}' found`);
74
+ }
75
+ const browserWSEndpoint = `ws://127.0.0.1:${port}${rawPath}`;
76
+ connectOptions.browserWSEndpoint = browserWSEndpoint;
77
+ }
78
+ catch (error) {
79
+ throw new Error(`Could not connect to Chrome in ${userDataDir}. Check if Chrome is running and remote debugging is enabled.`, {
80
+ cause: error,
81
+ });
82
+ }
83
+ }
84
+ else {
85
+ if (!channel) {
86
+ throw new Error('Channel must be provided if userDataDir is missing');
87
+ }
88
+ connectOptions.channel = (channel === 'stable' ? 'chrome' : `chrome-${channel}`);
89
+ }
90
+ }
52
91
  else {
53
- throw new Error('Either browserURL or wsEndpoint must be provided');
92
+ throw new Error('Either browserURL, wsEndpoint, channel or userDataDir must be provided');
54
93
  }
55
94
  logger('Connecting Puppeteer to ', JSON.stringify(connectOptions));
56
- browser = await puppeteer.connect(connectOptions);
95
+ try {
96
+ browser = await puppeteer.connect(connectOptions);
97
+ }
98
+ catch (err) {
99
+ throw new Error('Could not connect to Chrome. Check if Chrome is running and remote debugging is enabled by going to chrome://inspect/#remote-debugging.', {
100
+ cause: err,
101
+ });
102
+ }
57
103
  logger('Connected Puppeteer');
58
104
  return browser;
59
105
  }
package/build/src/cli.js CHANGED
@@ -5,6 +5,18 @@
5
5
  */
6
6
  import { yargs, hideBin } from './third_party/index.js';
7
7
  export const cliOptions = {
8
+ autoConnect: {
9
+ type: 'boolean',
10
+ description: 'If specified, automatically connects to a browser (Chrome 145+) running in the user data directory identified by the channel param. Falls back to launching a new instance if no running browser is found.',
11
+ conflicts: ['isolated', 'executablePath'],
12
+ default: true,
13
+ coerce: (value) => {
14
+ if (value === false) {
15
+ return false;
16
+ }
17
+ return true;
18
+ },
19
+ },
8
20
  browserUrl: {
9
21
  type: 'string',
10
22
  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.',
@@ -163,7 +175,7 @@ export function parseArguments(version, argv = process.argv) {
163
175
  !args.browserUrl &&
164
176
  !args.wsEndpoint &&
165
177
  !args.executablePath) {
166
- args.channel = 'stable';
178
+ args.channel = 'dev';
167
179
  }
168
180
  return true;
169
181
  })
@@ -204,6 +216,14 @@ export function parseArguments(version, argv = process.argv) {
204
216
  '$0 --user-data-dir=/tmp/user-data-dir',
205
217
  'Use a custom user data directory',
206
218
  ],
219
+ [
220
+ '$0 --auto-connect',
221
+ 'Connect to a stable Chrome instance (Chrome 145+) running instead of launching a new instance',
222
+ ],
223
+ [
224
+ '$0 --auto-connect --channel=canary',
225
+ 'Connect to a canary Chrome instance (Chrome 145+) running instead of launching a new instance',
226
+ ],
207
227
  ]);
208
228
  return yargsInstance
209
229
  .wrap(Math.min(120, yargsInstance.terminalWidth()))
package/build/src/main.js CHANGED
@@ -3,6 +3,7 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
+ import process from 'node:process';
6
7
  import './polyfill.js';
7
8
  import { ensureBrowserConnected, ensureBrowserLaunched } from './browser.js';
8
9
  import { parseArguments } from './cli.js';
@@ -17,8 +18,11 @@ import { ToolCategory } from './tools/categories.js';
17
18
  import { tools } from './tools/tools.js';
18
19
  // If moved update release-please config
19
20
  // x-release-please-start-version
20
- const VERSION = '0.11.0';
21
+ const VERSION = '0.12.1';
21
22
  // x-release-please-end
23
+ process.on('unhandledRejection', (reason, promise) => {
24
+ logger('Unhandled promise rejection', promise, reason);
25
+ });
22
26
  export const args = parseArguments(VERSION);
23
27
  const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined;
24
28
  logger(`Starting Chrome DevTools MCP Server v${VERSION}`);
@@ -39,14 +43,51 @@ async function getContext() {
39
43
  extraArgs.push(`--proxy-server=${args.proxyServer}`);
40
44
  }
41
45
  const devtools = args.experimentalDevtools ?? false;
42
- const browser = args.browserUrl || args.wsEndpoint
43
- ? await ensureBrowserConnected({
46
+ let browser;
47
+ // If explicit browserUrl or wsEndpoint is provided, connect without fallback
48
+ if (args.browserUrl || args.wsEndpoint) {
49
+ browser = await ensureBrowserConnected({
44
50
  browserURL: args.browserUrl,
45
51
  wsEndpoint: args.wsEndpoint,
46
52
  wsHeaders: args.wsHeaders,
47
53
  devtools,
48
- })
49
- : await ensureBrowserLaunched({
54
+ channel: undefined,
55
+ userDataDir: args.userDataDir,
56
+ });
57
+ }
58
+ // If autoConnect is true, try connecting first, then fall back to launching
59
+ else if (args.autoConnect) {
60
+ try {
61
+ logger('Attempting to connect to running browser instance...');
62
+ browser = await ensureBrowserConnected({
63
+ browserURL: undefined,
64
+ wsEndpoint: undefined,
65
+ wsHeaders: undefined,
66
+ devtools,
67
+ channel: args.channel,
68
+ userDataDir: args.userDataDir,
69
+ });
70
+ logger('Successfully connected to running browser instance');
71
+ }
72
+ catch (err) {
73
+ logger('Failed to connect to running browser, launching new instance...', err);
74
+ browser = await ensureBrowserLaunched({
75
+ headless: args.headless,
76
+ executablePath: args.executablePath,
77
+ channel: args.channel,
78
+ isolated: args.isolated ?? false,
79
+ userDataDir: args.userDataDir,
80
+ logFile,
81
+ viewport: args.viewport,
82
+ args: extraArgs,
83
+ acceptInsecureCerts: args.acceptInsecureCerts,
84
+ devtools,
85
+ });
86
+ }
87
+ }
88
+ // Otherwise, just launch a new browser
89
+ else {
90
+ browser = await ensureBrowserLaunched({
50
91
  headless: args.headless,
51
92
  executablePath: args.executablePath,
52
93
  channel: args.channel,
@@ -58,6 +99,7 @@ async function getContext() {
58
99
  acceptInsecureCerts: args.acceptInsecureCerts,
59
100
  devtools,
60
101
  });
102
+ }
61
103
  if (context?.browser !== browser) {
62
104
  context = await McpContext.from(browser, logger, {
63
105
  experimentalDevToolsDebugging: devtools,
@@ -107,7 +149,10 @@ function registerTool(tool) {
107
149
  }
108
150
  catch (err) {
109
151
  logger(`${tool.name} error:`, err, err?.stack);
110
- const errorText = err && 'message' in err ? err.message : String(err);
152
+ let errorText = err && 'message' in err ? err.message : String(err);
153
+ if ('cause' in err && err.cause) {
154
+ errorText += `\nCause: ${err.cause.message}`;
155
+ }
111
156
  return {
112
157
  content: [
113
158
  {
@@ -29,12 +29,18 @@ export const selectPage = defineTool({
29
29
  schema: {
30
30
  pageIdx: zod
31
31
  .number()
32
- .describe('The index of the page to select. Call list_pages to list pages.'),
32
+ .describe(`The index of the page to select. Call ${listPages.name} to get available pages.`),
33
+ bringToFront: zod
34
+ .boolean()
35
+ .optional()
36
+ .describe('Whether to focus the page and bring it to the top.'),
33
37
  },
34
38
  handler: async (request, response, context) => {
35
39
  const page = context.getPageByIdx(request.params.pageIdx);
36
- await page.bringToFront();
37
40
  context.selectPage(page);
41
+ if (request.params.bringToFront) {
42
+ await page.bringToFront();
43
+ }
38
44
  response.setIncludePages(true);
39
45
  },
40
46
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-b/chrome-devtools-mcp",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "MCP server for Chrome DevTools with WebMCP integration for connecting to website MCP tools",
5
5
  "keywords": [
6
6
  "mcp",