@mcp-b/chrome-devtools-mcp 1.5.8 → 1.6.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/build/src/McpContext.js +102 -8
- package/build/src/McpResponse.js +9 -0
- package/build/src/main.js +11 -12
- package/build/src/third_party/index.js +1 -1
- package/build/src/tools/webmcp.js +161 -23
- package/package.json +3 -3
package/build/src/McpContext.js
CHANGED
|
@@ -98,6 +98,13 @@ export class McpContext {
|
|
|
98
98
|
#toolHub;
|
|
99
99
|
/** Tracks pages that have WebMCP auto-detection listeners installed. */
|
|
100
100
|
#pagesWithWebMCPListeners = new WeakSet();
|
|
101
|
+
/**
|
|
102
|
+
* The windowId that this MCP session owns.
|
|
103
|
+
* When set, page operations are scoped to only pages in this window.
|
|
104
|
+
*/
|
|
105
|
+
#sessionWindowId;
|
|
106
|
+
/** Cached browser-level CDP session for window operations. */
|
|
107
|
+
#browserCdpSession;
|
|
101
108
|
constructor(browser, logger, options, locatorClass) {
|
|
102
109
|
this.browser = browser;
|
|
103
110
|
this.logger = logger;
|
|
@@ -193,6 +200,41 @@ export class McpContext {
|
|
|
193
200
|
getToolHub() {
|
|
194
201
|
return this.#toolHub;
|
|
195
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Get or create a browser-level CDP session for window operations.
|
|
205
|
+
*/
|
|
206
|
+
async #getBrowserCdpSession() {
|
|
207
|
+
if (!this.#browserCdpSession) {
|
|
208
|
+
this.#browserCdpSession = await this.browser.target().createCDPSession();
|
|
209
|
+
}
|
|
210
|
+
return this.#browserCdpSession;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get the windowId for a given page using CDP.
|
|
214
|
+
*/
|
|
215
|
+
async getWindowIdForPage(page) {
|
|
216
|
+
const cdpSession = await this.#getBrowserCdpSession();
|
|
217
|
+
// @ts-expect-error _targetId is internal but stable
|
|
218
|
+
const targetId = page.target()._targetId;
|
|
219
|
+
const { windowId } = await cdpSession.send('Browser.getWindowForTarget', {
|
|
220
|
+
targetId,
|
|
221
|
+
});
|
|
222
|
+
return windowId;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Set the window that this session owns.
|
|
226
|
+
* When set, page operations are scoped to only pages in this window.
|
|
227
|
+
*/
|
|
228
|
+
setSessionWindowId(windowId) {
|
|
229
|
+
this.#sessionWindowId = windowId;
|
|
230
|
+
this.logger(`Session bound to windowId: ${windowId}`);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get the session's window ID, or undefined if not set.
|
|
234
|
+
*/
|
|
235
|
+
getSessionWindowId() {
|
|
236
|
+
return this.#sessionWindowId;
|
|
237
|
+
}
|
|
196
238
|
/**
|
|
197
239
|
* Set up automatic WebMCP detection for a page.
|
|
198
240
|
* This installs listeners that detect WebMCP after navigation and sync tools.
|
|
@@ -361,7 +403,34 @@ export class McpContext {
|
|
|
361
403
|
return this.#consoleCollector.getById(this.getSelectedPage(), id);
|
|
362
404
|
}
|
|
363
405
|
async newPage() {
|
|
406
|
+
// If we have a session window, ensure our window is focused first
|
|
407
|
+
// This increases the chance that Chrome creates the new tab in our window
|
|
408
|
+
if (this.#sessionWindowId !== undefined && this.#pages.length > 0) {
|
|
409
|
+
try {
|
|
410
|
+
const existingPage = this.#pages[0];
|
|
411
|
+
if (existingPage) {
|
|
412
|
+
await existingPage.bringToFront();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
// Best effort - focus might fail if page is closing
|
|
417
|
+
}
|
|
418
|
+
}
|
|
364
419
|
const page = await this.browser.newPage();
|
|
420
|
+
// Verify the new page is in our window (if session scoping is active)
|
|
421
|
+
if (this.#sessionWindowId !== undefined) {
|
|
422
|
+
try {
|
|
423
|
+
const newPageWindowId = await this.getWindowIdForPage(page);
|
|
424
|
+
if (newPageWindowId !== this.#sessionWindowId) {
|
|
425
|
+
// New tab went to wrong window - this is a known Chrome behavior issue
|
|
426
|
+
this.logger(`Warning: new_page created tab in window ${newPageWindowId} instead of session window ${this.#sessionWindowId}. ` +
|
|
427
|
+
`Tab may not be visible in list_pages.`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
// Failed to get windowId - page might be in an unexpected state
|
|
432
|
+
}
|
|
433
|
+
}
|
|
365
434
|
await this.createPagesSnapshot();
|
|
366
435
|
// Mark as explicitly selected so this session sticks to this page
|
|
367
436
|
this.selectPage(page, true);
|
|
@@ -389,12 +458,12 @@ export class McpContext {
|
|
|
389
458
|
if (!page) {
|
|
390
459
|
throw new Error('Failed to get page from new window target');
|
|
391
460
|
}
|
|
461
|
+
// Get window ID for this target (required for session scoping)
|
|
462
|
+
const { windowId } = await cdpSession.send('Browser.getWindowForTarget', {
|
|
463
|
+
targetId,
|
|
464
|
+
});
|
|
392
465
|
// Set window to nearly full screen (large size that fits most displays)
|
|
393
466
|
try {
|
|
394
|
-
// Get window ID for this target
|
|
395
|
-
const { windowId } = await cdpSession.send('Browser.getWindowForTarget', {
|
|
396
|
-
targetId,
|
|
397
|
-
});
|
|
398
467
|
// Set to large dimensions (works well on 1920x1080 and larger displays)
|
|
399
468
|
// This is ~95% of common display sizes without being truly fullscreen
|
|
400
469
|
await cdpSession.send('Browser.setWindowBounds', {
|
|
@@ -420,7 +489,7 @@ export class McpContext {
|
|
|
420
489
|
this.#consoleCollector.addPage(page);
|
|
421
490
|
// Set up WebMCP auto-detection for the new page
|
|
422
491
|
this.#setupWebMCPAutoDetection(page);
|
|
423
|
-
return page;
|
|
492
|
+
return { page, windowId };
|
|
424
493
|
}
|
|
425
494
|
async closePage(pageIdx) {
|
|
426
495
|
if (this.#pages.length === 1) {
|
|
@@ -558,15 +627,40 @@ export class McpContext {
|
|
|
558
627
|
}
|
|
559
628
|
/**
|
|
560
629
|
* Creates a snapshot of the pages.
|
|
630
|
+
* If a sessionWindowId is set, only pages from that window are included.
|
|
561
631
|
*/
|
|
562
632
|
async createPagesSnapshot() {
|
|
563
633
|
const allPages = await this.browser.pages(this.#options.experimentalIncludeAllPages);
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
// If we are in regular mode, the user should only see non-DevTools page.
|
|
634
|
+
// First filter: DevTools pages (unless experimental mode is enabled)
|
|
635
|
+
let filteredPages = allPages.filter(page => {
|
|
567
636
|
return (this.#options.experimentalDevToolsDebugging ||
|
|
568
637
|
!page.url().startsWith('devtools://'));
|
|
569
638
|
});
|
|
639
|
+
// Second filter: Session window scoping
|
|
640
|
+
// If we have a sessionWindowId, only include pages from that window
|
|
641
|
+
if (this.#sessionWindowId !== undefined) {
|
|
642
|
+
const windowFilteredPages = [];
|
|
643
|
+
// Check window IDs in parallel for better performance
|
|
644
|
+
const windowIdResults = await Promise.allSettled(filteredPages.map(async (page) => {
|
|
645
|
+
try {
|
|
646
|
+
const windowId = await this.getWindowIdForPage(page);
|
|
647
|
+
return { page, windowId };
|
|
648
|
+
}
|
|
649
|
+
catch {
|
|
650
|
+
// Page might be closing, exclude it
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
}));
|
|
654
|
+
for (const result of windowIdResults) {
|
|
655
|
+
if (result.status === 'fulfilled' &&
|
|
656
|
+
result.value &&
|
|
657
|
+
result.value.windowId === this.#sessionWindowId) {
|
|
658
|
+
windowFilteredPages.push(result.value.page);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
filteredPages = windowFilteredPages;
|
|
662
|
+
}
|
|
663
|
+
this.#pages = filteredPages;
|
|
570
664
|
// Only auto-select pages[0] if:
|
|
571
665
|
// 1. No page has been explicitly selected for this session AND
|
|
572
666
|
// 2. Either there's no selected page OR the selected page is no longer valid
|
package/build/src/McpResponse.js
CHANGED
|
@@ -310,6 +310,15 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
310
310
|
}
|
|
311
311
|
else {
|
|
312
312
|
response.push('<no console messages found>');
|
|
313
|
+
// Provide helpful hint about preserved messages if not already enabled
|
|
314
|
+
if (!this.#consoleDataOptions.includePreservedMessages) {
|
|
315
|
+
response.push('');
|
|
316
|
+
response.push('Tip: Use includePreservedMessages: true to see messages from previous navigations.');
|
|
317
|
+
}
|
|
318
|
+
// Provide hint about type filtering if specified
|
|
319
|
+
if (this.#consoleDataOptions.types?.length) {
|
|
320
|
+
response.push(`(Filtering by types: ${this.#consoleDataOptions.types.join(', ')})`);
|
|
321
|
+
}
|
|
313
322
|
}
|
|
314
323
|
}
|
|
315
324
|
const text = {
|
package/build/src/main.js
CHANGED
|
@@ -127,17 +127,15 @@ async function getContext() {
|
|
|
127
127
|
// Fresh browser launch - use the existing default page
|
|
128
128
|
// Mark it as explicitly selected so this session stays pinned to it
|
|
129
129
|
context.selectPage(context.getSelectedPage(), true);
|
|
130
|
-
|
|
131
|
-
// Resize the window to nearly full screen
|
|
130
|
+
// Capture windowId for session scoping and resize the window
|
|
132
131
|
try {
|
|
133
132
|
const page = context.getSelectedPage();
|
|
133
|
+
const windowId = await context.getWindowIdForPage(page);
|
|
134
|
+
context.setSessionWindowId(windowId);
|
|
135
|
+
logger(`Using existing window for this MCP session, windowId: ${windowId}`);
|
|
136
|
+
// Resize to nearly full screen
|
|
134
137
|
const browserTarget = browser.target();
|
|
135
138
|
const cdpSession = await browserTarget.createCDPSession();
|
|
136
|
-
// @ts-expect-error _targetId is internal but stable
|
|
137
|
-
const targetId = page.target()._targetId;
|
|
138
|
-
const { windowId } = await cdpSession.send('Browser.getWindowForTarget', {
|
|
139
|
-
targetId,
|
|
140
|
-
});
|
|
141
139
|
await cdpSession.send('Browser.setWindowBounds', {
|
|
142
140
|
windowId,
|
|
143
141
|
bounds: {
|
|
@@ -152,15 +150,16 @@ async function getContext() {
|
|
|
152
150
|
logger('Resized window to nearly full screen');
|
|
153
151
|
}
|
|
154
152
|
catch (err) {
|
|
155
|
-
// Non-fatal: window sizing is best-effort
|
|
156
|
-
logger('Failed to resize window:', err);
|
|
153
|
+
// Non-fatal: window sizing is best-effort, but windowId capture is important
|
|
154
|
+
logger('Failed to capture windowId or resize window:', err);
|
|
157
155
|
}
|
|
158
156
|
}
|
|
159
157
|
else {
|
|
160
158
|
// Connected to existing browser - create new window for isolation
|
|
161
159
|
// This ensures multiple MCP clients don't step on each other's toes
|
|
162
|
-
await context.newWindow();
|
|
163
|
-
|
|
160
|
+
const { windowId } = await context.newWindow();
|
|
161
|
+
context.setSessionWindowId(windowId);
|
|
162
|
+
logger(`Created new window for this MCP session, windowId: ${windowId}`);
|
|
164
163
|
}
|
|
165
164
|
// Initialize WebMCP tool hub for dynamic tool registration
|
|
166
165
|
const toolHub = new WebMCPToolHub(server, context);
|
|
@@ -206,7 +205,7 @@ function registerTool(tool) {
|
|
|
206
205
|
}
|
|
207
206
|
server.registerTool(tool.name, {
|
|
208
207
|
description: tool.description,
|
|
209
|
-
inputSchema: zod.object(tool.schema).
|
|
208
|
+
inputSchema: zod.object(tool.schema).strict(),
|
|
210
209
|
annotations: tool.annotations,
|
|
211
210
|
}, async (params) => {
|
|
212
211
|
const guard = await toolMutex.acquire();
|
|
@@ -11,6 +11,6 @@ export { default as debug } from 'debug';
|
|
|
11
11
|
export { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
12
|
export { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
13
13
|
export { SetLevelRequestSchema, ToolListChangedNotificationSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
14
|
-
export { z as zod } from 'zod';
|
|
14
|
+
export { z as zod } from 'zod/v4';
|
|
15
15
|
export { Locator, PredefinedNetworkConditions, CDPSessionEvent, } from 'puppeteer-core';
|
|
16
16
|
export { default as puppeteer } from 'puppeteer-core';
|
|
@@ -78,40 +78,133 @@ import { defineTool } from './ToolDefinition.js';
|
|
|
78
78
|
// response.appendResponseLine(' 2. take_snapshot - verify page state');
|
|
79
79
|
// }
|
|
80
80
|
/**
|
|
81
|
-
*
|
|
81
|
+
* Convert a glob-style pattern to a RegExp.
|
|
82
|
+
* Supports * (any chars) and ? (single char).
|
|
83
|
+
*/
|
|
84
|
+
function globToRegex(pattern) {
|
|
85
|
+
const escaped = pattern
|
|
86
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape regex special chars except * and ?
|
|
87
|
+
.replace(/\*/g, '.*') // * -> .*
|
|
88
|
+
.replace(/\?/g, '.'); // ? -> .
|
|
89
|
+
return new RegExp(`^${escaped}$`, 'i'); // Case insensitive, full match
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* List WebMCP tools registered on browser pages.
|
|
93
|
+
* By default returns tools from the currently selected page only.
|
|
94
|
+
* Use all_pages=true to see tools from all tabs.
|
|
82
95
|
*/
|
|
83
96
|
export const listWebMCPTools = defineTool({
|
|
84
97
|
name: 'list_webmcp_tools',
|
|
85
|
-
description: 'List
|
|
86
|
-
'
|
|
87
|
-
'
|
|
98
|
+
description: 'List WebMCP tools registered on browser pages. ' +
|
|
99
|
+
'By default, returns tools from the currently selected page only. ' +
|
|
100
|
+
'Use all_pages=true to see tools from all tabs. ' +
|
|
101
|
+
'Returns tool definitions including name, description, input schema, and page index. ' +
|
|
102
|
+
'Use call_webmcp_tool to invoke a tool.',
|
|
88
103
|
annotations: {
|
|
89
104
|
title: 'List Website MCP Tools',
|
|
90
105
|
category: ToolCategory.WEBMCP,
|
|
91
106
|
readOnlyHint: true,
|
|
92
107
|
},
|
|
93
|
-
schema: {
|
|
94
|
-
|
|
108
|
+
schema: {
|
|
109
|
+
page_index: zod
|
|
110
|
+
.number()
|
|
111
|
+
.int()
|
|
112
|
+
.optional()
|
|
113
|
+
.describe('Only show tools from this specific page index'),
|
|
114
|
+
all_pages: zod
|
|
115
|
+
.boolean()
|
|
116
|
+
.optional()
|
|
117
|
+
.describe('If true, return tools from all pages instead of just the selected page (default: false)'),
|
|
118
|
+
pattern: zod
|
|
119
|
+
.string()
|
|
120
|
+
.optional()
|
|
121
|
+
.describe('Glob pattern to filter tool names (e.g., "skill*", "*_config")'),
|
|
122
|
+
summary: zod
|
|
123
|
+
.boolean()
|
|
124
|
+
.optional()
|
|
125
|
+
.describe('If true, return only name and first line of description, omitting full schemas (default: false)'),
|
|
126
|
+
},
|
|
127
|
+
handler: async (request, response, context) => {
|
|
128
|
+
const { page_index, all_pages, pattern, summary } = request.params;
|
|
95
129
|
const toolHub = context.getToolHub();
|
|
96
130
|
if (!toolHub) {
|
|
97
131
|
response.appendResponseLine('WebMCPToolHub not initialized.');
|
|
98
132
|
return;
|
|
99
133
|
}
|
|
100
|
-
|
|
134
|
+
let tools = toolHub.getRegisteredTools();
|
|
135
|
+
// If no tools found, try connecting to WebMCP on the current page
|
|
136
|
+
// This handles cases where auto-detection is still in progress or timed out
|
|
137
|
+
if (tools.length === 0) {
|
|
138
|
+
const page = context.getSelectedPage();
|
|
139
|
+
const result = await context.getWebMCPClient(page);
|
|
140
|
+
if (result.connected) {
|
|
141
|
+
// Re-fetch tools after connection (sync happens in getWebMCPClient)
|
|
142
|
+
tools = toolHub.getRegisteredTools();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Determine which page(s) to show
|
|
146
|
+
const selectedPageIdx = context.getPages().indexOf(context.getSelectedPage());
|
|
147
|
+
// Filter by page
|
|
148
|
+
if (page_index !== undefined) {
|
|
149
|
+
// Specific page requested
|
|
150
|
+
tools = tools.filter(t => t.pageIdx === page_index);
|
|
151
|
+
}
|
|
152
|
+
else if (!all_pages) {
|
|
153
|
+
// Default: selected page only
|
|
154
|
+
tools = tools.filter(t => t.pageIdx === selectedPageIdx);
|
|
155
|
+
}
|
|
156
|
+
// else: all_pages=true, show everything
|
|
157
|
+
// Filter by pattern
|
|
158
|
+
if (pattern) {
|
|
159
|
+
const regex = globToRegex(pattern);
|
|
160
|
+
tools = tools.filter(t => regex.test(t.originalName));
|
|
161
|
+
}
|
|
101
162
|
if (tools.length === 0) {
|
|
102
|
-
|
|
163
|
+
const filters = [];
|
|
164
|
+
if (page_index !== undefined) {
|
|
165
|
+
filters.push(`page_index=${page_index}`);
|
|
166
|
+
}
|
|
167
|
+
else if (!all_pages) {
|
|
168
|
+
filters.push(`selected page (${selectedPageIdx})`);
|
|
169
|
+
}
|
|
170
|
+
if (pattern) {
|
|
171
|
+
filters.push(`pattern="${pattern}"`);
|
|
172
|
+
}
|
|
173
|
+
const filterMsg = filters.length > 0 ? ` (filters: ${filters.join(', ')})` : '';
|
|
174
|
+
response.appendResponseLine(`No WebMCP tools found${filterMsg}.`);
|
|
175
|
+
if (!all_pages && page_index === undefined) {
|
|
176
|
+
response.appendResponseLine('');
|
|
177
|
+
response.appendResponseLine('Tip: Use all_pages=true to search across all pages.');
|
|
178
|
+
}
|
|
179
|
+
response.appendResponseLine('');
|
|
103
180
|
response.appendResponseLine('Navigate to a page with @mcp-b/global loaded to discover tools.');
|
|
104
181
|
return;
|
|
105
182
|
}
|
|
106
|
-
// Format
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
183
|
+
// Format output
|
|
184
|
+
if (summary) {
|
|
185
|
+
// Compact output: name + first line of description
|
|
186
|
+
const toolSummaries = tools.map(tool => {
|
|
187
|
+
const firstLine = tool.description.split('\n')[0].split('. ')[0];
|
|
188
|
+
const truncated = firstLine.length > 60 ? firstLine.slice(0, 57) + '...' : firstLine;
|
|
189
|
+
return {
|
|
190
|
+
name: tool.originalName,
|
|
191
|
+
description: truncated,
|
|
192
|
+
pageIdx: tool.pageIdx,
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
response.appendResponseLine(JSON.stringify({ tools: toolSummaries, count: tools.length }, null, 2));
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
// Full output with schemas
|
|
199
|
+
const toolDefinitions = tools.map(tool => ({
|
|
200
|
+
name: tool.originalName,
|
|
201
|
+
description: tool.description,
|
|
202
|
+
inputSchema: tool.inputSchema,
|
|
203
|
+
pageIdx: tool.pageIdx,
|
|
204
|
+
domain: tool.domain,
|
|
205
|
+
}));
|
|
206
|
+
response.appendResponseLine(JSON.stringify({ tools: toolDefinitions, count: tools.length }, null, 2));
|
|
207
|
+
}
|
|
115
208
|
},
|
|
116
209
|
});
|
|
117
210
|
/**
|
|
@@ -120,10 +213,9 @@ export const listWebMCPTools = defineTool({
|
|
|
120
213
|
*/
|
|
121
214
|
export const callWebMCPTool = defineTool({
|
|
122
215
|
name: 'call_webmcp_tool',
|
|
123
|
-
description: 'Call a tool registered on a webpage
|
|
124
|
-
'
|
|
125
|
-
'Use list_webmcp_tools to see available tools and their schemas.
|
|
126
|
-
'Use page_index to target a specific page.',
|
|
216
|
+
description: 'Call a WebMCP tool registered on a webpage. ' +
|
|
217
|
+
'Auto-connects to the page if needed. ' +
|
|
218
|
+
'Use list_webmcp_tools first to see available tools and their input schemas.',
|
|
127
219
|
annotations: {
|
|
128
220
|
title: 'Call Website MCP Tool',
|
|
129
221
|
category: ToolCategory.WEBMCP,
|
|
@@ -132,9 +224,31 @@ export const callWebMCPTool = defineTool({
|
|
|
132
224
|
schema: {
|
|
133
225
|
name: zod.string().describe('The name of the tool to call'),
|
|
134
226
|
arguments: zod
|
|
135
|
-
.
|
|
227
|
+
.union([
|
|
228
|
+
zod.record(zod.string(), zod.any()),
|
|
229
|
+
zod.string().transform((str, ctx) => {
|
|
230
|
+
try {
|
|
231
|
+
const parsed = JSON.parse(str);
|
|
232
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
233
|
+
ctx.addIssue({
|
|
234
|
+
code: zod.ZodIssueCode.custom,
|
|
235
|
+
message: 'Arguments must be a JSON object, not an array or primitive',
|
|
236
|
+
});
|
|
237
|
+
return zod.NEVER;
|
|
238
|
+
}
|
|
239
|
+
return parsed;
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
ctx.addIssue({
|
|
243
|
+
code: zod.ZodIssueCode.custom,
|
|
244
|
+
message: `Invalid JSON string for arguments: ${str.slice(0, 100)}${str.length > 100 ? '...' : ''}`,
|
|
245
|
+
});
|
|
246
|
+
return zod.NEVER;
|
|
247
|
+
}
|
|
248
|
+
}),
|
|
249
|
+
])
|
|
136
250
|
.optional()
|
|
137
|
-
.describe('Arguments to pass to the tool as a JSON object'),
|
|
251
|
+
.describe('Arguments to pass to the tool as a JSON object (or JSON string that will be parsed)'),
|
|
138
252
|
page_index: zod
|
|
139
253
|
.number()
|
|
140
254
|
.int()
|
|
@@ -166,6 +280,30 @@ export const callWebMCPTool = defineTool({
|
|
|
166
280
|
return;
|
|
167
281
|
}
|
|
168
282
|
const client = result.client;
|
|
283
|
+
// Check if arguments are empty but the tool expects required fields
|
|
284
|
+
const toolHub = context.getToolHub();
|
|
285
|
+
const toolDef = toolHub?.getToolByName(name, page);
|
|
286
|
+
const argsEmpty = !args || Object.keys(args).length === 0;
|
|
287
|
+
if (argsEmpty && toolDef?.inputSchema) {
|
|
288
|
+
const schema = toolDef.inputSchema;
|
|
289
|
+
const requiredFields = schema.required ?? [];
|
|
290
|
+
const properties = schema.properties;
|
|
291
|
+
if (requiredFields.length > 0) {
|
|
292
|
+
response.appendResponseLine(`⚠️ Warning: Calling "${name}" with empty arguments, but tool expects required fields: ${requiredFields.join(', ')}`);
|
|
293
|
+
response.appendResponseLine('');
|
|
294
|
+
// Show the expected schema
|
|
295
|
+
if (properties) {
|
|
296
|
+
response.appendResponseLine('Expected schema:');
|
|
297
|
+
for (const field of requiredFields) {
|
|
298
|
+
const prop = properties[field];
|
|
299
|
+
const typeStr = prop?.type || 'unknown';
|
|
300
|
+
const descStr = prop?.description ? ` - ${prop.description}` : '';
|
|
301
|
+
response.appendResponseLine(` • ${field} (${typeStr})${descStr}`);
|
|
302
|
+
}
|
|
303
|
+
response.appendResponseLine('');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
169
307
|
try {
|
|
170
308
|
const callResult = await client.callTool({
|
|
171
309
|
name,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-b/chrome-devtools-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "MCP server for Chrome DevTools with WebMCP integration for connecting to website MCP tools",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"puppeteer": "24.32.0",
|
|
57
57
|
"puppeteer-core": "24.32.0",
|
|
58
58
|
"yargs": "18.0.0",
|
|
59
|
-
"zod": "3.
|
|
59
|
+
"zod": "4.3.5"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@eslint/js": "^9.35.0",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"typescript": "^5.9.2",
|
|
90
90
|
"typescript-eslint": "^8.43.0",
|
|
91
91
|
"yargs": "18.0.0",
|
|
92
|
-
"zod": "3.
|
|
92
|
+
"zod": "4.3.5"
|
|
93
93
|
},
|
|
94
94
|
"engines": {
|
|
95
95
|
"node": "^20.19.0 || ^22.12.0 || >=23"
|