@mcp-b/chrome-devtools-mcp 1.3.1 → 1.4.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 +32 -44
- package/build/src/McpResponse.js +7 -0
- package/build/src/cli.js +1 -1
- package/build/src/main.js +10 -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 +713 -2
- 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 +6 -4
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
|
/**
|
|
@@ -547,7 +553,7 @@ export class McpContext {
|
|
|
547
553
|
}
|
|
548
554
|
}
|
|
549
555
|
getPages() {
|
|
550
|
-
return this.#pages;
|
|
556
|
+
return [...this.#pages];
|
|
551
557
|
}
|
|
552
558
|
getDevToolsPage(page) {
|
|
553
559
|
return this.#pageToDevToolsPage.get(page);
|
|
@@ -716,17 +722,12 @@ export class McpContext {
|
|
|
716
722
|
}
|
|
717
723
|
this.#webMCPConnections.delete(targetPage);
|
|
718
724
|
}
|
|
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
|
|
725
|
+
// Connect - no pre-checking needed (extension approach)
|
|
725
726
|
try {
|
|
726
727
|
const transport = new WebMCPClientTransport({
|
|
727
728
|
page: targetPage,
|
|
728
|
-
readyTimeout:
|
|
729
|
-
requireWebMCP: false, //
|
|
729
|
+
readyTimeout: 30000, // 30s to handle slow React apps (up from 10s)
|
|
730
|
+
requireWebMCP: false, // Don't pre-check, just try to connect
|
|
730
731
|
});
|
|
731
732
|
const client = new Client({ name: 'chrome-devtools-mcp', version: '1.0.0' }, { capabilities: {} });
|
|
732
733
|
// Set up onclose handler to clean up connection state
|
|
@@ -775,19 +776,6 @@ export class McpContext {
|
|
|
775
776
|
};
|
|
776
777
|
}
|
|
777
778
|
}
|
|
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
779
|
/**
|
|
792
780
|
* We need to ignore favicon request as they make our test flaky
|
|
793
781
|
*/
|
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/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ export const cliOptions = {
|
|
|
8
8
|
autoConnect: {
|
|
9
9
|
type: 'boolean',
|
|
10
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
|
-
default:
|
|
11
|
+
default: false,
|
|
12
12
|
coerce: (value) => {
|
|
13
13
|
if (value === false) {
|
|
14
14
|
return false;
|
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';
|
|
@@ -164,7 +164,10 @@ function registerTool(tool) {
|
|
|
164
164
|
}
|
|
165
165
|
server.registerTool(tool.name, {
|
|
166
166
|
description: tool.description,
|
|
167
|
-
|
|
167
|
+
// For call_webmcp_tool, use a fully permissive schema to allow any properties
|
|
168
|
+
inputSchema: tool.name === 'call_webmcp_tool'
|
|
169
|
+
? zod.record(zod.unknown())
|
|
170
|
+
: zod.object(tool.schema).passthrough(),
|
|
168
171
|
annotations: tool.annotations,
|
|
169
172
|
}, async (params) => {
|
|
170
173
|
const guard = await toolMutex.acquire();
|
|
@@ -178,9 +181,13 @@ function registerTool(tool) {
|
|
|
178
181
|
params,
|
|
179
182
|
}, response, context);
|
|
180
183
|
const content = await response.handle(tool.name, context);
|
|
181
|
-
|
|
184
|
+
const result = {
|
|
182
185
|
content,
|
|
183
186
|
};
|
|
187
|
+
if (response.isError) {
|
|
188
|
+
result.isError = true;
|
|
189
|
+
}
|
|
190
|
+
return result;
|
|
184
191
|
}
|
|
185
192
|
catch (err) {
|
|
186
193
|
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
|