@mcp-b/chrome-devtools-mcp 1.3.1 → 1.5.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 +9 -9
- package/build/src/McpContext.js +59 -44
- package/build/src/McpResponse.js +7 -0
- package/build/src/index.js +0 -0
- package/build/src/main.js +14 -3
- package/build/src/polyfillLoader.js +44 -0
- package/build/src/prompts/index.js +3 -3
- package/build/src/tools/WebMCPToolHub.js +88 -138
- package/build/src/tools/webmcp.js +714 -3
- package/build/src/transports/WebMCPBridgeScript.js +13 -7
- package/build/src/transports/WebMCPClientTransport.js +37 -19
- package/build/src/transports/bridgeConstants.js +22 -0
- package/package.json +28 -25
package/README.md
CHANGED
|
@@ -123,7 +123,7 @@ The AI can see the actual response, fix any bugs, and repeat until it works perf
|
|
|
123
123
|
This creates a tight feedback loop where your AI assistant can:
|
|
124
124
|
- **Write** WebMCP tools in your codebase
|
|
125
125
|
- **Deploy** them automatically via hot-reload
|
|
126
|
-
- **Discover** them through `
|
|
126
|
+
- **Discover** them through `list_webmcp_tools`
|
|
127
127
|
- **Test** them by calling tools directly by their prefixed names (e.g., `webmcp_localhost_3000_page0_search_products`)
|
|
128
128
|
- **Debug** issues using console messages and snapshots
|
|
129
129
|
- **Iterate** until the tool works correctly
|
|
@@ -476,7 +476,7 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
|
|
|
476
476
|
- [`take_screenshot`](docs/tool-reference.md#take_screenshot)
|
|
477
477
|
- [`take_snapshot`](docs/tool-reference.md#take_snapshot)
|
|
478
478
|
- **Website MCP Tools** (1 tool)
|
|
479
|
-
- [`
|
|
479
|
+
- [`list_webmcp_tools`](docs/tool-reference.md#list_webmcp_tools) - List available website tools across all pages (with diff)
|
|
480
480
|
|
|
481
481
|
<!-- END AUTO GENERATED TOOLS -->
|
|
482
482
|
|
|
@@ -814,7 +814,7 @@ Navigate to https://example.com/app
|
|
|
814
814
|
What tools are available on this website?
|
|
815
815
|
```
|
|
816
816
|
|
|
817
|
-
The AI agent will use `
|
|
817
|
+
The AI agent will use `list_webmcp_tools` to show you what functionality the
|
|
818
818
|
website exposes. This automatically connects to the page's WebMCP server.
|
|
819
819
|
|
|
820
820
|
**3. Use the tools directly**
|
|
@@ -840,13 +840,13 @@ By default, WebMCP tools are automatically registered as first-class MCP tools w
|
|
|
840
840
|
| Claude Code | Yes | Full support for `tools/list_changed` |
|
|
841
841
|
| GitHub Copilot | Yes | Supports list changed notifications |
|
|
842
842
|
| Gemini CLI | Yes | Recently added support |
|
|
843
|
-
| Cursor | No | Use `
|
|
844
|
-
| Cline | Partial | May need manual polling with `
|
|
845
|
-
| Continue | Unknown | Use `
|
|
843
|
+
| Cursor | No | Use `list_webmcp_tools` to poll manually |
|
|
844
|
+
| Cline | Partial | May need manual polling with `list_webmcp_tools` |
|
|
845
|
+
| Continue | Unknown | Use `list_webmcp_tools` if tools don't appear |
|
|
846
846
|
|
|
847
847
|
**For clients without dynamic tool support:**
|
|
848
848
|
|
|
849
|
-
If your MCP client doesn't support `tools/list_changed` notifications, use `
|
|
849
|
+
If your MCP client doesn't support `tools/list_changed` notifications, use `list_webmcp_tools` to manually see which tools are available, then call them directly by their prefixed names. The `list_webmcp_tools` tool is diff-aware to reduce context pollution:
|
|
850
850
|
- First call returns the full tool list
|
|
851
851
|
- Subsequent calls return only added/removed tools
|
|
852
852
|
- Use `full: true` to force the complete list
|
|
@@ -872,9 +872,9 @@ Call the website's form submission tool to fill out the contact form
|
|
|
872
872
|
- **"WebMCP not detected"**: The current webpage doesn't have `@mcp-b/global`
|
|
873
873
|
installed or no tools are registered. The page needs the WebMCP polyfill loaded.
|
|
874
874
|
- **Tool call fails**: Check the tool's input schema matches your parameters.
|
|
875
|
-
Use `
|
|
875
|
+
Use `list_webmcp_tools` to see the expected input format.
|
|
876
876
|
- **Tools not appearing after navigation**: WebMCP auto-reconnects when you
|
|
877
|
-
navigate. If the new page has different tools, call `
|
|
877
|
+
navigate. If the new page has different tools, call `list_webmcp_tools` again.
|
|
878
878
|
|
|
879
879
|
## Related Packages
|
|
880
880
|
|
package/build/src/McpContext.js
CHANGED
|
@@ -10,7 +10,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
|
10
10
|
import { extractUrlLikeFromDevToolsTitle, urlsEqual } from './DevtoolsUtils.js';
|
|
11
11
|
import { ToolListChangedNotificationSchema } from './third_party/index.js';
|
|
12
12
|
import { NetworkCollector, ConsoleCollector } from './PageCollector.js';
|
|
13
|
-
import { WEB_MCP_BRIDGE_SCRIPT
|
|
13
|
+
import { WEB_MCP_BRIDGE_SCRIPT } from './transports/WebMCPBridgeScript.js';
|
|
14
14
|
import { WebMCPClientTransport } from './transports/WebMCPClientTransport.js';
|
|
15
15
|
import { Locator } from './third_party/index.js';
|
|
16
16
|
import { listPages } from './tools/pages.js';
|
|
@@ -219,11 +219,11 @@ export class McpContext {
|
|
|
219
219
|
newUrl === 'about:blank') {
|
|
220
220
|
return;
|
|
221
221
|
}
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
222
|
+
// Immediately try to connect - no polling needed
|
|
223
|
+
// If no WebMCP, connection will timeout gracefully
|
|
224
|
+
this.#tryConnectWebMCP(page).catch(err => {
|
|
225
|
+
this.logger('WebMCP connection attempt failed (expected if page has no WebMCP):', err);
|
|
226
|
+
});
|
|
227
227
|
};
|
|
228
228
|
page.on('framenavigated', onFrameNavigated);
|
|
229
229
|
// Clean up listener when page closes
|
|
@@ -234,33 +234,37 @@ export class McpContext {
|
|
|
234
234
|
this.logger(`WebMCP auto-detection listener installed for page: ${url}`);
|
|
235
235
|
}
|
|
236
236
|
/**
|
|
237
|
-
*
|
|
238
|
-
*
|
|
237
|
+
* Attempt to connect to WebMCP on a page without pre-checking.
|
|
238
|
+
* Uses the extension's approach: just try to connect, handle failure gracefully.
|
|
239
|
+
*
|
|
240
|
+
* This matches the WebMCP extension's behavior:
|
|
241
|
+
* - No pre-flight detection polling
|
|
242
|
+
* - Immediate connection attempt
|
|
243
|
+
* - Graceful handling if no server exists
|
|
244
|
+
* - Notification-based syncing when tools appear later
|
|
245
|
+
*
|
|
246
|
+
* Reference: /WebMCP/apps/extension/entrypoints/content/connection.ts lines 88-118
|
|
239
247
|
*/
|
|
240
|
-
async #
|
|
248
|
+
async #tryConnectWebMCP(page) {
|
|
241
249
|
// Skip if tool hub is not enabled
|
|
242
250
|
if (!this.#toolHub?.isEnabled()) {
|
|
243
251
|
return;
|
|
244
252
|
}
|
|
245
253
|
try {
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
if (!hasWebMCP) {
|
|
249
|
-
this.logger(`No WebMCP detected on page: ${page.url()}`);
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
this.logger(`WebMCP detected on page: ${page.url()}, connecting...`);
|
|
253
|
-
// Connect and sync tools - this handles everything including sending list_changed
|
|
254
|
+
// Immediately try to get/create WebMCP client
|
|
255
|
+
// Transport is configured with requireWebMCP: false and 30s timeout in getWebMCPClient
|
|
254
256
|
const result = await this.getWebMCPClient(page);
|
|
255
257
|
if (result.connected) {
|
|
256
|
-
this.logger(`WebMCP
|
|
258
|
+
this.logger(`WebMCP connected for page: ${page.url()}`);
|
|
257
259
|
}
|
|
258
260
|
else {
|
|
259
|
-
|
|
261
|
+
// This is normal for pages without WebMCP
|
|
262
|
+
this.logger(`No WebMCP on page: ${page.url()}`);
|
|
260
263
|
}
|
|
261
264
|
}
|
|
262
265
|
catch (err) {
|
|
263
|
-
|
|
266
|
+
// Connection timeout or error is expected on pages without WebMCP
|
|
267
|
+
this.logger(`WebMCP connection failed for ${page.url()} (normal if page has no WebMCP):`, err);
|
|
264
268
|
}
|
|
265
269
|
}
|
|
266
270
|
/**
|
|
@@ -270,8 +274,10 @@ export class McpContext {
|
|
|
270
274
|
async #setupWebMCPAutoDetectionForAllPages() {
|
|
271
275
|
for (const page of this.#pages) {
|
|
272
276
|
this.#setupWebMCPAutoDetection(page);
|
|
273
|
-
//
|
|
274
|
-
|
|
277
|
+
// Try to connect immediately (don't await - run in parallel for all pages)
|
|
278
|
+
this.#tryConnectWebMCP(page).catch(err => {
|
|
279
|
+
this.logger('Initial WebMCP connection attempt failed (expected):', err);
|
|
280
|
+
});
|
|
275
281
|
}
|
|
276
282
|
}
|
|
277
283
|
/**
|
|
@@ -358,6 +364,33 @@ export class McpContext {
|
|
|
358
364
|
this.#setupWebMCPAutoDetection(page);
|
|
359
365
|
return page;
|
|
360
366
|
}
|
|
367
|
+
async newWindow() {
|
|
368
|
+
// Use CDP to create a new browser window instead of just a tab
|
|
369
|
+
const browserTarget = this.browser.target();
|
|
370
|
+
const cdpSession = await browserTarget.createCDPSession();
|
|
371
|
+
// Create a new target with newWindow: true
|
|
372
|
+
const { targetId } = await cdpSession.send('Target.createTarget', {
|
|
373
|
+
url: 'about:blank',
|
|
374
|
+
newWindow: true,
|
|
375
|
+
});
|
|
376
|
+
await cdpSession.detach();
|
|
377
|
+
// Wait for the new page to be available
|
|
378
|
+
const target = await this.browser.waitForTarget(target => {
|
|
379
|
+
// @ts-expect-error _targetId is internal but stable
|
|
380
|
+
return target._targetId === targetId;
|
|
381
|
+
}, { timeout: 5000 });
|
|
382
|
+
const page = await target.page();
|
|
383
|
+
if (!page) {
|
|
384
|
+
throw new Error('Failed to get page from new window target');
|
|
385
|
+
}
|
|
386
|
+
await this.createPagesSnapshot();
|
|
387
|
+
this.selectPage(page);
|
|
388
|
+
this.#networkCollector.addPage(page);
|
|
389
|
+
this.#consoleCollector.addPage(page);
|
|
390
|
+
// Set up WebMCP auto-detection for the new page
|
|
391
|
+
this.#setupWebMCPAutoDetection(page);
|
|
392
|
+
return page;
|
|
393
|
+
}
|
|
361
394
|
async closePage(pageIdx) {
|
|
362
395
|
if (this.#pages.length === 1) {
|
|
363
396
|
throw new Error(CLOSE_PAGE_ERROR);
|
|
@@ -547,7 +580,7 @@ export class McpContext {
|
|
|
547
580
|
}
|
|
548
581
|
}
|
|
549
582
|
getPages() {
|
|
550
|
-
return this.#pages;
|
|
583
|
+
return [...this.#pages];
|
|
551
584
|
}
|
|
552
585
|
getDevToolsPage(page) {
|
|
553
586
|
return this.#pageToDevToolsPage.get(page);
|
|
@@ -716,17 +749,12 @@ export class McpContext {
|
|
|
716
749
|
}
|
|
717
750
|
this.#webMCPConnections.delete(targetPage);
|
|
718
751
|
}
|
|
719
|
-
//
|
|
720
|
-
const hasWebMCP = await this.#checkWebMCPAvailable(targetPage);
|
|
721
|
-
if (!hasWebMCP) {
|
|
722
|
-
return { connected: false, error: 'WebMCP not detected on this page' };
|
|
723
|
-
}
|
|
724
|
-
// Connect
|
|
752
|
+
// Connect - no pre-checking needed (extension approach)
|
|
725
753
|
try {
|
|
726
754
|
const transport = new WebMCPClientTransport({
|
|
727
755
|
page: targetPage,
|
|
728
|
-
readyTimeout:
|
|
729
|
-
requireWebMCP: false, //
|
|
756
|
+
readyTimeout: 30000, // 30s to handle slow React apps (up from 10s)
|
|
757
|
+
requireWebMCP: false, // Don't pre-check, just try to connect
|
|
730
758
|
});
|
|
731
759
|
const client = new Client({ name: 'chrome-devtools-mcp', version: '1.0.0' }, { capabilities: {} });
|
|
732
760
|
// Set up onclose handler to clean up connection state
|
|
@@ -775,19 +803,6 @@ export class McpContext {
|
|
|
775
803
|
};
|
|
776
804
|
}
|
|
777
805
|
}
|
|
778
|
-
/**
|
|
779
|
-
* Check if WebMCP is available on a page by checking the bridge's hasWebMCP() method.
|
|
780
|
-
* The bridge is auto-injected into all pages, so we just need to check if it detected WebMCP.
|
|
781
|
-
*/
|
|
782
|
-
async #checkWebMCPAvailable(page) {
|
|
783
|
-
try {
|
|
784
|
-
const result = await page.evaluate(CHECK_WEBMCP_AVAILABLE_SCRIPT);
|
|
785
|
-
return result.available;
|
|
786
|
-
}
|
|
787
|
-
catch {
|
|
788
|
-
return false;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
806
|
/**
|
|
792
807
|
* We need to ignore favicon request as they make our test flaky
|
|
793
808
|
*/
|
package/build/src/McpResponse.js
CHANGED
|
@@ -17,6 +17,7 @@ export class McpResponse {
|
|
|
17
17
|
#attachedConsoleMessageId;
|
|
18
18
|
#textResponseLines = [];
|
|
19
19
|
#images = [];
|
|
20
|
+
#isError = false;
|
|
20
21
|
#networkRequestsOptions;
|
|
21
22
|
#consoleDataOptions;
|
|
22
23
|
#devToolsData;
|
|
@@ -96,6 +97,12 @@ export class McpResponse {
|
|
|
96
97
|
appendResponseLine(value) {
|
|
97
98
|
this.#textResponseLines.push(value);
|
|
98
99
|
}
|
|
100
|
+
setIsError(value) {
|
|
101
|
+
this.#isError = value;
|
|
102
|
+
}
|
|
103
|
+
get isError() {
|
|
104
|
+
return this.#isError;
|
|
105
|
+
}
|
|
99
106
|
attachImage(value) {
|
|
100
107
|
this.#images.push(value);
|
|
101
108
|
}
|
package/build/src/index.js
CHANGED
|
File without changes
|
package/build/src/main.js
CHANGED
|
@@ -12,7 +12,7 @@ import { logger, saveLogsToFile } from './logger.js';
|
|
|
12
12
|
import { McpContext } from './McpContext.js';
|
|
13
13
|
import { McpResponse } from './McpResponse.js';
|
|
14
14
|
import { Mutex } from './Mutex.js';
|
|
15
|
-
import { McpServer, StdioServerTransport, SetLevelRequestSchema, } from './third_party/index.js';
|
|
15
|
+
import { McpServer, StdioServerTransport, SetLevelRequestSchema, zod, } from './third_party/index.js';
|
|
16
16
|
import { registerPrompts } from './prompts/index.js';
|
|
17
17
|
import { ToolCategory } from './tools/categories.js';
|
|
18
18
|
import { tools } from './tools/tools.js';
|
|
@@ -120,6 +120,10 @@ async function getContext() {
|
|
|
120
120
|
experimentalDevToolsDebugging: devtools,
|
|
121
121
|
experimentalIncludeAllPages: args.experimentalIncludeAllPages,
|
|
122
122
|
});
|
|
123
|
+
// Always create a new window for this MCP session
|
|
124
|
+
// This ensures multiple MCP clients don't step on each other's toes
|
|
125
|
+
await context.newWindow();
|
|
126
|
+
logger('Created new window for this MCP session');
|
|
123
127
|
// Initialize WebMCP tool hub for dynamic tool registration
|
|
124
128
|
const toolHub = new WebMCPToolHub(server, context);
|
|
125
129
|
context.setToolHub(toolHub);
|
|
@@ -164,7 +168,10 @@ function registerTool(tool) {
|
|
|
164
168
|
}
|
|
165
169
|
server.registerTool(tool.name, {
|
|
166
170
|
description: tool.description,
|
|
167
|
-
|
|
171
|
+
// For call_webmcp_tool, use a fully permissive schema to allow any properties
|
|
172
|
+
inputSchema: tool.name === 'call_webmcp_tool'
|
|
173
|
+
? zod.record(zod.unknown())
|
|
174
|
+
: zod.object(tool.schema).passthrough(),
|
|
168
175
|
annotations: tool.annotations,
|
|
169
176
|
}, async (params) => {
|
|
170
177
|
const guard = await toolMutex.acquire();
|
|
@@ -178,9 +185,13 @@ function registerTool(tool) {
|
|
|
178
185
|
params,
|
|
179
186
|
}, response, context);
|
|
180
187
|
const content = await response.handle(tool.name, context);
|
|
181
|
-
|
|
188
|
+
const result = {
|
|
182
189
|
content,
|
|
183
190
|
};
|
|
191
|
+
if (response.isError) {
|
|
192
|
+
result.isError = true;
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
184
195
|
}
|
|
185
196
|
catch (err) {
|
|
186
197
|
logger(`${tool.name} error:`, err, err?.stack);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
let cachedPolyfill = null;
|
|
10
|
+
/**
|
|
11
|
+
* Load the @mcp-b/global IIFE polyfill code.
|
|
12
|
+
* This polyfill provides the navigator.modelContext API for pages that don't have WebMCP.
|
|
13
|
+
* Cached after first load for performance.
|
|
14
|
+
*
|
|
15
|
+
* @returns The IIFE JavaScript code as a string
|
|
16
|
+
* @throws Error if the polyfill file cannot be found or read
|
|
17
|
+
*/
|
|
18
|
+
export function getPolyfillCode() {
|
|
19
|
+
if (cachedPolyfill) {
|
|
20
|
+
return cachedPolyfill;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
// Use Node's module resolution to find the package regardless of
|
|
24
|
+
// workspace structure or installation method (pnpm, npm, yarn).
|
|
25
|
+
// Use the package's exported subpath '@mcp-b/global/iife' which resolves
|
|
26
|
+
// to the IIFE bundle via the exports field in package.json.
|
|
27
|
+
const polyfillPath = require.resolve('@mcp-b/global/iife');
|
|
28
|
+
cachedPolyfill = readFileSync(polyfillPath, 'utf-8');
|
|
29
|
+
return cachedPolyfill;
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
33
|
+
// Provide actionable error message
|
|
34
|
+
throw new Error(`Could not find @mcp-b/global polyfill: ${message}\n` +
|
|
35
|
+
'Run `pnpm build` in the global package or ensure @mcp-b/global is installed.');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Clear the cached polyfill code, forcing the next getPolyfillCode() call
|
|
40
|
+
* to reload from disk. Useful for testing or after @mcp-b/global updates.
|
|
41
|
+
*/
|
|
42
|
+
export function clearPolyfillCache() {
|
|
43
|
+
cachedPolyfill = null;
|
|
44
|
+
}
|
|
@@ -27,7 +27,7 @@ export function registerPrompts(server) {
|
|
|
27
27
|
1. **Write the Tool**: I'll ask you to create a WebMCP tool in my codebase using @mcp-b/global
|
|
28
28
|
2. **Hot Reload**: My dev server will automatically reload with the new tool
|
|
29
29
|
3. **Navigate**: Use navigate_page to open my dev server (e.g., http://localhost:3000)
|
|
30
|
-
4. **Discover**: Use
|
|
30
|
+
4. **Discover**: Use list_webmcp_tools to see registered tools (shown with callable names like webmcp_localhost_3000_page0_my_tool)
|
|
31
31
|
5. **Test**: Call the tool directly by its prefixed name (e.g., webmcp_localhost_3000_page0_my_tool)
|
|
32
32
|
6. **Iterate**: If something is wrong, fix the code and repeat
|
|
33
33
|
|
|
@@ -92,7 +92,7 @@ What would you like to build? Describe the tool you need and I'll help you imple
|
|
|
92
92
|
## Test Plan
|
|
93
93
|
|
|
94
94
|
1. Navigate to ${url}
|
|
95
|
-
2. Use
|
|
95
|
+
2. Use list_webmcp_tools to discover registered tools (shown with callable names like webmcp_localhost_3000_page0_tool_name)
|
|
96
96
|
3. ${toolNameInstruction}
|
|
97
97
|
4. Call the tool directly by its prefixed name and test with various inputs:
|
|
98
98
|
- Valid inputs (happy path)
|
|
@@ -138,7 +138,7 @@ ${urlInstruction}
|
|
|
138
138
|
- Errors loading @mcp-b/global
|
|
139
139
|
- Tool registration errors
|
|
140
140
|
- Any JavaScript errors
|
|
141
|
-
3. **Test WebMCP**: Try
|
|
141
|
+
3. **Test WebMCP**: Try list_webmcp_tools to see if connection works
|
|
142
142
|
4. **Verify registration**: If no tools appear, check if the code properly imports '@mcp-b/global' and calls registerTool
|
|
143
143
|
|
|
144
144
|
## Common Issues
|