mcp-web-inspector 0.1.3 → 0.1.4
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 +37 -7
- package/dist/index.js +5 -0
- package/dist/toolHandler.d.ts +6 -1
- package/dist/toolHandler.js +14 -4
- package/dist/tools/browser/ancestorInspection.d.ts +2 -0
- package/dist/tools/browser/ancestorInspection.js +128 -8
- package/dist/tools/browser/interaction.js +2 -5
- package/dist/tools/browser/visiblePage.js +80 -68
- package/dist/tools.d.ts +18 -28
- package/dist/tools.js +27 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -289,6 +289,7 @@ Customize server behavior with command line flags:
|
|
|
289
289
|
|
|
290
290
|
- **`--no-save-session`** - Disable automatic session persistence (start with fresh browser state each time)
|
|
291
291
|
- **`--user-data-dir <path>`** - Custom directory for session data (default: `./.mcp-web-inspector`)
|
|
292
|
+
- **`--headless`** - Run browser in headless mode by default (no visible window)
|
|
292
293
|
|
|
293
294
|
**Example usage:**
|
|
294
295
|
```json
|
|
@@ -302,6 +303,30 @@ Customize server behavior with command line flags:
|
|
|
302
303
|
}
|
|
303
304
|
```
|
|
304
305
|
|
|
306
|
+
**Run in headless mode for automation/CI:**
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"mcpServers": {
|
|
310
|
+
"web-inspector": {
|
|
311
|
+
"command": "npx",
|
|
312
|
+
"args": ["-y", "mcp-web-inspector", "--headless"]
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Combine multiple flags:**
|
|
319
|
+
```json
|
|
320
|
+
{
|
|
321
|
+
"mcpServers": {
|
|
322
|
+
"web-inspector": {
|
|
323
|
+
"command": "npx",
|
|
324
|
+
"args": ["-y", "mcp-web-inspector", "--headless", "--no-save-session"]
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
305
330
|
---
|
|
306
331
|
|
|
307
332
|
## Session Persistence & Data Storage
|
|
@@ -395,8 +420,8 @@ rm -rf ./.mcp-web-inspector/screenshots # Clear screenshots only
|
|
|
395
420
|
- Session files can be large and bloat your git history
|
|
396
421
|
|
|
397
422
|
**Best practices:**
|
|
398
|
-
-
|
|
399
|
-
- Use `headless:
|
|
423
|
+
- **Default is visible browser** (`headless: false`) for interactive debugging
|
|
424
|
+
- Use `headless: true` explicitly for automation and CI/CD environments
|
|
400
425
|
- Clear session data after testing sensitive applications
|
|
401
426
|
- Use `--no-save-session` flag when testing on shared/public sites
|
|
402
427
|
|
|
@@ -570,11 +595,16 @@ Ultra-lightweight existence check. Returns simple ✓ exists or ✗ not found st
|
|
|
570
595
|
Navigate to a URL with full browser configuration options.
|
|
571
596
|
|
|
572
597
|
**Parameters:**
|
|
573
|
-
- `browserType` - chromium, firefox, or webkit
|
|
574
|
-
- `width`, `height` - Viewport dimensions
|
|
575
|
-
- `headless` - Run in headless mode
|
|
576
|
-
- `timeout` - Navigation timeout
|
|
577
|
-
- `waitUntil` - Navigation wait condition
|
|
598
|
+
- `browserType` - chromium, firefox, or webkit (default: chromium)
|
|
599
|
+
- `width`, `height` - Viewport dimensions (default: auto-detected screen size)
|
|
600
|
+
- `headless` - Run in headless mode (default: **false** - browser window visible)
|
|
601
|
+
- `timeout` - Navigation timeout in ms (default: 30000)
|
|
602
|
+
- `waitUntil` - Navigation wait condition (default: "load")
|
|
603
|
+
|
|
604
|
+
**Default Behavior:**
|
|
605
|
+
- Browser window is **visible by default** for interactive debugging
|
|
606
|
+
- Use `headless: true` for automation, CI/CD, or when you don't need visual feedback
|
|
607
|
+
- Use `headless: false` (or omit) when debugging interactively
|
|
578
608
|
|
|
579
609
|
#### `go_back`
|
|
580
610
|
Navigate back in browser history. Essential for testing navigation flows and multi-step forms.
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,10 @@ const { values } = parseArgs({
|
|
|
16
16
|
type: 'string',
|
|
17
17
|
default: './.mcp-web-inspector',
|
|
18
18
|
},
|
|
19
|
+
'headless': {
|
|
20
|
+
type: 'boolean',
|
|
21
|
+
default: false,
|
|
22
|
+
},
|
|
19
23
|
},
|
|
20
24
|
strict: false,
|
|
21
25
|
});
|
|
@@ -25,6 +29,7 @@ const sessionConfig = {
|
|
|
25
29
|
saveSession: !Boolean(values['no-save-session']),
|
|
26
30
|
userDataDir: `${baseDir}/user-data`,
|
|
27
31
|
screenshotsDir: `${baseDir}/screenshots`,
|
|
32
|
+
headlessDefault: Boolean(values['headless']),
|
|
28
33
|
};
|
|
29
34
|
setSessionConfig(sessionConfig);
|
|
30
35
|
async function runServer() {
|
package/dist/toolHandler.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ interface SessionConfig {
|
|
|
22
22
|
saveSession: boolean;
|
|
23
23
|
userDataDir: string;
|
|
24
24
|
screenshotsDir: string;
|
|
25
|
+
headlessDefault: boolean;
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
28
|
* Sets the session configuration
|
|
@@ -31,6 +32,10 @@ export declare function setSessionConfig(config: Partial<SessionConfig>): void;
|
|
|
31
32
|
* Gets the screenshots directory
|
|
32
33
|
*/
|
|
33
34
|
export declare function getScreenshotsDir(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Gets the default headless setting
|
|
37
|
+
*/
|
|
38
|
+
export declare function getHeadlessDefault(): boolean;
|
|
34
39
|
/**
|
|
35
40
|
* Resets browser and page variables
|
|
36
41
|
* Used when browser is closed
|
|
@@ -48,7 +53,7 @@ export declare function clearNetworkLog(): void;
|
|
|
48
53
|
* Sets the provided page to the global page variable
|
|
49
54
|
* @param newPage The Page object to set as the global page
|
|
50
55
|
*/
|
|
51
|
-
export declare function setGlobalPage(newPage: Page): void
|
|
56
|
+
export declare function setGlobalPage(newPage: Page): Promise<void>;
|
|
52
57
|
interface BrowserSettings {
|
|
53
58
|
viewport?: {
|
|
54
59
|
width?: number;
|
package/dist/toolHandler.js
CHANGED
|
@@ -29,6 +29,7 @@ let sessionConfig = {
|
|
|
29
29
|
saveSession: false,
|
|
30
30
|
userDataDir: './.mcp-web-inspector/user-data',
|
|
31
31
|
screenshotsDir: './.mcp-web-inspector/screenshots',
|
|
32
|
+
headlessDefault: false,
|
|
32
33
|
};
|
|
33
34
|
/**
|
|
34
35
|
* Sets the session configuration
|
|
@@ -42,6 +43,12 @@ export function setSessionConfig(config) {
|
|
|
42
43
|
export function getScreenshotsDir() {
|
|
43
44
|
return sessionConfig.screenshotsDir;
|
|
44
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Gets the default headless setting
|
|
48
|
+
*/
|
|
49
|
+
export function getHeadlessDefault() {
|
|
50
|
+
return sessionConfig.headlessDefault;
|
|
51
|
+
}
|
|
45
52
|
/**
|
|
46
53
|
* Resets browser and page variables
|
|
47
54
|
* Used when browser is closed
|
|
@@ -68,10 +75,13 @@ export function clearNetworkLog() {
|
|
|
68
75
|
* Sets the provided page to the global page variable
|
|
69
76
|
* @param newPage The Page object to set as the global page
|
|
70
77
|
*/
|
|
71
|
-
export function setGlobalPage(newPage) {
|
|
78
|
+
export async function setGlobalPage(newPage) {
|
|
72
79
|
page = newPage;
|
|
80
|
+
// Register console message handlers and network listeners for the new page
|
|
81
|
+
await registerConsoleMessage(page);
|
|
82
|
+
await registerNetworkListeners(page);
|
|
73
83
|
page.bringToFront(); // Bring the new tab to the front
|
|
74
|
-
console.log("Global page has been updated.");
|
|
84
|
+
console.log("Global page has been updated with listeners registered.");
|
|
75
85
|
}
|
|
76
86
|
// Tool instances
|
|
77
87
|
let screenshotTool;
|
|
@@ -300,7 +310,7 @@ export async function ensureBrowser(browserSettings) {
|
|
|
300
310
|
}
|
|
301
311
|
// Launch new browser if needed
|
|
302
312
|
if (!browser) {
|
|
303
|
-
const { viewport, userAgent, headless =
|
|
313
|
+
const { viewport, userAgent, headless = sessionConfig.headlessDefault, browserType = 'chromium', device } = browserSettings ?? {};
|
|
304
314
|
// If browser type is changing, force a new browser instance
|
|
305
315
|
if (browser && currentBrowserType !== browserType) {
|
|
306
316
|
try {
|
|
@@ -451,7 +461,7 @@ export async function ensureBrowser(browserSettings) {
|
|
|
451
461
|
}
|
|
452
462
|
resetBrowserState();
|
|
453
463
|
// Try one more time from scratch
|
|
454
|
-
const { viewport, userAgent, headless =
|
|
464
|
+
const { viewport, userAgent, headless = sessionConfig.headlessDefault, browserType = 'chromium', device } = browserSettings ?? {};
|
|
455
465
|
// Get device configuration if device preset is specified
|
|
456
466
|
let deviceConfig = null;
|
|
457
467
|
if (device && DEVICE_PRESETS[device]) {
|
|
@@ -33,6 +33,10 @@ export class InspectAncestorsTool extends BrowserToolBase {
|
|
|
33
33
|
maxWidth: computed.maxWidth,
|
|
34
34
|
minWidth: computed.minWidth,
|
|
35
35
|
margin: computed.margin,
|
|
36
|
+
marginTop: computed.marginTop,
|
|
37
|
+
marginRight: computed.marginRight,
|
|
38
|
+
marginBottom: computed.marginBottom,
|
|
39
|
+
marginLeft: computed.marginLeft,
|
|
36
40
|
padding: computed.padding,
|
|
37
41
|
display: computed.display,
|
|
38
42
|
overflow: computed.overflow,
|
|
@@ -47,6 +51,10 @@ export class InspectAncestorsTool extends BrowserToolBase {
|
|
|
47
51
|
flexDirection: computed.flexDirection,
|
|
48
52
|
justifyContent: computed.justifyContent,
|
|
49
53
|
alignItems: computed.alignItems,
|
|
54
|
+
gap: computed.gap,
|
|
55
|
+
// Grid
|
|
56
|
+
gridTemplateColumns: computed.gridTemplateColumns,
|
|
57
|
+
gridTemplateRows: computed.gridTemplateRows,
|
|
50
58
|
// Conditional
|
|
51
59
|
position: computed.position !== "static" ? computed.position : undefined,
|
|
52
60
|
zIndex: computed.zIndex !== "auto" ? computed.zIndex : undefined,
|
|
@@ -103,9 +111,6 @@ export class InspectAncestorsTool extends BrowserToolBase {
|
|
|
103
111
|
// Display (only if not block)
|
|
104
112
|
if (ancestor.display !== "block") {
|
|
105
113
|
layoutInfo.push(`display:${ancestor.display}`);
|
|
106
|
-
if (ancestor.flexDirection && ancestor.flexDirection !== "row") {
|
|
107
|
-
layoutInfo.push(ancestor.flexDirection);
|
|
108
|
-
}
|
|
109
114
|
}
|
|
110
115
|
// Only show non-default values
|
|
111
116
|
if (ancestor.margin !== "0px") {
|
|
@@ -123,6 +128,16 @@ export class InspectAncestorsTool extends BrowserToolBase {
|
|
|
123
128
|
if (layoutInfo.length > 0) {
|
|
124
129
|
parts.push(` | ${layoutInfo.join(" ")}`);
|
|
125
130
|
}
|
|
131
|
+
// Flexbox/Grid context (on separate line for clarity)
|
|
132
|
+
const layoutContext = this.formatLayoutContext(ancestor);
|
|
133
|
+
if (layoutContext) {
|
|
134
|
+
parts.push(`\n ${layoutContext}`);
|
|
135
|
+
}
|
|
136
|
+
// Margin details (only if non-zero or has auto)
|
|
137
|
+
const marginDetails = this.formatMarginDetails(ancestor);
|
|
138
|
+
if (marginDetails) {
|
|
139
|
+
parts.push(`\n ${marginDetails}`);
|
|
140
|
+
}
|
|
126
141
|
// Border - only if set
|
|
127
142
|
const borderInfo = this.formatBorder(ancestor);
|
|
128
143
|
if (borderInfo) {
|
|
@@ -222,6 +237,116 @@ export class InspectAncestorsTool extends BrowserToolBase {
|
|
|
222
237
|
}
|
|
223
238
|
return null;
|
|
224
239
|
}
|
|
240
|
+
formatLayoutContext(ancestor) {
|
|
241
|
+
const parts = [];
|
|
242
|
+
// Flexbox
|
|
243
|
+
if (ancestor.display === "flex" || ancestor.display === "inline-flex") {
|
|
244
|
+
const flexParts = ["flex"];
|
|
245
|
+
if (ancestor.flexDirection && ancestor.flexDirection !== "row") {
|
|
246
|
+
flexParts.push(ancestor.flexDirection);
|
|
247
|
+
}
|
|
248
|
+
if (ancestor.justifyContent && ancestor.justifyContent !== "normal" && ancestor.justifyContent !== "flex-start") {
|
|
249
|
+
flexParts.push(`justify:${ancestor.justifyContent}`);
|
|
250
|
+
}
|
|
251
|
+
if (ancestor.alignItems && ancestor.alignItems !== "normal" && ancestor.alignItems !== "stretch") {
|
|
252
|
+
flexParts.push(`items:${ancestor.alignItems}`);
|
|
253
|
+
}
|
|
254
|
+
if (ancestor.gap && ancestor.gap !== "0px" && ancestor.gap !== "normal") {
|
|
255
|
+
flexParts.push(`gap:${ancestor.gap}`);
|
|
256
|
+
}
|
|
257
|
+
parts.push(flexParts.join(" "));
|
|
258
|
+
}
|
|
259
|
+
// Grid
|
|
260
|
+
if (ancestor.display === "grid" || ancestor.display === "inline-grid") {
|
|
261
|
+
const gridParts = ["grid"];
|
|
262
|
+
if (ancestor.gridTemplateColumns && ancestor.gridTemplateColumns !== "none") {
|
|
263
|
+
gridParts.push(`cols:${ancestor.gridTemplateColumns}`);
|
|
264
|
+
}
|
|
265
|
+
if (ancestor.gridTemplateRows && ancestor.gridTemplateRows !== "none") {
|
|
266
|
+
gridParts.push(`rows:${ancestor.gridTemplateRows}`);
|
|
267
|
+
}
|
|
268
|
+
if (ancestor.gap && ancestor.gap !== "0px" && ancestor.gap !== "normal") {
|
|
269
|
+
gridParts.push(`gap:${ancestor.gap}`);
|
|
270
|
+
}
|
|
271
|
+
parts.push(gridParts.join(" "));
|
|
272
|
+
}
|
|
273
|
+
return parts.length > 0 ? parts.join(" | ") : null;
|
|
274
|
+
}
|
|
275
|
+
formatMarginDetails(ancestor) {
|
|
276
|
+
// Check if any margin is "auto" (CSS value)
|
|
277
|
+
// Note: computed styles show actual values, not "auto"
|
|
278
|
+
const hasAuto = ancestor.margin.includes("auto") ||
|
|
279
|
+
ancestor.marginTop === "auto" ||
|
|
280
|
+
ancestor.marginRight === "auto" ||
|
|
281
|
+
ancestor.marginBottom === "auto" ||
|
|
282
|
+
ancestor.marginLeft === "auto";
|
|
283
|
+
// Check if margins are non-uniform (can't be represented by shorthand)
|
|
284
|
+
const isNonUniform = ancestor.marginTop !== ancestor.marginBottom ||
|
|
285
|
+
ancestor.marginLeft !== ancestor.marginRight ||
|
|
286
|
+
ancestor.marginTop !== ancestor.marginLeft;
|
|
287
|
+
// Parse margin values to detect large symmetric margins (likely auto-centered)
|
|
288
|
+
const parseMarginValue = (val) => {
|
|
289
|
+
const match = val.match(/^([\d.]+)px$/);
|
|
290
|
+
return match ? parseFloat(match[1]) : 0;
|
|
291
|
+
};
|
|
292
|
+
const marginLeftPx = parseMarginValue(ancestor.marginLeft);
|
|
293
|
+
const marginRightPx = parseMarginValue(ancestor.marginRight);
|
|
294
|
+
const marginTopPx = parseMarginValue(ancestor.marginTop);
|
|
295
|
+
const marginBottomPx = parseMarginValue(ancestor.marginBottom);
|
|
296
|
+
// Detect horizontal centering: large equal left/right margins, small top/bottom
|
|
297
|
+
const isHorizontallyCentered = marginLeftPx > 100 &&
|
|
298
|
+
marginRightPx > 100 &&
|
|
299
|
+
Math.abs(marginLeftPx - marginRightPx) < 2 && // Allow 1px rounding
|
|
300
|
+
marginTopPx === 0 &&
|
|
301
|
+
marginBottomPx === 0;
|
|
302
|
+
if (!hasAuto && !isHorizontallyCentered && ancestor.margin === "0px") {
|
|
303
|
+
return null; // All zeros, skip
|
|
304
|
+
}
|
|
305
|
+
// If has auto, always show detailed breakdown with arrows
|
|
306
|
+
if (hasAuto) {
|
|
307
|
+
const parts = [];
|
|
308
|
+
if (ancestor.marginTop !== "0px") {
|
|
309
|
+
parts.push(`↑${ancestor.marginTop}`);
|
|
310
|
+
}
|
|
311
|
+
if (ancestor.marginRight === "auto" || ancestor.marginRight !== "0px") {
|
|
312
|
+
parts.push(`→${ancestor.marginRight}`);
|
|
313
|
+
}
|
|
314
|
+
if (ancestor.marginBottom !== "0px") {
|
|
315
|
+
parts.push(`↓${ancestor.marginBottom}`);
|
|
316
|
+
}
|
|
317
|
+
if (ancestor.marginLeft === "auto" || ancestor.marginLeft !== "0px") {
|
|
318
|
+
parts.push(`←${ancestor.marginLeft}`);
|
|
319
|
+
}
|
|
320
|
+
const marginStr = `margin: ${parts.join(" ")}`;
|
|
321
|
+
// Add diagnostic if horizontally centered
|
|
322
|
+
if (ancestor.marginLeft === "auto" && ancestor.marginRight === "auto") {
|
|
323
|
+
return `${marginStr} ← Horizontally centered by auto margins`;
|
|
324
|
+
}
|
|
325
|
+
return marginStr;
|
|
326
|
+
}
|
|
327
|
+
// Show horizontal centering diagnostic
|
|
328
|
+
if (isHorizontallyCentered) {
|
|
329
|
+
return `margin: →${ancestor.marginRight} ←${ancestor.marginLeft} ← Horizontally centered (likely margin:0 auto)`;
|
|
330
|
+
}
|
|
331
|
+
// If non-uniform and non-zero, show with arrows
|
|
332
|
+
if (isNonUniform && ancestor.margin !== "0px") {
|
|
333
|
+
const parts = [];
|
|
334
|
+
if (ancestor.marginTop !== "0px") {
|
|
335
|
+
parts.push(`↑${ancestor.marginTop}`);
|
|
336
|
+
}
|
|
337
|
+
if (ancestor.marginRight !== "0px") {
|
|
338
|
+
parts.push(`→${ancestor.marginRight}`);
|
|
339
|
+
}
|
|
340
|
+
if (ancestor.marginBottom !== "0px") {
|
|
341
|
+
parts.push(`↓${ancestor.marginBottom}`);
|
|
342
|
+
}
|
|
343
|
+
if (ancestor.marginLeft !== "0px") {
|
|
344
|
+
parts.push(`←${ancestor.marginLeft}`);
|
|
345
|
+
}
|
|
346
|
+
return `margin: ${parts.join(" ")}`;
|
|
347
|
+
}
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
225
350
|
generateDiagnostics(ancestor, index) {
|
|
226
351
|
const diagnostics = [];
|
|
227
352
|
// Overflow hidden warning
|
|
@@ -232,11 +357,6 @@ export class InspectAncestorsTool extends BrowserToolBase {
|
|
|
232
357
|
if (ancestor.maxWidth !== "none" && index > 0) {
|
|
233
358
|
diagnostics.push("🎯 WIDTH CONSTRAINT");
|
|
234
359
|
}
|
|
235
|
-
// Large margins (potential centering)
|
|
236
|
-
const marginMatch = ancestor.margin.match(/0px (\d+)px/);
|
|
237
|
-
if (marginMatch && parseInt(marginMatch[1]) > 100) {
|
|
238
|
-
diagnostics.push(`⚠ Auto margins centering (${marginMatch[1]}px each side)`);
|
|
239
|
-
}
|
|
240
360
|
return diagnostics.length > 0 ? diagnostics.join("\n ") : null;
|
|
241
361
|
}
|
|
242
362
|
}
|
|
@@ -36,11 +36,8 @@ export class ClickAndSwitchTabTool extends BrowserToolBase {
|
|
|
36
36
|
]);
|
|
37
37
|
// Wait for the new page to load
|
|
38
38
|
await newPage.waitForLoadState('domcontentloaded');
|
|
39
|
-
// Switch control to the new tab
|
|
40
|
-
setGlobalPage(newPage);
|
|
41
|
-
//page= newPage; // Update the current page to the new tab
|
|
42
|
-
//context.page = newPage;
|
|
43
|
-
//context.page.bringToFront(); // Bring the new tab to the front
|
|
39
|
+
// Switch control to the new tab and register listeners
|
|
40
|
+
await setGlobalPage(newPage);
|
|
44
41
|
return createSuccessResponse(`Clicked link and switched to new tab: ${newPage.url()}`);
|
|
45
42
|
//return createSuccessResponse(`Clicked link and switched to new tab: ${context.page.url()}`);
|
|
46
43
|
});
|
|
@@ -21,8 +21,25 @@ export class VisibleTextTool extends BrowserToolBase {
|
|
|
21
21
|
}
|
|
22
22
|
return this.safeExecute(context, async (page) => {
|
|
23
23
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
// Normalize selector (support testid: shorthand)
|
|
25
|
+
const selector = args.selector ? this.normalizeSelector(args.selector) : undefined;
|
|
26
|
+
// If selector provided, validate element exists
|
|
27
|
+
if (selector) {
|
|
28
|
+
const element = await page.$(selector);
|
|
29
|
+
if (!element) {
|
|
30
|
+
return createErrorResponse(`Element with selector "${args.selector}" not found`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const visibleText = await page.evaluate((sel) => {
|
|
34
|
+
// Find root element - either selected element or body
|
|
35
|
+
let rootElement = document.body;
|
|
36
|
+
if (sel) {
|
|
37
|
+
const element = document.querySelector(sel);
|
|
38
|
+
if (!element)
|
|
39
|
+
return ""; // Should not happen due to earlier check, but be safe
|
|
40
|
+
rootElement = element;
|
|
41
|
+
}
|
|
42
|
+
const walker = document.createTreeWalker(rootElement, NodeFilter.SHOW_TEXT, {
|
|
26
43
|
acceptNode: (node) => {
|
|
27
44
|
const style = window.getComputedStyle(node.parentElement);
|
|
28
45
|
return (style.display !== "none" && style.visibility !== "hidden")
|
|
@@ -39,7 +56,7 @@ export class VisibleTextTool extends BrowserToolBase {
|
|
|
39
56
|
}
|
|
40
57
|
}
|
|
41
58
|
return text.trim();
|
|
42
|
-
});
|
|
59
|
+
}, selector);
|
|
43
60
|
// Truncate logic
|
|
44
61
|
const maxLength = typeof args.maxLength === 'number' ? args.maxLength : 20000;
|
|
45
62
|
let output = visibleText;
|
|
@@ -48,7 +65,13 @@ export class VisibleTextTool extends BrowserToolBase {
|
|
|
48
65
|
output = output.slice(0, maxLength) + '\n[Output truncated due to size limits]';
|
|
49
66
|
truncated = true;
|
|
50
67
|
}
|
|
51
|
-
|
|
68
|
+
// Add guidance footer
|
|
69
|
+
const scopeInfo = selector ? ` (from "${args.selector}")` : " (entire page)";
|
|
70
|
+
const guidance = `\n\n💡 TIP: If you need structured inspection rather than raw text:
|
|
71
|
+
• inspect_dom() - See page structure with positions and relationships
|
|
72
|
+
• find_by_text("text") - Locate specific text with element context
|
|
73
|
+
• query_selector("selector") - Find and inspect specific elements`;
|
|
74
|
+
return createSuccessResponse(`Visible text content${scopeInfo}:\n${output}${guidance}`);
|
|
52
75
|
}
|
|
53
76
|
catch (error) {
|
|
54
77
|
return createErrorResponse(`Failed to get visible text content: ${error.message}`);
|
|
@@ -76,91 +99,80 @@ export class VisibleHtmlTool extends BrowserToolBase {
|
|
|
76
99
|
}
|
|
77
100
|
return this.safeExecute(context, async (page) => {
|
|
78
101
|
try {
|
|
79
|
-
const { removeComments, removeStyles, removeMeta, minify, cleanHtml } = args;
|
|
80
|
-
// Default removeScripts to true unless explicitly set to false
|
|
81
|
-
const removeScripts = args.removeScripts === false ? false : true;
|
|
82
102
|
// Normalize selector (support testid: shorthand)
|
|
83
103
|
const selector = args.selector ? this.normalizeSelector(args.selector) : undefined;
|
|
104
|
+
// If selector provided, validate element exists
|
|
105
|
+
if (selector) {
|
|
106
|
+
const element = await page.$(selector);
|
|
107
|
+
if (!element) {
|
|
108
|
+
return createErrorResponse(`Element with selector "${args.selector}" not found`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
84
111
|
// Get the HTML content
|
|
85
112
|
let htmlContent;
|
|
86
113
|
if (selector) {
|
|
87
114
|
// If a selector is provided, get only the HTML for that element
|
|
88
115
|
const element = await page.$(selector);
|
|
89
|
-
if (!element) {
|
|
90
|
-
return createErrorResponse(`Element with selector "${selector}" not found`);
|
|
91
|
-
}
|
|
92
116
|
htmlContent = await page.evaluate((el) => el.outerHTML, element);
|
|
93
117
|
}
|
|
94
118
|
else {
|
|
95
119
|
// Otherwise get the full page HTML
|
|
96
120
|
htmlContent = await page.content();
|
|
97
121
|
}
|
|
98
|
-
// Determine
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
const shouldRemoveMeta = removeMeta || cleanHtml;
|
|
122
|
+
// Determine cleanup level
|
|
123
|
+
// Default: remove scripts only (security/size)
|
|
124
|
+
// clean=true: remove scripts + styles + comments + meta
|
|
125
|
+
const clean = args.clean === true;
|
|
103
126
|
// Apply filters in the browser context
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// Remove
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const removeComments = (node) => {
|
|
127
|
-
const childNodes = node.childNodes;
|
|
128
|
-
for (let i = childNodes.length - 1; i >= 0; i--) {
|
|
129
|
-
const child = childNodes[i];
|
|
130
|
-
if (child.nodeType === 8) { // 8 is for comment nodes
|
|
131
|
-
node.removeChild(child);
|
|
132
|
-
}
|
|
133
|
-
else if (child.nodeType === 1) { // 1 is for element nodes
|
|
134
|
-
removeComments(child);
|
|
135
|
-
}
|
|
127
|
+
htmlContent = await page.evaluate(({ html, clean }) => {
|
|
128
|
+
// Create a DOM parser to work with the HTML
|
|
129
|
+
const parser = new DOMParser();
|
|
130
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
131
|
+
// Always remove scripts (default behavior)
|
|
132
|
+
const scripts = doc.querySelectorAll('script');
|
|
133
|
+
scripts.forEach(script => script.remove());
|
|
134
|
+
// If clean=true, remove additional noise
|
|
135
|
+
if (clean) {
|
|
136
|
+
// Remove style tags
|
|
137
|
+
const styles = doc.querySelectorAll('style');
|
|
138
|
+
styles.forEach(style => style.remove());
|
|
139
|
+
// Remove meta tags
|
|
140
|
+
const metaTags = doc.querySelectorAll('meta');
|
|
141
|
+
metaTags.forEach(meta => meta.remove());
|
|
142
|
+
// Remove HTML comments
|
|
143
|
+
const removeCommentsRecursive = (node) => {
|
|
144
|
+
const childNodes = node.childNodes;
|
|
145
|
+
for (let i = childNodes.length - 1; i >= 0; i--) {
|
|
146
|
+
const child = childNodes[i];
|
|
147
|
+
if (child.nodeType === 8) { // 8 is for comment nodes
|
|
148
|
+
node.removeChild(child);
|
|
136
149
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return result;
|
|
148
|
-
}, {
|
|
149
|
-
html: htmlContent,
|
|
150
|
-
removeScripts: shouldRemoveScripts,
|
|
151
|
-
removeComments: shouldRemoveComments,
|
|
152
|
-
removeStyles: shouldRemoveStyles,
|
|
153
|
-
removeMeta: shouldRemoveMeta,
|
|
154
|
-
minify
|
|
155
|
-
});
|
|
156
|
-
}
|
|
150
|
+
else if (child.nodeType === 1) { // 1 is for element nodes
|
|
151
|
+
removeCommentsRecursive(child);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
removeCommentsRecursive(doc.documentElement);
|
|
156
|
+
}
|
|
157
|
+
// Get the processed HTML
|
|
158
|
+
return doc.documentElement.outerHTML;
|
|
159
|
+
}, { html: htmlContent, clean });
|
|
157
160
|
// Truncate logic
|
|
158
161
|
const maxLength = typeof args.maxLength === 'number' ? args.maxLength : 20000;
|
|
159
162
|
let output = htmlContent;
|
|
163
|
+
let truncated = false;
|
|
160
164
|
if (output.length > maxLength) {
|
|
161
165
|
output = output.slice(0, maxLength) + '\n<!-- Output truncated due to size limits -->';
|
|
166
|
+
truncated = true;
|
|
162
167
|
}
|
|
163
|
-
|
|
168
|
+
// Add guidance footer
|
|
169
|
+
const scopeInfo = selector ? ` (from "${args.selector}")` : " (entire page)";
|
|
170
|
+
const cleanInfo = clean ? ", clean mode" : ", scripts removed";
|
|
171
|
+
const guidance = `\n\n💡 TIP: If you need structured inspection rather than raw HTML:
|
|
172
|
+
• inspect_dom() - See page structure with positions and relationships
|
|
173
|
+
• query_selector("selector") - Find and inspect specific elements
|
|
174
|
+
• get_computed_styles("selector") - Get CSS values for elements`;
|
|
175
|
+
return createSuccessResponse(`HTML content${scopeInfo}${cleanInfo}:\n${output}${guidance}`);
|
|
164
176
|
}
|
|
165
177
|
catch (error) {
|
|
166
178
|
return createErrorResponse(`Failed to get visible HTML content: ${error.message}`);
|
package/dist/tools.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ interface SessionConfig {
|
|
|
2
2
|
saveSession: boolean;
|
|
3
3
|
userDataDir: string;
|
|
4
4
|
screenshotsDir: string;
|
|
5
|
+
headlessDefault: boolean;
|
|
5
6
|
}
|
|
6
7
|
export declare function createToolDefinitions(sessionConfig?: SessionConfig): [{
|
|
7
8
|
readonly name: "navigate";
|
|
@@ -41,7 +42,7 @@ export declare function createToolDefinitions(sessionConfig?: SessionConfig): [{
|
|
|
41
42
|
};
|
|
42
43
|
readonly headless: {
|
|
43
44
|
readonly type: "boolean";
|
|
44
|
-
readonly description: "Run browser in headless mode (default: false)";
|
|
45
|
+
readonly description: "Run browser in headless mode (default: true - no window shown)" | "Run browser in headless mode (default: false - browser window visible)";
|
|
45
46
|
};
|
|
46
47
|
};
|
|
47
48
|
readonly required: readonly ["url"];
|
|
@@ -202,45 +203,34 @@ export declare function createToolDefinitions(sessionConfig?: SessionConfig): [{
|
|
|
202
203
|
};
|
|
203
204
|
}, {
|
|
204
205
|
readonly name: "get_text";
|
|
205
|
-
readonly description: "Get
|
|
206
|
+
readonly description: "⚠️ RARELY NEEDED: Get ALL visible text content from the entire page (no structure, just raw text). Most tasks need structured inspection instead. ONLY use get_text for: (1) extracting text for content analysis (word count, language detection), (2) searching for text when location is completely unknown, (3) text-only snapshots for comparison. For structured tasks, use: inspect_dom() to understand page structure, find_by_text() to locate specific text with context, query_selector() to find elements. Returns plain text up to 20000 chars (truncated if longer). Supports testid shortcuts.";
|
|
206
207
|
readonly inputSchema: {
|
|
207
208
|
readonly type: "object";
|
|
208
|
-
readonly properties: {
|
|
209
|
+
readonly properties: {
|
|
210
|
+
readonly selector: {
|
|
211
|
+
readonly type: "string";
|
|
212
|
+
readonly description: "CSS selector, text selector, or testid shorthand to limit text extraction to a specific container. Omit to get text from entire page. Example: 'testid:article-body' or '#main-content'";
|
|
213
|
+
};
|
|
214
|
+
readonly maxLength: {
|
|
215
|
+
readonly type: "number";
|
|
216
|
+
readonly description: "Maximum number of characters to return (default: 20000)";
|
|
217
|
+
};
|
|
218
|
+
};
|
|
209
219
|
readonly required: readonly [];
|
|
210
220
|
};
|
|
211
221
|
}, {
|
|
212
222
|
readonly name: "get_html";
|
|
213
|
-
readonly description: "Get raw HTML
|
|
223
|
+
readonly description: "⚠️ RARELY NEEDED: Get raw HTML markup from the page (no rendering, just source code). Most tasks need structured inspection instead. ONLY use get_html for: (1) checking specific HTML attributes or element nesting, (2) analyzing markup structure, (3) debugging SSR/HTML issues. For structured tasks, use: inspect_dom() to understand page structure with positions, query_selector() to find and inspect elements, get_computed_styles() for CSS values. Returns HTML up to 20000 chars (truncated if longer), scripts removed by default for security/size. Supports testid shortcuts.";
|
|
214
224
|
readonly inputSchema: {
|
|
215
225
|
readonly type: "object";
|
|
216
226
|
readonly properties: {
|
|
217
227
|
readonly selector: {
|
|
218
228
|
readonly type: "string";
|
|
219
|
-
readonly description: "CSS selector to limit
|
|
220
|
-
};
|
|
221
|
-
readonly removeScripts: {
|
|
222
|
-
readonly type: "boolean";
|
|
223
|
-
readonly description: "Remove all script tags from the HTML (default: true)";
|
|
224
|
-
};
|
|
225
|
-
readonly removeComments: {
|
|
226
|
-
readonly type: "boolean";
|
|
227
|
-
readonly description: "Remove all HTML comments (default: false)";
|
|
228
|
-
};
|
|
229
|
-
readonly removeStyles: {
|
|
230
|
-
readonly type: "boolean";
|
|
231
|
-
readonly description: "Remove all style tags from the HTML (default: false)";
|
|
232
|
-
};
|
|
233
|
-
readonly removeMeta: {
|
|
234
|
-
readonly type: "boolean";
|
|
235
|
-
readonly description: "Remove all meta tags from the HTML (default: false)";
|
|
236
|
-
};
|
|
237
|
-
readonly cleanHtml: {
|
|
238
|
-
readonly type: "boolean";
|
|
239
|
-
readonly description: "Perform comprehensive HTML cleaning (default: false)";
|
|
229
|
+
readonly description: "CSS selector, text selector, or testid shorthand to limit HTML extraction to a specific container. Omit to get entire page HTML. Example: 'testid:main-content' or '#app'";
|
|
240
230
|
};
|
|
241
|
-
readonly
|
|
231
|
+
readonly clean: {
|
|
242
232
|
readonly type: "boolean";
|
|
243
|
-
readonly description: "
|
|
233
|
+
readonly description: "Remove noise from HTML: false (default) = remove scripts only, true = remove scripts + styles + comments + meta tags for minimal markup";
|
|
244
234
|
};
|
|
245
235
|
readonly maxLength: {
|
|
246
236
|
readonly type: "number";
|
|
@@ -470,7 +460,7 @@ export declare function createToolDefinitions(sessionConfig?: SessionConfig): [{
|
|
|
470
460
|
};
|
|
471
461
|
}, {
|
|
472
462
|
readonly name: "inspect_ancestors";
|
|
473
|
-
readonly description: "DEBUG LAYOUT CONSTRAINTS: Walk up the DOM tree to find where width constraints, margins, borders, and overflow clipping come from.
|
|
463
|
+
readonly description: "DEBUG LAYOUT CONSTRAINTS: Walk up the DOM tree to find where width constraints, margins, borders, and overflow clipping come from. Shows for each ancestor: position/size, width constraints (w, max-w, min-w), margins with directional arrows (↑↓←→ format), padding, display type, borders (directional if non-uniform), overflow (🔒=hidden, ↕️=scroll), flexbox context (flex direction justify items gap), grid context (cols rows gap), position/z-index/transform when set. Automatically detects horizontal centering via auto margins and flags clipping points (🎯). Essential for debugging unexpected centering, constrained width, or clipped content. Default: 10 ancestors (reaches <body> in most React apps), max: 15. Use after inspect_dom() to understand parent layout constraints.";
|
|
474
464
|
readonly inputSchema: {
|
|
475
465
|
readonly type: "object";
|
|
476
466
|
readonly properties: {
|
package/dist/tools.js
CHANGED
|
@@ -3,6 +3,7 @@ export function createToolDefinitions(sessionConfig) {
|
|
|
3
3
|
const sessionEnabled = sessionConfig?.saveSession ?? true;
|
|
4
4
|
const userDataDir = sessionConfig?.userDataDir || './.mcp-web-inspector/user-data';
|
|
5
5
|
const screenshotsDir = sessionConfig?.screenshotsDir || './.mcp-web-inspector/screenshots';
|
|
6
|
+
const headlessDefault = sessionConfig?.headlessDefault ?? false;
|
|
6
7
|
const navigateDescription = sessionEnabled
|
|
7
8
|
? `Navigate to a URL. Browser sessions (cookies, localStorage, sessionStorage) are automatically saved in ${userDataDir} directory and persist across restarts. To clear saved sessions, delete the directory.`
|
|
8
9
|
: "Navigate to a URL. Browser starts fresh each time with no persistent session state (started with --no-save-session flag).";
|
|
@@ -24,7 +25,7 @@ export function createToolDefinitions(sessionConfig) {
|
|
|
24
25
|
height: { type: "number", description: "Viewport height in pixels. If not specified, automatically matches screen height. Ignored if device is specified." },
|
|
25
26
|
timeout: { type: "number", description: "Navigation timeout in milliseconds" },
|
|
26
27
|
waitUntil: { type: "string", description: "Navigation wait condition" },
|
|
27
|
-
headless: { type: "boolean", description:
|
|
28
|
+
headless: { type: "boolean", description: `Run browser in headless mode (default: ${headlessDefault ? 'true - no window shown' : 'false - browser window visible'})` }
|
|
28
29
|
},
|
|
29
30
|
required: ["url"],
|
|
30
31
|
},
|
|
@@ -167,27 +168,40 @@ export function createToolDefinitions(sessionConfig) {
|
|
|
167
168
|
},
|
|
168
169
|
{
|
|
169
170
|
name: "get_text",
|
|
170
|
-
description: "Get
|
|
171
|
+
description: "⚠️ RARELY NEEDED: Get ALL visible text content from the entire page (no structure, just raw text). Most tasks need structured inspection instead. ONLY use get_text for: (1) extracting text for content analysis (word count, language detection), (2) searching for text when location is completely unknown, (3) text-only snapshots for comparison. For structured tasks, use: inspect_dom() to understand page structure, find_by_text() to locate specific text with context, query_selector() to find elements. Returns plain text up to 20000 chars (truncated if longer). Supports testid shortcuts.",
|
|
171
172
|
inputSchema: {
|
|
172
173
|
type: "object",
|
|
173
|
-
properties: {
|
|
174
|
+
properties: {
|
|
175
|
+
selector: {
|
|
176
|
+
type: "string",
|
|
177
|
+
description: "CSS selector, text selector, or testid shorthand to limit text extraction to a specific container. Omit to get text from entire page. Example: 'testid:article-body' or '#main-content'"
|
|
178
|
+
},
|
|
179
|
+
maxLength: {
|
|
180
|
+
type: "number",
|
|
181
|
+
description: "Maximum number of characters to return (default: 20000)"
|
|
182
|
+
}
|
|
183
|
+
},
|
|
174
184
|
required: [],
|
|
175
185
|
},
|
|
176
186
|
},
|
|
177
187
|
{
|
|
178
188
|
name: "get_html",
|
|
179
|
-
description: "Get raw HTML
|
|
189
|
+
description: "⚠️ RARELY NEEDED: Get raw HTML markup from the page (no rendering, just source code). Most tasks need structured inspection instead. ONLY use get_html for: (1) checking specific HTML attributes or element nesting, (2) analyzing markup structure, (3) debugging SSR/HTML issues. For structured tasks, use: inspect_dom() to understand page structure with positions, query_selector() to find and inspect elements, get_computed_styles() for CSS values. Returns HTML up to 20000 chars (truncated if longer), scripts removed by default for security/size. Supports testid shortcuts.",
|
|
180
190
|
inputSchema: {
|
|
181
191
|
type: "object",
|
|
182
192
|
properties: {
|
|
183
|
-
selector: {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
193
|
+
selector: {
|
|
194
|
+
type: "string",
|
|
195
|
+
description: "CSS selector, text selector, or testid shorthand to limit HTML extraction to a specific container. Omit to get entire page HTML. Example: 'testid:main-content' or '#app'"
|
|
196
|
+
},
|
|
197
|
+
clean: {
|
|
198
|
+
type: "boolean",
|
|
199
|
+
description: "Remove noise from HTML: false (default) = remove scripts only, true = remove scripts + styles + comments + meta tags for minimal markup"
|
|
200
|
+
},
|
|
201
|
+
maxLength: {
|
|
202
|
+
type: "number",
|
|
203
|
+
description: "Maximum number of characters to return (default: 20000)"
|
|
204
|
+
}
|
|
191
205
|
},
|
|
192
206
|
required: [],
|
|
193
207
|
},
|
|
@@ -439,7 +453,7 @@ More efficient than get_html() or evaluate(). Supports testid shortcuts.`,
|
|
|
439
453
|
},
|
|
440
454
|
{
|
|
441
455
|
name: "inspect_ancestors",
|
|
442
|
-
description: "DEBUG LAYOUT CONSTRAINTS: Walk up the DOM tree to find where width constraints, margins, borders, and overflow clipping come from.
|
|
456
|
+
description: "DEBUG LAYOUT CONSTRAINTS: Walk up the DOM tree to find where width constraints, margins, borders, and overflow clipping come from. Shows for each ancestor: position/size, width constraints (w, max-w, min-w), margins with directional arrows (↑↓←→ format), padding, display type, borders (directional if non-uniform), overflow (🔒=hidden, ↕️=scroll), flexbox context (flex direction justify items gap), grid context (cols rows gap), position/z-index/transform when set. Automatically detects horizontal centering via auto margins and flags clipping points (🎯). Essential for debugging unexpected centering, constrained width, or clipped content. Default: 10 ancestors (reaches <body> in most React apps), max: 15. Use after inspect_dom() to understand parent layout constraints.",
|
|
443
457
|
inputSchema: {
|
|
444
458
|
type: "object",
|
|
445
459
|
properties: {
|