browser-devtools-mcp 0.2.26 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -66
- package/SECURITY.md +0 -2
- package/dist/cli/runner.js +3 -3
- package/dist/{core-247YVH6X.js → core-DOXUXYCD.js} +3 -3
- package/dist/{core-FCPLN2O4.js → core-LAPVCKSF.js} +1 -1
- package/dist/core-RRWTV5B5.js +956 -0
- package/dist/daemon-server.js +1 -1
- package/dist/index.js +2 -2
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/types.d.ts +7 -0
- package/package.json +7 -2
- package/dist/core-LM2SLZDP.js +0 -1195
package/README.md
CHANGED
|
@@ -61,18 +61,18 @@ Choose the platform by running the appropriate MCP server or CLI:
|
|
|
61
61
|
## Browser Platform Features
|
|
62
62
|
|
|
63
63
|
### Content Tools
|
|
64
|
-
- **Screenshots**: Capture full page or specific elements (PNG/JPEG) with image data; optional `annotate: true` overlays numbered labels (1, 2, ...) on elements from the last ARIA snapshot refs and returns an `annotations` array (ref, number, role, name, box). If the ref map is empty, a snapshot is taken automatically. Set `annotateContent: true` to also include content elements (headings, list items, etc.) in the overlay. Set `annotateCursorInteractive: true` to also include cursor-interactive elements (clickable/focusable by CSS without ARIA role) in the overlay.
|
|
64
|
+
- **Screenshots**: Capture full page or specific elements (PNG/JPEG) with image data; optional `annotate: true` overlays numbered labels (1, 2, ...) on elements from the last ARIA snapshot refs and returns an `annotations` array (ref, number, role, name, box). If the ref map is empty, a snapshot is taken automatically. Set `annotateContent: true` to also include content elements (headings, list items, etc.) in the overlay. Set `annotateCursorInteractive: true` to also include cursor-interactive elements (clickable/focusable by CSS without ARIA role) in the overlay. The `selector` parameter accepts a ref (e.g. `e1`, `@e1`), a getByRole/getByLabel/getByText/etc. expression, or a CSS selector; with `selector` set, only annotations overlapping that element are returned and box coordinates are element-relative; with `fullPage: true` (no selector), box coordinates are document-relative.
|
|
65
65
|
- **HTML/Text Extraction**: Get page content with filtering, cleaning, and minification options
|
|
66
66
|
- **PDF Export**: Save pages as PDF documents with customizable format and margins
|
|
67
67
|
|
|
68
68
|
### Interaction Tools
|
|
69
|
-
- **Click**: Click elements by
|
|
70
|
-
- **Fill**: Fill form inputs (
|
|
71
|
-
- **Hover**: Hover over elements (
|
|
72
|
-
- **Press Key**: Simulate keyboard input; optional
|
|
73
|
-
- **Select**: Select dropdown options (
|
|
74
|
-
- **Drag**: Drag and drop
|
|
75
|
-
- **Scroll**: Scroll the page viewport or specific scrollable elements with multiple modes (by, to, top, bottom, left, right); optional
|
|
69
|
+
- **Click**: Click elements by snapshot ref (e.g. `e1`, `@e1`, `ref=e1`), Playwright-style expression (e.g. `getByRole('button', { name: 'Login' })`, `getByLabel('Email')`, `getByText('Register')`, `getByPlaceholder('demo@example.com')`, `getByTitle('…')`, `getByAltText('…')`, `getByTestId('…')`), or CSS selector. Refs come from `a11y_take-aria-snapshot` and are valid until the next snapshot or navigation.
|
|
70
|
+
- **Fill**: Fill form inputs (ref, getByRole/getByLabel/getByPlaceholder/etc., or CSS selector)
|
|
71
|
+
- **Hover**: Hover over elements (ref, getByXYZ expression, or CSS selector)
|
|
72
|
+
- **Press Key**: Simulate keyboard input; optional ref, getByXYZ expression, or CSS selector to focus an element before sending the key
|
|
73
|
+
- **Select**: Select dropdown options (ref, getByRole/getByTestId/etc., or CSS selector)
|
|
74
|
+
- **Drag**: Drag and drop (source and target as ref, getByXYZ expression, or CSS selector)
|
|
75
|
+
- **Scroll**: Scroll the page viewport or specific scrollable elements with multiple modes (by, to, top, bottom, left, right); optional ref, getByXYZ, or CSS selector for scrollable container
|
|
76
76
|
- **Resize Viewport**: Resize the page viewport using Playwright viewport emulation
|
|
77
77
|
- **Resize Window**: Resize the real browser window (OS-level) using Chrome DevTools Protocol
|
|
78
78
|
|
|
@@ -97,7 +97,7 @@ Choose the platform by running the appropriate MCP server or CLI:
|
|
|
97
97
|
- **Wait for Network Idle**: Wait until the page reaches a network-idle condition based on in-flight request count, useful for SPA pages and before taking screenshots
|
|
98
98
|
|
|
99
99
|
### Accessibility (A11Y) Tools
|
|
100
|
-
- **ARIA Snapshots**: Capture semantic structure and accessibility roles in YAML format. Returns a tree with element refs (e1, e2, ...) and a `refs` map; refs are stored in session context for use in interaction tools (click, fill, hover, select, drag, scroll, press-key) as the selector (e.g. `e1`, `@e1`, `ref=e1`). Refs are valid until the next ARIA snapshot or navigation—re-snapshot after page/DOM changes. Options: **interactiveOnly** (only interactive elements get refs); **cursorInteractive: true** (also assign refs to elements that are clickable/focusable by CSS but have no ARIA role, e.g. custom div/span buttons); **selector** (scope the snapshot to an element).
|
|
100
|
+
- **ARIA Snapshots**: Capture semantic structure and accessibility roles in YAML format. Returns a tree with element refs (e1, e2, ...) and a `refs` map; refs are stored in session context for use in interaction tools (click, fill, hover, select, drag, scroll, press-key) as the selector (e.g. `e1`, `@e1`, `ref=e1`). You can also use Playwright-style expressions in those tools: `getByRole('button', { name: 'Login' })`, `getByLabel('Email')`, `getByText('Register')`, `getByPlaceholder('…')`, `getByTitle('…')`, `getByAltText('…')`, `getByTestId('…')`, or CSS selectors. Refs are valid until the next ARIA snapshot or navigation—re-snapshot after page/DOM changes. Options: **interactiveOnly** (only interactive elements get refs); **cursorInteractive: true** (also assign refs to elements that are clickable/focusable by CSS but have no ARIA role, e.g. custom div/span buttons); **selector** (scope the snapshot to an element).
|
|
101
101
|
- **AX Tree Snapshots**: Combine Chromium's Accessibility tree with runtime visual diagnostics (bounding boxes, visibility, occlusion detection, computed styles)
|
|
102
102
|
|
|
103
103
|
### Stub Tools
|
|
@@ -127,8 +127,6 @@ Non-blocking debugging tools that capture snapshots without pausing execution. I
|
|
|
127
127
|
- **Tracepoint**: Captures call stack, local variables, and async stack traces at a code location
|
|
128
128
|
- **Logpoint**: Evaluates and logs expressions without full debug context (lightweight)
|
|
129
129
|
- **Exceptionpoint**: Captures snapshots when exceptions occur (uncaught or all)
|
|
130
|
-
- **Dompoint**: Monitors DOM mutations (subtree-modified, attribute-modified, node-removed)
|
|
131
|
-
- **Netpoint**: Monitors network requests/responses matching a URL pattern
|
|
132
130
|
|
|
133
131
|
**Core Operations (per probe type):**
|
|
134
132
|
- `put-*`: Create a probe at a location
|
|
@@ -581,7 +579,7 @@ node-devtools-cli
|
|
|
581
579
|
├── run # Script execution commands
|
|
582
580
|
│ └── js-in-node # Run JavaScript in the connected Node process
|
|
583
581
|
└── debug # Debug commands
|
|
584
|
-
├── connect # Connect to Node.js process (pid, processName,
|
|
582
|
+
├── connect # Connect to Node.js process (pid, processName, inspectorPort, containerId, etc.)
|
|
585
583
|
├── disconnect # Disconnect from current process
|
|
586
584
|
├── status # Show connection status
|
|
587
585
|
├── put-tracepoint # Set a tracepoint
|
|
@@ -618,8 +616,7 @@ browser-devtools-cli
|
|
|
618
616
|
├── update # Check for and install updates
|
|
619
617
|
├── navigation # Navigation commands
|
|
620
618
|
│ ├── go-to # Navigate to a URL
|
|
621
|
-
│ ├── go-back
|
|
622
|
-
│ ├── go-forward # Navigate forward
|
|
619
|
+
│ ├── go-back-or-forward # Navigate back or forward in history (direction: back | forward)
|
|
623
620
|
│ └── reload # Reload the page
|
|
624
621
|
├── content # Content extraction commands
|
|
625
622
|
│ ├── take-screenshot # Take a screenshot
|
|
@@ -662,36 +659,14 @@ browser-devtools-cli
|
|
|
662
659
|
│ └── wait-for-network-idle # Wait for network idle
|
|
663
660
|
├── debug # Non-blocking debugging commands
|
|
664
661
|
│ ├── put-tracepoint # Set a tracepoint (captures call stack)
|
|
665
|
-
│ ├── remove-
|
|
666
|
-
│ ├── list-
|
|
667
|
-
│ ├── clear-tracepoints # Clear all tracepoints
|
|
662
|
+
│ ├── remove-probe # Remove a tracepoint, logpoint, or watch by ID (type + id)
|
|
663
|
+
│ ├── list-probes # List tracepoints, logpoints, and/or watches (optional types; omit to list all)
|
|
668
664
|
│ ├── put-logpoint # Set a logpoint (evaluates expression)
|
|
669
|
-
│ ├── remove-logpoint # Remove a logpoint
|
|
670
|
-
│ ├── list-logpoints # List all logpoints
|
|
671
|
-
│ ├── clear-logpoints # Clear all logpoints
|
|
672
665
|
│ ├── put-exceptionpoint # Enable exception catching
|
|
673
|
-
│ ├── put-dompoint # Set DOM mutation breakpoint
|
|
674
|
-
│ ├── remove-dompoint # Remove a DOM breakpoint
|
|
675
|
-
│ ├── list-dompoints # List all DOM breakpoints
|
|
676
|
-
│ ├── clear-dompoints # Clear all DOM breakpoints
|
|
677
|
-
│ ├── put-netpoint # Set network request breakpoint
|
|
678
|
-
│ ├── remove-netpoint # Remove a network breakpoint
|
|
679
|
-
│ ├── list-netpoints # List all network breakpoints
|
|
680
|
-
│ ├── clear-netpoints # Clear all network breakpoints
|
|
681
666
|
│ ├── add-watch # Add a watch expression
|
|
682
|
-
│ ├──
|
|
683
|
-
│ ├──
|
|
684
|
-
│ ├── clear-
|
|
685
|
-
│ ├── get-tracepoint-snapshots # Get tracepoint snapshots
|
|
686
|
-
│ ├── clear-tracepoint-snapshots # Clear tracepoint snapshots
|
|
687
|
-
│ ├── get-logpoint-snapshots # Get logpoint snapshots
|
|
688
|
-
│ ├── clear-logpoint-snapshots # Clear logpoint snapshots
|
|
689
|
-
│ ├── get-exceptionpoint-snapshots # Get exception snapshots
|
|
690
|
-
│ ├── clear-exceptionpoint-snapshots # Clear exception snapshots
|
|
691
|
-
│ ├── get-dompoint-snapshots # Get DOM mutation snapshots
|
|
692
|
-
│ ├── clear-dompoint-snapshots # Clear DOM snapshots
|
|
693
|
-
│ ├── get-netpoint-snapshots # Get network snapshots
|
|
694
|
-
│ ├── clear-netpoint-snapshots # Clear network snapshots
|
|
667
|
+
│ ├── clear-probes # Clear tracepoints, logpoints, and/or watches (optional types; omit to clear all)
|
|
668
|
+
│ ├── get-probe-snapshots # Get tracepoint/logpoint/exceptionpoint snapshots (optional types; response: tracepointSnapshots, logpointSnapshots, exceptionpointSnapshots)
|
|
669
|
+
│ ├── clear-probe-snapshots # Clear tracepoint/logpoint/exceptionpoint snapshots (optional types; omit to clear all)
|
|
695
670
|
│ └── status # Get debugging status
|
|
696
671
|
└── figma # Figma integration commands
|
|
697
672
|
└── compare-page-with-design
|
|
@@ -827,7 +802,7 @@ browser-devtools-cli tools list
|
|
|
827
802
|
#
|
|
828
803
|
# navigation:
|
|
829
804
|
# go-to Navigate the browser to the given URL...
|
|
830
|
-
# go-back
|
|
805
|
+
# go-back-or-forward Navigate back or forward in history (direction: back | forward)
|
|
831
806
|
# ...
|
|
832
807
|
|
|
833
808
|
# Filter tools by domain
|
|
@@ -1131,7 +1106,7 @@ The server can be configured using environment variables. Configuration is divid
|
|
|
1131
1106
|
| Variable | Description | Default |
|
|
1132
1107
|
|----------|-------------|---------|
|
|
1133
1108
|
| `NODE_CONSOLE_MESSAGES_BUFFER_SIZE` | Maximum console messages to buffer from Node.js process | `1000` |
|
|
1134
|
-
| `NODE_INSPECTOR_HOST` | Inspector host for `debug_connect` when MCP runs in Docker (e.g. `host.docker.internal`). Use with host-mapped `
|
|
1109
|
+
| `NODE_INSPECTOR_HOST` | Inspector host for `debug_connect` when MCP runs in Docker (e.g. `host.docker.internal`). Use with host-mapped `inspectorPort` so the MCP connects to the right address. | `127.0.0.1` |
|
|
1135
1110
|
| `PLATFORM` | Platform to use: `browser` or `node` | `browser` |
|
|
1136
1111
|
|
|
1137
1112
|
### Browser Platform Configuration
|
|
@@ -1166,7 +1141,7 @@ The server can be configured using environment variables. Configuration is divid
|
|
|
1166
1141
|
**Parameters:**
|
|
1167
1142
|
- `outputPath` (string, optional): Directory path where screenshot will be saved (default: OS temp directory)
|
|
1168
1143
|
- `name` (string, optional): Screenshot name (default: "screenshot")
|
|
1169
|
-
- `selector` (string, optional): CSS selector for element to capture
|
|
1144
|
+
- `selector` (string, optional): Ref (e.g. `e1`, `@e1`), getByRole/getByLabel/getByText/getByPlaceholder/getByTitle/getByAltText/getByTestId expression, or CSS selector for element to capture
|
|
1170
1145
|
- `fullPage` (boolean, optional): Capture full scrollable page (default: false)
|
|
1171
1146
|
- `type` (enum, optional): Image format - "png" or "jpeg" (default: "png")
|
|
1172
1147
|
- `quality` (number, optional): The quality of the image, between 0-100. Not applicable to PNG images, only used for JPEG format (default: 100)
|
|
@@ -1237,7 +1212,7 @@ The server can be configured using environment variables. Configuration is divid
|
|
|
1237
1212
|
<summary><code>interaction_click</code> - Clicks an element on the page.</summary>
|
|
1238
1213
|
|
|
1239
1214
|
**Parameters:**
|
|
1240
|
-
- `selector` (string, required):
|
|
1215
|
+
- `selector` (string, required): Ref (e.g. `e1`, `@e1`, `ref=e1`), getByRole/getByLabel/getByText/getByPlaceholder/getByTitle/getByAltText/getByTestId expression, or CSS selector for the element to click
|
|
1241
1216
|
- `timeoutMs` (number, optional): Time to wait for the element in ms (default: 10000)
|
|
1242
1217
|
</details>
|
|
1243
1218
|
|
|
@@ -1245,7 +1220,7 @@ The server can be configured using environment variables. Configuration is divid
|
|
|
1245
1220
|
<summary><code>interaction_fill</code> - Fills a form input field.</summary>
|
|
1246
1221
|
|
|
1247
1222
|
**Parameters:**
|
|
1248
|
-
- `selector` (string, required):
|
|
1223
|
+
- `selector` (string, required): Ref (e.g. `e1`, `@e1`), getByRole/getByLabel/getByPlaceholder expression, or CSS selector for the input field
|
|
1249
1224
|
- `value` (string, required): Value to fill
|
|
1250
1225
|
- `timeoutMs` (number, optional): Time to wait for the element in ms (default: 10000)
|
|
1251
1226
|
</details>
|
|
@@ -1254,7 +1229,7 @@ The server can be configured using environment variables. Configuration is divid
|
|
|
1254
1229
|
<summary><code>interaction_hover</code> - Hovers over an element.</summary>
|
|
1255
1230
|
|
|
1256
1231
|
**Parameters:**
|
|
1257
|
-
- `selector` (string, required):
|
|
1232
|
+
- `selector` (string, required): Ref (e.g. `e1`, `@e1`), getByRole/getByText/etc. expression, or CSS selector for the element to hover
|
|
1258
1233
|
- `timeoutMs` (number, optional): Time to wait for the element in ms (default: 10000)
|
|
1259
1234
|
</details>
|
|
1260
1235
|
|
|
@@ -1263,7 +1238,7 @@ The server can be configured using environment variables. Configuration is divid
|
|
|
1263
1238
|
|
|
1264
1239
|
**Parameters:**
|
|
1265
1240
|
- `key` (string, required): Key to press (e.g., "Enter", "Escape", "Tab")
|
|
1266
|
-
- `selector` (string, optional):
|
|
1241
|
+
- `selector` (string, optional): Ref (e.g. `e1`, `@e1`), getByRole/getByLabel/etc. expression, or CSS selector to focus before sending the key
|
|
1267
1242
|
- `holdMs` (number, optional): Duration in milliseconds to hold the key (repeat duration if `repeat` is true)
|
|
1268
1243
|
- `repeat` (boolean, optional, default: false): If true, simulates key auto-repeat by pressing repeatedly during `holdMs`
|
|
1269
1244
|
- `repeatIntervalMs` (number, optional, default: 50, min: 10): Interval between repeated key presses in ms (only when `repeat` is true)
|
|
@@ -1274,7 +1249,7 @@ The server can be configured using environment variables. Configuration is divid
|
|
|
1274
1249
|
<summary><code>interaction_select</code> - Selects an option from a dropdown.</summary>
|
|
1275
1250
|
|
|
1276
1251
|
**Parameters:**
|
|
1277
|
-
- `selector` (string, required):
|
|
1252
|
+
- `selector` (string, required): Ref (e.g. `e1`, `@e1`), getByRole/getByTestId expression, or CSS selector for the select element
|
|
1278
1253
|
- `value` (string, required): Value to select
|
|
1279
1254
|
- `timeoutMs` (number, optional): Time to wait for the element in ms (default: 10000)
|
|
1280
1255
|
</details>
|
|
@@ -1283,8 +1258,8 @@ The server can be configured using environment variables. Configuration is divid
|
|
|
1283
1258
|
<summary><code>interaction_drag</code> - Performs drag and drop operation.</summary>
|
|
1284
1259
|
|
|
1285
1260
|
**Parameters:**
|
|
1286
|
-
- `sourceSelector` (string, required):
|
|
1287
|
-
- `targetSelector` (string, required):
|
|
1261
|
+
- `sourceSelector` (string, required): Ref (e.g. `e1`, `@e1`), getByRole/getByText/etc. expression, or CSS selector for the source element
|
|
1262
|
+
- `targetSelector` (string, required): Ref, getByRole/getByText/etc. expression, or CSS selector for the target element
|
|
1288
1263
|
- `timeoutMs` (number, optional): Time to wait for source and target elements in ms (default: 10000)
|
|
1289
1264
|
</details>
|
|
1290
1265
|
|
|
@@ -1293,7 +1268,7 @@ The server can be configured using environment variables. Configuration is divid
|
|
|
1293
1268
|
|
|
1294
1269
|
**Parameters:**
|
|
1295
1270
|
- `mode` (enum, optional): Scroll mode - "by" (relative delta), "to" (absolute position), "top", "bottom", "left", "right" (default: "by")
|
|
1296
|
-
- `selector` (string, optional):
|
|
1271
|
+
- `selector` (string, optional): Ref (e.g. `e1`, `@e1`), getByRole/getByText/etc. expression, or CSS selector for a scrollable container. If omitted, scrolls the document viewport
|
|
1297
1272
|
- `dx` (number, optional): Horizontal scroll delta in pixels (used when mode="by", default: 0)
|
|
1298
1273
|
- `dy` (number, optional): Vertical scroll delta in pixels (used when mode="by", default: 0)
|
|
1299
1274
|
- `x` (number, optional): Absolute horizontal scroll position in pixels (used when mode="to")
|
|
@@ -1387,17 +1362,9 @@ The server can be configured using environment variables. Configuration is divid
|
|
|
1387
1362
|
</details>
|
|
1388
1363
|
|
|
1389
1364
|
<details>
|
|
1390
|
-
<summary><code>navigation_go-back</code> - Navigates
|
|
1365
|
+
<summary><code>navigation_go-back-or-forward</code> - Navigates back or forward in browser history.</summary>
|
|
1391
1366
|
|
|
1392
|
-
**Parameters:** `timeout`, `waitUntil`, `includeSnapshot` (default true), `snapshotInteractiveOnly`, `snapshotCursorInteractive` — same semantics as navigation_go-to for snapshot/refs.
|
|
1393
|
-
|
|
1394
|
-
**Returns:** `url`, `status`, `statusText`, `ok`; when includeSnapshot is true also `output` and `refs` (ARIA snapshot with refs).
|
|
1395
|
-
</details>
|
|
1396
|
-
|
|
1397
|
-
<details>
|
|
1398
|
-
<summary><code>navigation_go-forward</code> - Navigates forward in browser history.</summary>
|
|
1399
|
-
|
|
1400
|
-
**Parameters:** `timeout`, `waitUntil`, `includeSnapshot` (default true), `snapshotInteractiveOnly`, `snapshotCursorInteractive` — same semantics as navigation_go-to for snapshot/refs.
|
|
1367
|
+
**Parameters:** `direction` (required: `"back"` or `"forward"`), `timeout`, `waitUntil`, `includeSnapshot` (default true), `snapshotInteractiveOnly`, `snapshotCursorInteractive` — same semantics as navigation_go-to for snapshot/refs.
|
|
1401
1368
|
|
|
1402
1369
|
**Returns:** `url`, `status`, `statusText`, `ok`; when includeSnapshot is true also `output` and `refs` (ARIA snapshot with refs).
|
|
1403
1370
|
</details>
|
|
@@ -1675,13 +1642,13 @@ The server can be configured using environment variables. Configuration is divid
|
|
|
1675
1642
|
- `output` (string): Includes the page URL, title, and a YAML-formatted accessibility tree
|
|
1676
1643
|
|
|
1677
1644
|
**Usage:**
|
|
1678
|
-
- Use in combination with `
|
|
1645
|
+
- Use in combination with `a11y_take-ax-tree-snapshot` for comprehensive UI analysis
|
|
1679
1646
|
- Provides semantic structure and accessibility roles
|
|
1680
1647
|
- Helps identify accessibility issues and page hierarchy problems
|
|
1681
1648
|
</details>
|
|
1682
1649
|
|
|
1683
1650
|
<details>
|
|
1684
|
-
<summary><code>
|
|
1651
|
+
<summary><code>a11y_take-ax-tree-snapshot</code> - Captures a UI-focused snapshot by combining Chromium's Accessibility (AX) tree with runtime visual diagnostics.</summary>
|
|
1685
1652
|
|
|
1686
1653
|
**Parameters:**
|
|
1687
1654
|
- `roles` (array, optional): Optional role allowlist (button, link, textbox, checkbox, radio, combobox, switch, tab, menuitem, dialog, heading, listbox, listitem, option). If omitted, a built-in set of interactive roles is used
|
|
@@ -2007,6 +1974,52 @@ npm run build
|
|
|
2007
1974
|
- `npm run inspector:http` - Run MCP Inspector (HTTP)
|
|
2008
1975
|
- `npm run lint:check` - Check code formatting
|
|
2009
1976
|
- `npm run lint:format` - Format code
|
|
1977
|
+
- `npm run tools:token-report` - Generate tool definition token consumption report (see below)
|
|
1978
|
+
|
|
1979
|
+
#### Tool definition token report
|
|
1980
|
+
|
|
1981
|
+
The script counts tokens for each tool’s MCP definition (name, description, inputSchema, outputSchema) using **gpt-tokenizer** (OpenAI-style BPE). Useful for understanding context size when clients load the tool list.
|
|
1982
|
+
|
|
1983
|
+
When run without `--platform`, **both browser and node** are measured (default). The script starts the MCP server separately for each platform (`PLATFORM=browser` then `PLATFORM=node`), connects as an MCP client, calls `tools/list` for each, and counts characters from the **actual payload** (so the report matches what clients receive per platform). **Requires a built server** (`npm run build`). Run from the repo root.
|
|
1984
|
+
|
|
1985
|
+
```bash
|
|
1986
|
+
# Via npm (recommended): MCP-based, writes to docs/TOOL-DEFINITION-TOKENS.md
|
|
1987
|
+
npm run tools:token-report
|
|
1988
|
+
```
|
|
1989
|
+
|
|
1990
|
+
**Options:**
|
|
1991
|
+
|
|
1992
|
+
| Option | Description |
|
|
1993
|
+
|--------|-------------|
|
|
1994
|
+
| *(default)* | Run server with output schema disabled (measure name + description + inputSchema only; Output schema column omitted); write to `docs/TOOL-DEFINITION-TOKENS.md` |
|
|
1995
|
+
| `--platform browser` or `--platform node` | Run only one platform (faster; report has a single section) |
|
|
1996
|
+
| `--output-schema` | Run server with output schema enabled (include output schema in measurement and table) |
|
|
1997
|
+
| `--no-output-schema` | Explicitly run without output schema (same as default) |
|
|
1998
|
+
| `--stdout` | Print report to stdout instead of writing to the default file |
|
|
1999
|
+
| `--output=path` or `-o path` | Write report to the given file path |
|
|
2000
|
+
|
|
2001
|
+
Examples:
|
|
2002
|
+
|
|
2003
|
+
```bash
|
|
2004
|
+
# Default: measure without output schema, write to docs/TOOL-DEFINITION-TOKENS.md
|
|
2005
|
+
npm run tools:token-report
|
|
2006
|
+
|
|
2007
|
+
# Include output schema in the report
|
|
2008
|
+
npm run tools:token-report -- --output-schema
|
|
2009
|
+
|
|
2010
|
+
# Print to console
|
|
2011
|
+
npm run tools:token-report -- --stdout
|
|
2012
|
+
|
|
2013
|
+
# Only browser or only node platform
|
|
2014
|
+
npm run tools:token-report -- --platform browser
|
|
2015
|
+
npm run tools:token-report -- --platform node
|
|
2016
|
+
|
|
2017
|
+
# Write to a custom file
|
|
2018
|
+
npm run tools:token-report -- --output=./my-report.md
|
|
2019
|
+
npm run tools:token-report -- -o reports/tokens.md
|
|
2020
|
+
```
|
|
2021
|
+
|
|
2022
|
+
The generated report is [docs/TOOL-DEFINITION-TOKENS.md](docs/TOOL-DEFINITION-TOKENS.md).
|
|
2010
2023
|
|
|
2011
2024
|
## Use Cases
|
|
2012
2025
|
|
|
@@ -2017,7 +2030,7 @@ The Node platform enables AI assistants to:
|
|
|
2017
2030
|
1. **Debug Node.js APIs**: Connect to a running server, set tracepoints at API handlers, capture request/response context
|
|
2018
2031
|
2. **Inspect Backend State**: Use watch expressions and tracepoints to understand variable values, call stacks
|
|
2019
2032
|
3. **Catch Exceptions**: Enable exception breakpoints to capture uncaught errors with full stack traces
|
|
2020
|
-
4. **Docker Debugging**: Connect to Node.js processes running inside Docker containers. When the MCP runs in a container, set `NODE_INSPECTOR_HOST=host.docker.internal` and pass the host-mapped debug port to `debug_connect` (e.g. `debug_connect({ containerName: "my-service",
|
|
2033
|
+
4. **Docker Debugging**: Connect to Node.js processes running inside Docker containers. When the MCP runs in a container, set `NODE_INSPECTOR_HOST=host.docker.internal` and pass the host-mapped debug port to `debug_connect` (e.g. `debug_connect({ containerName: "my-service", inspectorPort: 30019 })`).
|
|
2021
2034
|
|
|
2022
2035
|
### Browser Platform Use Cases
|
|
2023
2036
|
|
|
@@ -2040,7 +2053,7 @@ The Browser platform enables AI assistants to:
|
|
|
2040
2053
|
3. Take a screenshot with `content_take-screenshot` to see the current state
|
|
2041
2054
|
4. Check console messages with `o11y_get-console-messages` for errors
|
|
2042
2055
|
5. Monitor HTTP requests with `o11y_get-http-requests` to see API calls
|
|
2043
|
-
6. Capture accessibility snapshots with `a11y_take-aria-snapshot` and `
|
|
2056
|
+
6. Capture accessibility snapshots with `a11y_take-aria-snapshot` and `a11y_take-ax-tree-snapshot` to understand page structure
|
|
2044
2057
|
7. Compare page with Figma design using `figma_compare-page-with-design` to validate design parity
|
|
2045
2058
|
8. Interact with elements using `interaction_click`, `interaction_fill`, etc.
|
|
2046
2059
|
9. Extract content using `content_get-as-html` or `content_get-as-text`
|
package/SECURITY.md
CHANGED
|
@@ -66,8 +66,6 @@ Browser DevTools MCP provides powerful browser automation capabilities. Users sh
|
|
|
66
66
|
- Tracepoints capture call stack and local variables (may include sensitive data)
|
|
67
67
|
- Logpoints evaluate expressions in page context
|
|
68
68
|
- Exception snapshots capture error state and stack traces
|
|
69
|
-
- Network monitors (netpoints) capture request/response bodies
|
|
70
|
-
- DOM monitors (dompoints) capture element mutations
|
|
71
69
|
- Snapshots are stored in memory and cleared on session end
|
|
72
70
|
|
|
73
71
|
### API Keys and Secrets
|
package/dist/cli/runner.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{platformInfo}from"../core-LM2SLZDP.js";import{AMAZON_BEDROCK_ENABLE,AMAZON_BEDROCK_IMAGE_EMBED_MODEL_ID,AMAZON_BEDROCK_TEXT_EMBED_MODEL_ID,AMAZON_BEDROCK_VISION_MODEL_ID,AWS_PROFILE,AWS_REGION,DAEMON_PORT,DAEMON_SESSION_IDLE_CHECK_SECONDS,DAEMON_SESSION_IDLE_SECONDS,FIGMA_ACCESS_TOKEN,FIGMA_API_BASE_URL,OTEL_ENABLE,OTEL_EXPORTER_HTTP_URL,OTEL_EXPORTER_TYPE,OTEL_SERVICE_NAME,OTEL_SERVICE_VERSION}from"../core-247YVH6X.js";import{Command,Option}from"commander";import{ZodFirstPartyTypeKind}from"zod";function _unwrapZodType(zodType){let current=zodType,isOptional=!1,defaultValue;for(;;){let typeName=current._def.typeName;if(typeName===ZodFirstPartyTypeKind.ZodOptional)isOptional=!0,current=current._def.innerType;else if(typeName===ZodFirstPartyTypeKind.ZodDefault)isOptional=!0,defaultValue=current._def.defaultValue(),current=current._def.innerType;else if(typeName===ZodFirstPartyTypeKind.ZodNullable)isOptional=!0,current=current._def.innerType;else break}return{innerType:current,isOptional,defaultValue}}function _getDescription(zodType){return zodType._def.description}function _toCamelCase(str){return str.replace(/[-_]([a-z])/g,(_,letter)=>letter.toUpperCase())}function _toKebabCase(str){return str.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function _createOption(name,zodType){let{innerType,isOptional,defaultValue}=_unwrapZodType(zodType),description=_getDescription(zodType)||`The ${name} value`,flagName=_toKebabCase(name),typeName=innerType._def.typeName,option;switch(typeName){case ZodFirstPartyTypeKind.ZodString:option=new Option(`--${flagName} <string>`,description);break;case ZodFirstPartyTypeKind.ZodNumber:option=new Option(`--${flagName} <number>`,description),option.argParser(value=>{let n=Number(value);if(!Number.isFinite(n))throw new Error(`Invalid number: ${value}`);return n});break;case ZodFirstPartyTypeKind.ZodBoolean:option=new Option(`--${flagName}`,description);break;case ZodFirstPartyTypeKind.ZodEnum:let enumValues=innerType._def.values;option=new Option(`--${flagName} <choice>`,description).choices(enumValues);break;case ZodFirstPartyTypeKind.ZodArray:option=new Option(`--${flagName} <value...>`,description);break;case ZodFirstPartyTypeKind.ZodObject:case ZodFirstPartyTypeKind.ZodRecord:option=new Option(`--${flagName} <json>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{throw new Error(`Invalid JSON: ${value}`)}});break;case ZodFirstPartyTypeKind.ZodAny:case ZodFirstPartyTypeKind.ZodUnknown:option=new Option(`--${flagName} <value>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{return value}});break;case ZodFirstPartyTypeKind.ZodLiteral:let literalValue=innerType._def.value;typeof literalValue=="boolean"?option=new Option(`--${flagName}`,description):(option=new Option(`--${flagName} <value>`,description),option.default(literalValue));break;case ZodFirstPartyTypeKind.ZodUnion:let unionOptions=innerType._def.options;if(unionOptions.every(opt=>opt._def.typeName===ZodFirstPartyTypeKind.ZodLiteral)){let choices=unionOptions.map(opt=>String(opt._def.value));option=new Option(`--${flagName} <choice>`,description).choices(choices)}else option=new Option(`--${flagName} <value>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{return value}});break;default:option=new Option(`--${flagName} <value>`,description);break}return defaultValue!==void 0&&option.default(defaultValue),!isOptional&&defaultValue===void 0&&option.makeOptionMandatory(!0),option}function _generateOptionsFromSchema(schema){let options=[];for(let[name,zodType]of Object.entries(schema)){let option=_createOption(name,zodType);option&&options.push(option)}return options}function _parseOptionsToToolInput(options){let result={};for(let[key,value]of Object.entries(options)){let camelKey=_toCamelCase(key);value!==void 0&&(result[camelKey]=value)}return result}function _parseToolName(toolName){let underscoreIndex=toolName.indexOf("_");return underscoreIndex===-1?{domain:"default",commandName:toolName}:{domain:toolName.substring(0,underscoreIndex),commandName:toolName.substring(underscoreIndex+1)}}function registerToolCommands(program,tools2,handler){let domainCommands=new Map;for(let tool of tools2){let{domain,commandName}=_parseToolName(tool.name()),domainCommand=domainCommands.get(domain);domainCommand||(domainCommand=new Command(domain).description(`${domain.charAt(0).toUpperCase()+domain.slice(1)} commands`),domainCommands.set(domain,domainCommand),program.addCommand(domainCommand));let toolCommand=new Command(commandName).description(tool.description().trim()),options=_generateOptionsFromSchema(tool.inputSchema());for(let option of options)toolCommand.addOption(option);toolCommand.action(async opts=>{let toolInput=_parseOptionsToToolInput(opts),globalOptions=program.opts();await handler(tool.name(),toolInput,globalOptions)}),domainCommand.addCommand(toolCommand)}}import{spawn,execSync}from"node:child_process";import{createRequire}from"node:module";import*as path from"node:path";import*as readline from"node:readline";import{fileURLToPath}from"node:url";import{Command as Command2,Option as Option2}from"commander";var require2=createRequire(import.meta.url),__filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename),cliProvider=platformInfo.cliInfo.cliProvider,tools=cliProvider.tools,DEFAULT_TIMEOUT=3e4,verboseEnabled=!1,quietEnabled=!1;function _debug(message,data){if(verboseEnabled){let timestamp=new Date().toISOString();data!==void 0?console.error(`[${timestamp}] [DEBUG] ${message}`,data):console.error(`[${timestamp}] [DEBUG] ${message}`)}}function _output(message){quietEnabled||console.log(message)}function _error(message){console.error(message)}async function _isDaemonRunning(port){_debug(`Checking if daemon is running on port ${port}`);try{let response=await fetch(`http://localhost:${port}/health`,{method:"GET",signal:AbortSignal.timeout(3e3)});if(response.ok){let isRunning=(await response.json()).status==="ok";return _debug(`Daemon health check result: ${isRunning?"running":"not running"}`),isRunning}return _debug(`Daemon health check failed: HTTP ${response.status}`),!1}catch(err){return _debug(`Daemon health check error: ${err.message}`),!1}}function _buildDaemonEnv(opts){return cliProvider.buildEnv(opts)}function _startDaemonDetached(opts){let daemonServerPath=path.join(__dirname,"..","daemon-server.js"),env=_buildDaemonEnv(opts);_debug(`Starting daemon server from: ${daemonServerPath}`),_debug(`Daemon port: ${opts.port}`);let child=spawn(process.execPath,[daemonServerPath,"--port",String(opts.port)],{detached:!0,stdio:"ignore",env});child.unref(),_debug(`Daemon process spawned with PID: ${child.pid}`),_output(`Started daemon server as detached process (PID: ${child.pid})`)}async function _ensureDaemonRunning(opts){if(await _isDaemonRunning(opts.port))_debug("Daemon is already running");else{_output(`Daemon server is not running on port ${opts.port}, starting...`),_startDaemonDetached(opts);let maxRetries=10,retryDelay=500;_debug(`Waiting for daemon to be ready (max ${maxRetries} retries, ${retryDelay}ms delay)`);for(let i=0;i<maxRetries;i++)if(await new Promise(resolve=>setTimeout(resolve,retryDelay)),_debug(`Retry ${i+1}/${maxRetries}: checking daemon status...`),await _isDaemonRunning(opts.port)){_debug("Daemon is now ready"),_output("Daemon server is ready");return}throw new Error(`Daemon server failed to start within ${maxRetries*retryDelay/1e3} seconds`)}}async function _stopDaemon(port,timeout){try{return(await fetch(`http://localhost:${port}/shutdown`,{method:"POST",signal:AbortSignal.timeout(timeout)})).ok}catch{return!1}}async function _callTool(port,toolName,toolInput,sessionId,timeout){let headers={"Content-Type":"application/json"};sessionId&&(headers["session-id"]=sessionId);let request={toolName,toolInput};_debug(`Calling tool: ${toolName}`),_debug("Tool input:",toolInput),_debug(`Session ID: ${sessionId||"(default)"}`),_debug(`Timeout: ${timeout||"none"}`);let startTime=Date.now(),response=await fetch(`http://localhost:${port}/call`,{method:"POST",headers,body:JSON.stringify(request),signal:timeout?AbortSignal.timeout(timeout):void 0}),duration=Date.now()-startTime;if(_debug(`Tool call completed in ${duration}ms, status: ${response.status}`),!response.ok){let errorBody=await response.json().catch(()=>({}));throw _debug("Tool call error:",errorBody),new Error(errorBody?.error?.message||`HTTP ${response.status}: ${response.statusText}`)}let result=await response.json();return _debug("Tool call result:",result.toolError?{error:result.toolError}:{success:!0}),result}async function _deleteSession(port,sessionId,timeout){try{return(await fetch(`http://localhost:${port}/session`,{method:"DELETE",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)})).ok}catch{return!1}}async function _getDaemonInfo(port,timeout){try{let response=await fetch(`http://localhost:${port}/info`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _listSessions(port,timeout){try{let response=await fetch(`http://localhost:${port}/sessions`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _getSessionInfo(port,sessionId,timeout){try{let response=await fetch(`http://localhost:${port}/session`,{method:"GET",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}function _formatUptime(seconds){let days=Math.floor(seconds/86400),hours=Math.floor(seconds%86400/3600),minutes=Math.floor(seconds%3600/60),secs=seconds%60,parts=[];return days>0&&parts.push(`${days}d`),hours>0&&parts.push(`${hours}h`),minutes>0&&parts.push(`${minutes}m`),parts.push(`${secs}s`),parts.join(" ")}function _formatTimestamp(timestamp){return new Date(timestamp).toISOString()}function _getZodTypeName(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"||typeName==="ZodDefault"?_getZodTypeName(schema._def.innerType):typeName==="ZodArray"?`${_getZodTypeName(schema._def.type)}[]`:typeName==="ZodEnum"?schema._def.values.join(" | "):typeName==="ZodLiteral"?JSON.stringify(schema._def.value):typeName==="ZodUnion"?schema._def.options.map(opt=>_getZodTypeName(opt)).join(" | "):{ZodString:"string",ZodNumber:"number",ZodBoolean:"boolean",ZodObject:"object",ZodRecord:"Record<string, any>",ZodAny:"any"}[typeName]||typeName.replace("Zod","").toLowerCase()}function _getZodDescription(schema){if(schema._def.description)return schema._def.description;if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"||schema._def.typeName==="ZodDefault")return _getZodDescription(schema._def.innerType)}function _isZodOptional(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"}function _hasZodDefault(schema){return schema._def.typeName==="ZodDefault"?!0:schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"?_hasZodDefault(schema._def.innerType):!1}function _getZodDefault(schema){if(schema._def.typeName==="ZodDefault")return schema._def.defaultValue();if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable")return _getZodDefault(schema._def.innerType)}function _formatOutput(output,indent=0){let prefix=" ".repeat(indent);if(output==null)return`${prefix}(empty)`;if(typeof output=="string")return output.split(`
|
|
2
|
+
import{isToolEnabled,platformInfo}from"../core-RRWTV5B5.js";import{AMAZON_BEDROCK_ENABLE,AMAZON_BEDROCK_IMAGE_EMBED_MODEL_ID,AMAZON_BEDROCK_TEXT_EMBED_MODEL_ID,AMAZON_BEDROCK_VISION_MODEL_ID,AWS_PROFILE,AWS_REGION,DAEMON_PORT,DAEMON_SESSION_IDLE_CHECK_SECONDS,DAEMON_SESSION_IDLE_SECONDS,FIGMA_ACCESS_TOKEN,FIGMA_API_BASE_URL,OTEL_ENABLE,OTEL_EXPORTER_HTTP_URL,OTEL_EXPORTER_TYPE,OTEL_SERVICE_NAME,OTEL_SERVICE_VERSION}from"../core-DOXUXYCD.js";import{Command,Option}from"commander";import{ZodFirstPartyTypeKind}from"zod";function _unwrapZodType(zodType){let current=zodType,isOptional=!1,defaultValue;for(;;){let typeName=current._def.typeName;if(typeName===ZodFirstPartyTypeKind.ZodOptional)isOptional=!0,current=current._def.innerType;else if(typeName===ZodFirstPartyTypeKind.ZodDefault)isOptional=!0,defaultValue=current._def.defaultValue(),current=current._def.innerType;else if(typeName===ZodFirstPartyTypeKind.ZodNullable)isOptional=!0,current=current._def.innerType;else break}return{innerType:current,isOptional,defaultValue}}function _getDescription(zodType){return zodType._def.description}function _toCamelCase(str){return str.replace(/[-_]([a-z])/g,(_,letter)=>letter.toUpperCase())}function _toKebabCase(str){return str.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function _createOption(name,zodType){let{innerType,isOptional,defaultValue}=_unwrapZodType(zodType),description=_getDescription(zodType)||`The ${name} value`,flagName=_toKebabCase(name),typeName=innerType._def.typeName,option;switch(typeName){case ZodFirstPartyTypeKind.ZodString:option=new Option(`--${flagName} <string>`,description);break;case ZodFirstPartyTypeKind.ZodNumber:option=new Option(`--${flagName} <number>`,description),option.argParser(value=>{let n=Number(value);if(!Number.isFinite(n))throw new Error(`Invalid number: ${value}`);return n});break;case ZodFirstPartyTypeKind.ZodBoolean:option=new Option(`--${flagName}`,description);break;case ZodFirstPartyTypeKind.ZodEnum:let enumValues=innerType._def.values;option=new Option(`--${flagName} <choice>`,description).choices(enumValues);break;case ZodFirstPartyTypeKind.ZodArray:option=new Option(`--${flagName} <value...>`,description);break;case ZodFirstPartyTypeKind.ZodObject:case ZodFirstPartyTypeKind.ZodRecord:option=new Option(`--${flagName} <json>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{throw new Error(`Invalid JSON: ${value}`)}});break;case ZodFirstPartyTypeKind.ZodAny:case ZodFirstPartyTypeKind.ZodUnknown:option=new Option(`--${flagName} <value>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{return value}});break;case ZodFirstPartyTypeKind.ZodLiteral:let literalValue=innerType._def.value;typeof literalValue=="boolean"?option=new Option(`--${flagName}`,description):(option=new Option(`--${flagName} <value>`,description),option.default(literalValue));break;case ZodFirstPartyTypeKind.ZodUnion:let unionOptions=innerType._def.options;if(unionOptions.every(opt=>opt._def.typeName===ZodFirstPartyTypeKind.ZodLiteral)){let choices=unionOptions.map(opt=>String(opt._def.value));option=new Option(`--${flagName} <choice>`,description).choices(choices)}else option=new Option(`--${flagName} <value>`,description),option.argParser(value=>{try{return JSON.parse(value)}catch{return value}});break;default:option=new Option(`--${flagName} <value>`,description);break}return defaultValue!==void 0&&option.default(defaultValue),!isOptional&&defaultValue===void 0&&option.makeOptionMandatory(!0),option}function _generateOptionsFromSchema(schema,toolName){let options=[];for(let[name,zodType]of Object.entries(schema)){let option=_createOption(name,zodType);option&&options.push(option)}return options}function _parseOptionsToToolInput(options){let result={};for(let[key,value]of Object.entries(options)){let camelKey=_toCamelCase(key);value!==void 0&&(result[camelKey]=value)}return result}function _parseToolName(toolName){let underscoreIndex=toolName.indexOf("_");return underscoreIndex===-1?{domain:"default",commandName:toolName}:{domain:toolName.substring(0,underscoreIndex),commandName:toolName.substring(underscoreIndex+1)}}function registerToolCommands(program,tools2,handler){let domainCommands=new Map;for(let tool of tools2){let{domain,commandName}=_parseToolName(tool.name()),domainCommand=domainCommands.get(domain);domainCommand||(domainCommand=new Command(domain).description(`${domain.charAt(0).toUpperCase()+domain.slice(1)} commands`),domainCommands.set(domain,domainCommand),program.addCommand(domainCommand));let toolCommand=new Command(commandName).description(tool.description().trim()),options=_generateOptionsFromSchema(tool.inputSchema(),tool.name());for(let option of options)toolCommand.addOption(option);toolCommand.action(async opts=>{let toolInput=_parseOptionsToToolInput(opts),globalOptions=program.opts();await handler(tool.name(),toolInput,globalOptions)}),domainCommand.addCommand(toolCommand)}}import{spawn,execSync}from"node:child_process";import{createRequire}from"node:module";import*as path from"node:path";import*as readline from"node:readline";import{fileURLToPath}from"node:url";import{Command as Command2,Option as Option2}from"commander";var require2=createRequire(import.meta.url),__filename=fileURLToPath(import.meta.url),__dirname=path.dirname(__filename),cliProvider=platformInfo.cliInfo.cliProvider,tools=cliProvider.tools.filter(isToolEnabled),DEFAULT_TIMEOUT=3e4,verboseEnabled=!1,quietEnabled=!1;function _debug(message,data){if(verboseEnabled){let timestamp=new Date().toISOString();data!==void 0?console.error(`[${timestamp}] [DEBUG] ${message}`,data):console.error(`[${timestamp}] [DEBUG] ${message}`)}}function _output(message){quietEnabled||console.log(message)}function _error(message){console.error(message)}async function _isDaemonRunning(port){_debug(`Checking if daemon is running on port ${port}`);try{let response=await fetch(`http://localhost:${port}/health`,{method:"GET",signal:AbortSignal.timeout(3e3)});if(response.ok){let isRunning=(await response.json()).status==="ok";return _debug(`Daemon health check result: ${isRunning?"running":"not running"}`),isRunning}return _debug(`Daemon health check failed: HTTP ${response.status}`),!1}catch(err){return _debug(`Daemon health check error: ${err.message}`),!1}}function _buildDaemonEnv(opts){return cliProvider.buildEnv(opts)}function _startDaemonDetached(opts){let daemonServerPath=path.join(__dirname,"..","daemon-server.js"),env=_buildDaemonEnv(opts);_debug(`Starting daemon server from: ${daemonServerPath}`),_debug(`Daemon port: ${opts.port}`);let child=spawn(process.execPath,[daemonServerPath,"--port",String(opts.port)],{detached:!0,stdio:"ignore",env});child.unref(),_debug(`Daemon process spawned with PID: ${child.pid}`),_output(`Started daemon server as detached process (PID: ${child.pid})`)}async function _ensureDaemonRunning(opts){if(await _isDaemonRunning(opts.port))_debug("Daemon is already running");else{_output(`Daemon server is not running on port ${opts.port}, starting...`),_startDaemonDetached(opts);let maxRetries=10,retryDelay=500;_debug(`Waiting for daemon to be ready (max ${maxRetries} retries, ${retryDelay}ms delay)`);for(let i=0;i<maxRetries;i++)if(await new Promise(resolve=>setTimeout(resolve,retryDelay)),_debug(`Retry ${i+1}/${maxRetries}: checking daemon status...`),await _isDaemonRunning(opts.port)){_debug("Daemon is now ready"),_output("Daemon server is ready");return}throw new Error(`Daemon server failed to start within ${maxRetries*retryDelay/1e3} seconds`)}}async function _stopDaemon(port,timeout){try{return(await fetch(`http://localhost:${port}/shutdown`,{method:"POST",signal:AbortSignal.timeout(timeout)})).ok}catch{return!1}}async function _callTool(port,toolName,toolInput,sessionId,timeout){let headers={"Content-Type":"application/json"};sessionId&&(headers["session-id"]=sessionId);let request={toolName,toolInput};_debug(`Calling tool: ${toolName}`),_debug("Tool input:",toolInput),_debug(`Session ID: ${sessionId||"(default)"}`),_debug(`Timeout: ${timeout||"none"}`);let startTime=Date.now(),response=await fetch(`http://localhost:${port}/call`,{method:"POST",headers,body:JSON.stringify(request),signal:timeout?AbortSignal.timeout(timeout):void 0}),duration=Date.now()-startTime;if(_debug(`Tool call completed in ${duration}ms, status: ${response.status}`),!response.ok){let errorBody=await response.json().catch(()=>({}));_debug("Tool call error:",errorBody);let message=errorBody?.toolError?.message||errorBody?.error?.message||`HTTP ${response.status}: ${response.statusText}`;throw new Error(message)}let result=await response.json();return _debug("Tool call result:",result.toolError?{error:result.toolError}:{success:!0}),result}async function _deleteSession(port,sessionId,timeout){try{return(await fetch(`http://localhost:${port}/session`,{method:"DELETE",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)})).ok}catch{return!1}}async function _getDaemonInfo(port,timeout){try{let response=await fetch(`http://localhost:${port}/info`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _listSessions(port,timeout){try{let response=await fetch(`http://localhost:${port}/sessions`,{method:"GET",signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}async function _getSessionInfo(port,sessionId,timeout){try{let response=await fetch(`http://localhost:${port}/session`,{method:"GET",headers:{"session-id":sessionId},signal:AbortSignal.timeout(timeout)});return response.ok?await response.json():null}catch{return null}}function _formatUptime(seconds){let days=Math.floor(seconds/86400),hours=Math.floor(seconds%86400/3600),minutes=Math.floor(seconds%3600/60),secs=seconds%60,parts=[];return days>0&&parts.push(`${days}d`),hours>0&&parts.push(`${hours}h`),minutes>0&&parts.push(`${minutes}m`),parts.push(`${secs}s`),parts.join(" ")}function _formatTimestamp(timestamp){return new Date(timestamp).toISOString()}function _getZodTypeName(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"||typeName==="ZodDefault"?_getZodTypeName(schema._def.innerType):typeName==="ZodArray"?`${_getZodTypeName(schema._def.type)}[]`:typeName==="ZodEnum"?schema._def.values.join(" | "):typeName==="ZodLiteral"?JSON.stringify(schema._def.value):typeName==="ZodUnion"?schema._def.options.map(opt=>_getZodTypeName(opt)).join(" | "):{ZodString:"string",ZodNumber:"number",ZodBoolean:"boolean",ZodObject:"object",ZodRecord:"Record<string, any>",ZodAny:"any"}[typeName]||typeName.replace("Zod","").toLowerCase()}function _getZodDescription(schema){if(schema._def.description)return schema._def.description;if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"||schema._def.typeName==="ZodDefault")return _getZodDescription(schema._def.innerType)}function _isZodOptional(schema){let typeName=schema._def.typeName;return typeName==="ZodOptional"||typeName==="ZodNullable"}function _hasZodDefault(schema){return schema._def.typeName==="ZodDefault"?!0:schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable"?_hasZodDefault(schema._def.innerType):!1}function _getZodDefault(schema){if(schema._def.typeName==="ZodDefault")return schema._def.defaultValue();if(schema._def.typeName==="ZodOptional"||schema._def.typeName==="ZodNullable")return _getZodDefault(schema._def.innerType)}function _formatOutput(output,indent=0){let prefix=" ".repeat(indent);if(output==null)return`${prefix}(empty)`;if(typeof output=="string")return output.split(`
|
|
3
3
|
`).map(line=>`${prefix}${line}`).join(`
|
|
4
4
|
`);if(typeof output=="number"||typeof output=="boolean")return`${prefix}${output}`;if(Array.isArray(output))return output.length===0?`${prefix}[]`:output.map(item=>_formatOutput(item,indent)).join(`
|
|
5
5
|
`);if(typeof output=="object"){let lines=[];for(let[key,value]of Object.entries(output))value!==void 0&&(typeof value=="object"&&value!==null&&!Array.isArray(value)?(lines.push(`${prefix}${key}:`),lines.push(_formatOutput(value,indent+1))):Array.isArray(value)?(lines.push(`${prefix}${key}:`),lines.push(_formatOutput(value,indent+1))):lines.push(`${prefix}${key}: ${value}`));return lines.join(`
|
|
6
6
|
`)}return`${prefix}${String(output)}`}function _printOutput(data,json,isError=!1){let output=json?JSON.stringify(data,null,2):String(data);isError?console.error(output):console.log(output)}function _addGlobalOptions(cmd){return cmd.addOption(new Option2("--port <number>","Daemon server port").argParser(value=>{let n=Number(value);if(!Number.isInteger(n)||n<1||n>65535)throw new Error("Port must be an integer between 1 and 65535");return n}).default(DAEMON_PORT)).addOption(new Option2("--session-id <string>","Session ID for maintaining state across commands")).addOption(new Option2("--json","Output results as JSON")).addOption(new Option2("--quiet","Suppress log messages, only show output")).addOption(new Option2("--verbose","Enable verbose/debug output")).addOption(new Option2("--timeout <ms>","Timeout for operations in milliseconds").argParser(value=>{let n=Number(value);if(!Number.isFinite(n)||n<0)throw new Error("Timeout must be a positive number");return n}).default(DEFAULT_TIMEOUT)),cliProvider.addOptions(cmd)}async function main(){let program=_addGlobalOptions(new Command2(cliProvider.cliName).description(cliProvider.cliDescription).version(require2("../../package.json").version));program.hook("preAction",thisCommand=>{let opts=thisCommand.opts();opts.verbose&&(verboseEnabled=!0),opts.quiet&&(quietEnabled=!0),_debug("Verbose mode enabled"),_debug("CLI version:",require2("../../package.json").version),_debug("Node version:",process.version),_debug("Platform:",process.platform)});let daemonCmd=new Command2("daemon").description("Manage the daemon server");daemonCmd.command("start").description("Start the daemon server").action(async()=>{let opts=program.opts();if(await _isDaemonRunning(opts.port)){opts.json?_printOutput({status:"already_running",port:opts.port},!0):_output(`Daemon server is already running on port ${opts.port}`);return}_startDaemonDetached(opts);let maxRetries=10,retryDelay=500;for(let i=0;i<maxRetries;i++)if(await new Promise(resolve=>setTimeout(resolve,retryDelay)),await _isDaemonRunning(opts.port)){opts.json?_printOutput({status:"started",port:opts.port},!0):_output(`Daemon server started on port ${opts.port}`);return}opts.json?_printOutput({status:"failed",error:"Daemon server failed to start"},!0,!0):_error("Failed to start daemon server"),process.exit(1)}),daemonCmd.command("stop").description("Stop the daemon server").action(async()=>{let opts=program.opts();if(!await _isDaemonRunning(opts.port)){opts.json?_printOutput({status:"not_running",port:opts.port},!0):_output(`Daemon server is not running on port ${opts.port}`);return}await _stopDaemon(opts.port,opts.timeout??DEFAULT_TIMEOUT)?opts.json?_printOutput({status:"stopped",port:opts.port},!0):_output(`Daemon server stopped on port ${opts.port}`):(opts.json?_printOutput({status:"failed",error:"Failed to stop daemon server"},!0,!0):_error("Failed to stop daemon server"),process.exit(1))}),daemonCmd.command("restart").description("Restart the daemon server (stop + start)").action(async()=>{let opts=program.opts(),wasRunning=await _isDaemonRunning(opts.port);wasRunning&&(_debug("Stopping daemon server..."),await _stopDaemon(opts.port,opts.timeout??DEFAULT_TIMEOUT)||(opts.json?_printOutput({status:"failed",error:"Failed to stop daemon server"},!0,!0):_error("Failed to stop daemon server"),process.exit(1)),_debug("Waiting for port to be released..."),await new Promise(resolve=>setTimeout(resolve,1e3))),_debug("Starting daemon server..."),_startDaemonDetached(opts);let maxRetries=10,retryDelay=500;for(let i=0;i<maxRetries;i++)if(await new Promise(resolve=>setTimeout(resolve,retryDelay)),await _isDaemonRunning(opts.port)){opts.json?_printOutput({status:"restarted",port:opts.port},!0):_output(`Daemon server ${wasRunning?"restarted":"started"} on port ${opts.port}`);return}opts.json?_printOutput({status:"failed",error:"Daemon server failed to start"},!0,!0):_error("Failed to start daemon server"),process.exit(1)}),daemonCmd.command("status").description("Check daemon server status").action(async()=>{let opts=program.opts(),isRunning=await _isDaemonRunning(opts.port);opts.json?_printOutput({status:isRunning?"running":"stopped",port:opts.port},!0):_output(isRunning?`Daemon server is running on port ${opts.port}`:`Daemon server is not running on port ${opts.port}`)}),daemonCmd.command("info").description("Get detailed daemon server information").action(async()=>{let opts=program.opts();await _isDaemonRunning(opts.port)||(opts.json?_printOutput({status:"not_running",port:opts.port},!0,!0):_error(`Daemon server is not running on port ${opts.port}`),process.exit(1));let info=await _getDaemonInfo(opts.port,opts.timeout??DEFAULT_TIMEOUT);info?opts.json?_printOutput(info,!0):(_output("Daemon Server Information:"),_output(` Version: ${info.version}`),_output(` Port: ${info.port}`),_output(` Uptime: ${_formatUptime(info.uptime)}`),_output(` Sessions: ${info.sessionCount}`)):(opts.json?_printOutput({status:"error",error:"Failed to get daemon info"},!0,!0):_error("Failed to get daemon info"),process.exit(1))}),program.addCommand(daemonCmd);let sessionCmd=new Command2("session").description(cliProvider.sessionDescription);sessionCmd.command("list").description("List all active sessions").action(async()=>{let opts=program.opts();try{await _ensureDaemonRunning(opts);let result=await _listSessions(opts.port,opts.timeout??DEFAULT_TIMEOUT);if(result)if(opts.json)_printOutput(result,!0);else if(result.sessions.length===0)_output("No active sessions");else{_output(`Active Sessions (${result.sessions.length}):`);for(let session of result.sessions)_output(` ${session.id}`),_output(` Created: ${_formatTimestamp(session.createdAt)}`),_output(` Last Active: ${_formatTimestamp(session.lastActiveAt)}`),_output(` Idle: ${_formatUptime(session.idleSeconds)}`)}else opts.json?_printOutput({status:"error",error:"Failed to list sessions"},!0,!0):_error("Failed to list sessions"),process.exit(1)}catch(err){opts.json?_printOutput({status:"error",error:err.message},!0,!0):_error(`Error: ${err.message}`),process.exit(1)}}),sessionCmd.command("info <session-id>").description("Get information about a specific session").action(async sessionId=>{let opts=program.opts();try{await _ensureDaemonRunning(opts);let info=await _getSessionInfo(opts.port,sessionId,opts.timeout??DEFAULT_TIMEOUT);info?opts.json?_printOutput(info,!0):(_output(`Session: ${info.id}`),_output(` Created: ${_formatTimestamp(info.createdAt)}`),_output(` Last Active: ${_formatTimestamp(info.lastActiveAt)}`),_output(` Idle: ${_formatUptime(info.idleSeconds)}`)):(opts.json?_printOutput({status:"not_found",sessionId},!0,!0):_error(`Session '${sessionId}' not found`),process.exit(1))}catch(err){opts.json?_printOutput({status:"error",error:err.message},!0,!0):_error(`Error: ${err.message}`),process.exit(1)}}),sessionCmd.command("delete <session-id>").description("Delete a specific session").action(async sessionId=>{let opts=program.opts();try{await _ensureDaemonRunning(opts),await _deleteSession(opts.port,sessionId,opts.timeout??DEFAULT_TIMEOUT)?opts.json?_printOutput({status:"deleted",sessionId},!0):_output(`Session '${sessionId}' deleted`):(opts.json?_printOutput({status:"not_found",sessionId},!0,!0):_error(`Session '${sessionId}' not found or already deleted`),process.exit(1))}catch(err){opts.json?_printOutput({status:"error",error:err.message},!0,!0):_error(`Error: ${err.message}`),process.exit(1)}}),program.addCommand(sessionCmd);let toolsCmd=new Command2("tools").description("List and inspect available tools");toolsCmd.command("list").description("List all available tools").option("--domain <domain>","Filter by domain (e.g., navigation, content, interaction)").action(cmdOpts=>{let opts=program.opts(),toolsByDomain=new Map;for(let tool of tools){let domain=tool.name().split("_")[0];cmdOpts.domain&&domain!==cmdOpts.domain||(toolsByDomain.has(domain)||toolsByDomain.set(domain,[]),toolsByDomain.get(domain).push(tool))}if(opts.json){let result=[];for(let[domain,domainTools]of toolsByDomain)result.push({domain,tools:domainTools.map(t=>({name:t.name(),description:t.description().trim().split(`
|
|
7
7
|
`)[0]}))});_printOutput(result,!0)}else{if(toolsByDomain.size===0){_output("No tools found");return}_output(`Available Tools (${tools.length} total):
|
|
8
8
|
`);for(let[domain,domainTools]of toolsByDomain){_output(` ${domain}:`);for(let tool of domainTools){let name=tool.name().split("_")[1]||tool.name(),desc=tool.description().trim().split(`
|
|
9
|
-
`)[0];_output(` ${name.padEnd(30)} ${desc}`)}_output("")}}}),toolsCmd.command("info <tool-name>").description(
|
|
9
|
+
`)[0];_output(` ${name.padEnd(30)} ${desc}`)}_output("")}}}),toolsCmd.command("info <tool-name> [other-parts...]").description('Get detailed information about a specific tool (e.g. "tools info navigation go-to" or "tools info navigation_go-to")').action((toolName,otherParts=[])=>{let opts=program.opts(),resolvedName=otherParts.length>0?[toolName,...otherParts].join("_"):toolName,tool=tools.find(t=>t.name()===resolvedName);tool||(tool=tools.find(t=>t.name().split("_")[1]===resolvedName)),tool||(opts.json?_printOutput({status:"not_found",toolName:resolvedName},!0,!0):_error(`Tool '${resolvedName}' not found`),process.exit(1));let inputSchema=tool.inputSchema(),params=[];for(let[key,schema]of Object.entries(inputSchema))params.push({name:key,type:_getZodTypeName(schema),required:!_isZodOptional(schema),description:_getZodDescription(schema),default:_hasZodDefault(schema)?_getZodDefault(schema):void 0});if(opts.json)_printOutput({name:tool.name(),description:tool.description().trim(),parameters:params},!0);else{let nameParts=tool.name().split("_");if(_output(`Tool: ${tool.name()}`),_output(`Domain: ${nameParts[0]}`),_output(`
|
|
10
10
|
Description:`),_output(tool.description().trim().split(`
|
|
11
11
|
`).map(line=>` ${line}`).join(`
|
|
12
12
|
`)),_output(`
|
|
@@ -146,7 +146,7 @@ A new version is available!`),cmdOpts.check||_output(`Run: npm install -g ${pack
|
|
|
146
146
|
`);try{await _ensureDaemonRunning(opts)}catch(err){_error(`Error: ${err.message}`),process.exit(1)}let replProgram=_createReplProgram(opts),rl=readline.createInterface({input:process.stdin,output:process.stdout,prompt:cliProvider.replPrompt});rl.prompt(),rl.on("line",async line=>{let input=line.trim();if(!input){rl.prompt();return}if((input==="exit"||input==="quit")&&(_output("Goodbye!"),rl.close(),process.exit(0)),input==="help"){_output(`
|
|
147
147
|
REPL Commands:`),_output(" help Show this help"),_output(" exit, quit Exit interactive mode"),_output(`
|
|
148
148
|
Available Commands:`),_output(" status Show daemon status summary"),_output(" config Show current configuration"),_output(" update Check for CLI updates"),_output(" daemon <cmd> Daemon management (start, stop, restart, status, info)"),_output(" session <cmd> Session management (list, info, delete)"),_output(" tools <cmd> Tool discovery (list, search, info)"),_output(" <domain> <tool> Execute a tool (e.g., navigation go-to --url ...)"),_output(`
|
|
149
|
-
Examples:`),_output(" # Daemon & Session"),_output(" daemon status"),_output(" daemon info"),_output(" session list"),_output(" session delete my-session"),_output(""),_output(" # Tool Discovery"),_output(" tools list"),_output(" tools search screenshot"),_output(" tools info navigation_go-to"),_output(""),_output(" # Navigation"),_output(' navigation go-to --url "https://example.com"'),_output(" navigation go-back"),_output(" navigation reload"),_output(""),_output(" # Content"),_output(' content take-screenshot --name "test"'),_output(" content get-as-text"),_output(' content get-as-html --selector "#main"'),_output(""),_output(" # Interaction"),_output(' interaction click --ref "Submit"'),_output(' interaction fill --ref "Email" --value "test@example.com"'),_output(' interaction hover --ref "Menu"'),_output(""),_output(" # Accessibility"),_output(" a11y get-snapshot"),_output(" a11y get-ax-tree-snapshot"),_output(`
|
|
149
|
+
Examples:`),_output(" # Daemon & Session"),_output(" daemon status"),_output(" daemon info"),_output(" session list"),_output(" session delete my-session"),_output(""),_output(" # Tool Discovery"),_output(" tools list"),_output(" tools search screenshot"),_output(" tools info navigation_go-to"),_output(""),_output(" # Navigation"),_output(' navigation go-to --url "https://example.com"'),_output(" navigation go-back-or-forward --direction back"),_output(" navigation reload"),_output(""),_output(" # Content"),_output(' content take-screenshot --name "test"'),_output(" content get-as-text"),_output(' content get-as-html --selector "#main"'),_output(""),_output(" # Interaction"),_output(' interaction click --ref "Submit"'),_output(' interaction fill --ref "Email" --value "test@example.com"'),_output(' interaction hover --ref "Menu"'),_output(""),_output(" # Accessibility"),_output(" a11y get-snapshot"),_output(" a11y get-ax-tree-snapshot"),_output(`
|
|
150
150
|
Tip: Use global options when starting interactive mode:`);for(let example of cliProvider.cliExamples)_output(` ${example}`);_output(""),rl.prompt();return}try{let args=_parseReplInput(input);await replProgram.parseAsync(["node","repl",...args])}catch(err){err.code==="commander.help"||(err.code==="commander.unknownCommand"?(_output(`Unknown command: ${input}`),_output('Type "help" for available commands')):err.code==="commander.missingArgument"?_error(`Missing argument: ${err.message}`):err.code==="commander.invalidArgument"?_error(`Invalid argument: ${err.message}`):err.code&&err.code.startsWith("commander.")||_error(`Error: ${err.message}`))}rl.prompt()}),rl.on("close",()=>{process.exit(0)})});program.addCommand(interactiveCmd);let updateCmd=new Command2("update").description("Check for updates and optionally install them").option("--check","Only check for updates without installing").action(async cmdOpts=>{let opts=program.opts(),currentVersion=require2("../../package.json").version,packageName=cliProvider.packageName;_output(`Checking for updates...
|
|
151
151
|
`);try{let response=await fetch(`https://registry.npmjs.org/${packageName}/latest`,{method:"GET",signal:AbortSignal.timeout(1e4)});if(!response.ok)throw new Error(`Failed to check npm registry: HTTP ${response.status}`);let latestVersion=(await response.json()).version;if(opts.json){_printOutput({currentVersion,latestVersion,updateAvailable:latestVersion!==currentVersion},!0);return}if(_output(` Current version: ${currentVersion}`),_output(` Latest version: ${latestVersion}`),_output(""),latestVersion===currentVersion){_output("\x1B[32m\u2713 You are using the latest version!\x1B[0m");return}let currentParts=currentVersion.split(".").map(Number),latestParts=latestVersion.split(".").map(Number),isNewer=!1;for(let i=0;i<3;i++)if(latestParts[i]>currentParts[i]){isNewer=!0;break}else if(latestParts[i]<currentParts[i])break;if(!isNewer){_output("\x1B[32m\u2713 You are using a newer version than published!\x1B[0m");return}if(_output(`\x1B[33m\u26A0 Update available: ${currentVersion} \u2192 ${latestVersion}\x1B[0m
|
|
152
152
|
`),cmdOpts.check){_output("To update, run:"),_output(` npm install -g ${packageName}@latest`),_output("or"),_output(` npx ${packageName}@latest`);return}let rl=readline.createInterface({input:process.stdin,output:process.stdout}),answer=await new Promise(resolve=>{rl.question("Do you want to update now? (y/N) ",ans=>{rl.close(),resolve(ans.toLowerCase())})});if(answer!=="y"&&answer!=="yes"){_output(`
|