browser-devtools-mcp 0.3.3 → 0.3.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 CHANGED
@@ -45,7 +45,7 @@ Choose the platform by running the appropriate MCP server or CLI:
45
45
  - **Browser Automation**: Navigation, input, clicking, scrolling, viewport control
46
46
  - **Execution Monitoring**: Console message capture, HTTP request/response tracking
47
47
  - **OpenTelemetry Integration**: Automatic trace injection into web pages, UI trace collection, and backend trace correlation via trace context propagation
48
- - **JavaScript Execution**: Execute code in browser page context or in Node.js VM sandbox on the server
48
+ - **JavaScript Execution**: Use the **execute** tool to batch-execute tool calls and run custom JavaScript; on the browser platform the VM receives \`page\` (Playwright Page) so you can use \`page.evaluate()\` or the Playwright API
49
49
  - **Session Management**: Long-lived, session-based debugging with automatic cleanup
50
50
  - **Multiple Transport Modes**: Supports both stdio and HTTP transports
51
51
 
@@ -53,7 +53,6 @@ Choose the platform by running the appropriate MCP server or CLI:
53
53
 
54
54
  - **Connection**: Connect to Node.js processes by PID, process name, port, WebSocket URL, or Docker container
55
55
  - **Non-Blocking Debugging**: Tracepoints, logpoints, exceptionpoints without pausing execution
56
- - **JavaScript Execution**: Run arbitrary JavaScript in the connected Node process via `run_js-in-node` (CDP Runtime.evaluate)—use `return` to get output; async/await supported. Inspect `process.memoryUsage()`, call `require()` modules, read globals
57
56
  - **Source Map Support**: Resolves bundled/minified code to original source locations; `debug_resolve-source-location` translates stack traces and bundle locations to original source
58
57
  - **OpenTelemetry Integration**: When the Node process uses `@opentelemetry/api`, tracepoint/logpoint snapshots automatically include `traceContext` (traceId, spanId) for correlating backend traces with browser traces
59
58
  - **Docker Support**: Connect to Node.js processes running inside Docker containers (`containerId` / `containerName`)
@@ -77,17 +76,16 @@ Choose the platform by running the appropriate MCP server or CLI:
77
76
  - **Resize Window**: Resize the real browser window (OS-level) using Chrome DevTools Protocol
78
77
 
79
78
  ### Navigation Tools
80
- - **Go To**: Navigate to URLs with configurable wait strategies. By default (`includeSnapshot: true`) returns an ARIA snapshot with refs (`output`, `refs`); set `includeSnapshot: false` for url/status/ok only. Use **snapshotOptions** (e.g. `interactiveOnly`, `cursorInteractive`) to control which elements get refs (same as `a11y_take-aria-snapshot`). Optional **includeScreenshot** saves a screenshot to disk and returns `screenshotFilePath`; use **screenshotOptions** (outputPath, name, fullPage, type, annotate, includeBase64) — defaults: OS temp dir, name "screenshot"; set `includeBase64: true` only when the file cannot be read from the path (e.g. remote, container).
81
- - **Go Back / Go Forward**: Navigate backward or forward in history. Same snapshot/refs and optional screenshot behavior as Go To (snapshotOptions, includeScreenshot, screenshotOptions).
82
- - **Reload**: Reload the current page. Same snapshot/refs and optional screenshot behavior as Go To (snapshotOptions, includeScreenshot, screenshotOptions).
79
+ - **Go To**: Navigate to URLs with configurable wait strategies. By default **waitForNavigation** is `true`: after navigation completes, waits for network idle before taking snapshot/screenshot; set `waitForNavigation: false` to skip the network idle wait. **waitForTimeoutMs** (default 30000) is the timeout for that wait when `waitForNavigation` is true. By default (`includeSnapshot: true`) returns an ARIA snapshot with refs (`output`, `refs`); set `includeSnapshot: false` for url/status/ok only. Use **snapshotOptions** (e.g. `interactiveOnly`, `cursorInteractive`) to control which elements get refs (same as `a11y_take-aria-snapshot`). Optional **includeScreenshot** saves a screenshot to disk and returns `screenshotFilePath`; use **screenshotOptions** (outputPath, name, fullPage, type, annotate, includeBase64) — defaults: OS temp dir, name "screenshot"; set `includeBase64: true` only when the file cannot be read from the path (e.g. remote, container).
80
+ - **Go Back / Go Forward**: Navigate backward or forward in history. Same **waitForNavigation** (default true), **waitForTimeoutMs**, snapshot/refs and optional screenshot behavior as Go To.
81
+ - **Reload**: Reload the current page. Same **waitForNavigation** (default true), **waitForTimeoutMs**, snapshot/refs and optional screenshot behavior as Go To.
83
82
 
84
83
  ### Run Tools
85
- - **JS in Browser**: Execute JavaScript code inside the active browser page (page context with access to window, document, DOM, and Web APIs)
86
- - **JS in Sandbox**: Execute JavaScript code in a Node.js VM sandbox on the MCP server (with access to Playwright Page, console logging, and safe built-ins)
84
+ - **Execute**: Batch-execute multiple tool calls in a single request via custom JavaScript. Use `callTool(name, input, returnOutput?)` to invoke any registered tool. Reduces round-trips and token usage. Includes wall-clock timeout, max tool call limit (50), console log capture, and fail-fast error handling with `failedTool` diagnostics. On the **browser** platform the VM also receives the session execution context: `page` (Playwright Page) is available — use the Playwright API (e.g. `page.locator()`, `page.goto()`) or `page.evaluate()` to run script in the browser. On the **Node** platform no extra bindings are injected. **Also available via CLI**: use the subcommand `run execute` or call the daemon HTTP API (POST `/call`) with `toolName: "execute"`.
87
85
 
88
86
  ### Observability (O11Y) Tools
89
87
  - **Console Messages**: Capture and filter browser console logs with advanced filtering (level, search, timestamp, sequence number)
90
- - **HTTP Requests**: Monitor network traffic with detailed request/response data, filtering by resource type, status code, and more
88
+ - **HTTP Requests**: Monitor network traffic with filtering by resource type, status code, and more. Request headers, response headers, and response body are opt-in (`includeRequestHeaders`, `includeResponseHeaders`, `includeResponseBody`; default off). Response body is not stored for static assets (e.g. .js, .css, .map)
91
89
  - **Web Vitals**: Collect Core Web Vitals (LCP, INP, CLS) and supporting metrics (TTFB, FCP) with ratings and recommendations based on Google's thresholds
92
90
  - **OpenTelemetry Tracing**: Automatic trace injection into web pages, UI trace collection (document load, fetch, XMLHttpRequest, user interactions), and trace context propagation for backend correlation
93
91
  - **Trace ID Management**: Get, set, and generate OpenTelemetry compatible trace IDs for distributed tracing across API calls
@@ -146,6 +144,7 @@ Non-blocking debugging tools that capture snapshots without pausing execution. I
146
144
  - Configurable limits (max snapshots, call stack depth, async segments)
147
145
  - Sequence numbers for efficient snapshot polling
148
146
  - OpenTelemetry trace context in Node snapshots (traceId, spanId when process uses @opentelemetry/api)
147
+ - **Snapshot capture**: 1 call stack frame with scopes (both platforms) to keep payloads small. **Output trimming** for `get-probe-snapshots`: default scopes = `local` only (both Browser and Node), 20 variables/scope. Override via `maxCallStackDepth`, `includeScopes`, `maxVariablesPerScope`.
149
148
 
150
149
  ## Prerequisites
151
150
 
@@ -159,6 +158,39 @@ like VS Code, Claude, Cursor, Windsurf, GitHub Copilot via the `browser-devtools
159
158
 
160
159
  No manual installation required! The server can be run directly using `npx`, which automatically downloads and runs the package.
161
160
 
161
+ ---
162
+
163
+ ### 📦 Playwright browser binaries (required for browser platform)
164
+
165
+ > **The browser platform needs Playwright browser binaries (e.g. Chromium) to control the browser.**
166
+ > If you use **npx** to run the server, browsers are not downloaded at install time — you must install them once (see below). With a normal `npm install`, Playwright’s own packages may install Chromium automatically.
167
+
168
+ | Goal | What to do |
169
+ |------|------------|
170
+ | **Install at first run (npx)** | Set env before running: `BROWSER_DEVTOOLS_INSTALL_CHROMIUM=true npx -y browser-devtools-mcp` |
171
+ | **Install manually anytime** | Run: `npx playwright install chromium` (or `firefox`, `webkit`) |
172
+ | **Skip download (CI / system browser)** | Set: `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1` |
173
+
174
+ **1. Opt-in at install time** — set env vars before `npm install` or `npx` so the postinstall script downloads the chosen browsers:
175
+ - **Chromium** (chromium + chromium-headless-shell + ffmpeg):
176
+ ```bash
177
+ BROWSER_DEVTOOLS_INSTALL_CHROMIUM=true npx -y browser-devtools-mcp
178
+ ```
179
+ - **Firefox:** `BROWSER_DEVTOOLS_INSTALL_FIREFOX=true`
180
+ - **WebKit:** `BROWSER_DEVTOOLS_INSTALL_WEBKIT=true`
181
+ Combine as needed, e.g. `BROWSER_DEVTOOLS_INSTALL_CHROMIUM=true BROWSER_DEVTOOLS_INSTALL_FIREFOX=true npx -y browser-devtools-mcp`.
182
+
183
+ **2. Install via Playwright CLI** (same global cache):
184
+ ```bash
185
+ npx playwright install chromium # or firefox, webkit
186
+ ```
187
+ On Linux, include system dependencies:
188
+ ```bash
189
+ npx playwright install --with-deps chromium
190
+ ```
191
+
192
+ ---
193
+
162
194
  ### CLI Arguments
163
195
 
164
196
  Browser DevTools MCP server supports the following CLI arguments for configuration:
@@ -200,6 +232,9 @@ Add the following configuration into the `claude_desktop_config.json` file.
200
232
  See the [Claude Desktop MCP docs](https://modelcontextprotocol.io/docs/develop/connect-local-servers) for more info.
201
233
 
202
234
  **Browser platform (default):**
235
+
236
+ > **→ Browser platform requires Playwright browser binaries.** See [📦 Playwright browser binaries](#-playwright-browser-binaries-required-for-browser-platform) for one-time install (env vars or `npx playwright install chromium`).
237
+
203
238
  ```json
204
239
  {
205
240
  "mcpServers": {
@@ -241,6 +276,8 @@ Then, go to `Settings` > `Connectors` > `Add Custom Connector` in Claude Desktop
241
276
  Run the following command.
242
277
  See [Claude Code MCP docs](https://docs.anthropic.com/en/docs/claude-code/mcp) for more info.
243
278
 
279
+ > **→ Browser platform requires Playwright browser binaries.** See [📦 Playwright browser binaries](#-playwright-browser-binaries-required-for-browser-platform) for one-time install (env vars or `npx playwright install chromium`).
280
+
244
281
  #### Local Server
245
282
  ```bash
246
283
  claude mcp add browser-devtools -- npx -y browser-devtools-mcp
@@ -267,6 +304,8 @@ Replace `<SERVER_URL>` with your server URL (e.g., `http://localhost:3000/mcp` i
267
304
  Add the following configuration into the `~/.cursor/mcp.json` file (or `.cursor/mcp.json` in your project folder).
268
305
  See the [Cursor MCP docs](https://docs.cursor.com/context/model-context-protocol) for more info.
269
306
 
307
+ > **→ Browser platform requires Playwright browser binaries.** See [📦 Playwright browser binaries](#-playwright-browser-binaries-required-for-browser-platform) for one-time install (env vars or `npx playwright install chromium`).
308
+
270
309
  #### Local Server
271
310
  ```json
272
311
  {
@@ -306,6 +345,8 @@ Replace `<SERVER_URL>` with your server URL (e.g., `http://localhost:3000/mcp` i
306
345
  Add the following configuration into the `.vscode/mcp.json` file.
307
346
  See the [VS Code MCP docs](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) for more info.
308
347
 
348
+ > **→ Browser platform requires Playwright browser binaries.** See [📦 Playwright browser binaries](#-playwright-browser-binaries-required-for-browser-platform) for one-time install (env vars or `npx playwright install chromium`).
349
+
309
350
  #### Local Server
310
351
  ```json
311
352
  {
@@ -351,6 +392,8 @@ Replace `<SERVER_URL>` with your server URL (e.g., `http://localhost:3000/mcp` i
351
392
  Add the following configuration into the `~/.codeium/windsurf/mcp_config.json` file.
352
393
  See the [Windsurf MCP docs](https://docs.windsurf.com/windsurf/cascade/mcp) for more info.
353
394
 
395
+ > **→ Browser platform requires Playwright browser binaries.** See [📦 Playwright browser binaries](#-playwright-browser-binaries-required-for-browser-platform) for one-time install (env vars or `npx playwright install chromium`).
396
+
354
397
  #### Local Server
355
398
  ```json
356
399
  {
@@ -391,6 +434,8 @@ Add the following configuration to the `mcpServers` section of your Copilot Codi
391
434
  `Repository` > `Settings` > `Copilot` > `Coding agent` > `MCP configuration`.
392
435
  See the [Copilot Coding Agent MCP docs](https://docs.github.com/en/enterprise-cloud@latest/copilot/how-tos/agents/copilot-coding-agent/extending-copilot-coding-agent-with-mcp) for more info.
393
436
 
437
+ > **→ Browser platform requires Playwright browser binaries.** See [📦 Playwright browser binaries](#-playwright-browser-binaries-required-for-browser-platform) for one-time install (env vars or `npx playwright install chromium`).
438
+
394
439
  #### Local Server
395
440
  ```json
396
441
  {
@@ -539,6 +584,8 @@ browser-devtools-cli --help
539
584
  node-devtools-cli --help
540
585
  ```
541
586
 
587
+ To install Playwright browser binaries (required for the browser CLI when not using a system browser), run `npx playwright install chromium` (see [Playwright browser binaries](#playwright-browser-binaries)).
588
+
542
589
  ### Global Options
543
590
 
544
591
  | Option | Description | Default |
@@ -576,8 +623,6 @@ node-devtools-cli
576
623
  ├── completion # Generate shell completion scripts
577
624
  ├── interactive (repl) # Start interactive REPL mode
578
625
  ├── update # Check for updates
579
- ├── run # Script execution commands
580
- │ └── js-in-node # Run JavaScript in the connected Node process
581
626
  └── debug # Debug commands
582
627
  ├── connect # Connect to Node.js process (pid, processName, inspectorPort, containerId, etc.)
583
628
  ├── disconnect # Disconnect from current process
@@ -648,15 +693,14 @@ browser-devtools-cli
648
693
  │ ├── get-component-for-element
649
694
  │ └── get-element-for-component
650
695
  ├── run # Script execution commands
651
- ├── js-in-browser # Run JS in browser
652
- │ └── js-in-sandbox # Run JS in sandbox
696
+ └── execute # Batch-execute tool calls via JS (VM has page on browser)
653
697
  ├── stub # HTTP stubbing commands
654
698
  │ ├── mock-http-response # Mock HTTP responses
655
699
  │ ├── intercept-http-request # Intercept requests
656
700
  │ ├── list # List stubs
657
701
  │ └── clear # Clear stubs
658
702
  ├── sync # Synchronization commands
659
- │ └── wait-for-network-idle # Wait for network idle
703
+ │ └── wait-for-network-idle # Wait for network idle (configurable idle time / threshold)
660
704
  ├── debug # Non-blocking debugging commands
661
705
  │ ├── put-tracepoint # Set a tracepoint (captures call stack)
662
706
  │ ├── remove-probe # Remove a tracepoint, logpoint, or watch by ID (type + id)
@@ -665,7 +709,7 @@ browser-devtools-cli
665
709
  │ ├── put-exceptionpoint # Enable exception catching
666
710
  │ ├── add-watch # Add a watch expression
667
711
  │ ├── 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)
712
+ │ ├── get-probe-snapshots # Get tracepoint/logpoint/exceptionpoint snapshots (1 frame captured; default scopes: local only; 20 vars/scope; override: maxCallStackDepth, includeScopes, maxVariablesPerScope)
669
713
  │ ├── clear-probe-snapshots # Clear tracepoint/logpoint/exceptionpoint snapshots (optional types; omit to clear all)
670
714
  │ └── status # Get debugging status
671
715
  └── figma # Figma integration commands
@@ -1071,6 +1115,7 @@ The CLI uses a daemon server architecture for efficient browser management:
1071
1115
  2. **Shared browser**: Multiple CLI invocations share the same browser instance
1072
1116
  3. **Session isolation**: Each session ID gets its own isolated browser context
1073
1117
  4. **Auto-cleanup**: Idle sessions are automatically cleaned up after inactivity
1118
+ 5. **Full tool set**: The daemon exposes the same tools as MCP (including **execute** for batch execution). Tools can be invoked via CLI subcommands (e.g. `navigation go-to`, `run execute`) or via the daemon HTTP API (POST `/call` with `toolName` and `toolInput`).
1074
1119
 
1075
1120
  The daemon listens on port 2020 by default. Use `--port` to specify a different port.
1076
1121
 
@@ -1102,10 +1147,14 @@ The server can be configured using environment variables. Configuration is divid
1102
1147
  | `TOOL_OUTPUT_SCHEMA_DISABLE` | When true, omit tool output schema from MCP tool registration (can reduce token usage for some clients) | `false` |
1103
1148
  | `AVAILABLE_TOOL_DOMAINS` | Optional comma-separated list of tool domains to enable. When set, only tools from these domains are registered; unset means all tools. **Browser domains:** `a11y`, `content`, `debug`, `figma`, `interaction`, `navigation`, `o11y`, `react`, `run`, `stub`, `sync`. **Node domains:** `debug`, `run`. Example: `AVAILABLE_TOOL_DOMAINS=navigation,interaction,a11y` | (all tools) |
1104
1149
 
1150
+ Tool inputs are validated with a strict schema; unknown or misspelled argument keys (e.g. `port` instead of `inspectorPort`) cause a validation error.
1151
+
1105
1152
  ### Node Platform Configuration
1106
1153
 
1107
1154
  | Variable | Description | Default |
1108
1155
  |----------|-------------|---------|
1156
+ | `NODE_SERVER_INSTRUCTIONS_ENABLE` | When true, include server instructions in MCP server info | `true` |
1157
+ | `NODE_POLICY_DEBUGGING_ENABLE` | When true, include NODE_DEBUGGING_POLICY in server policies | `false` |
1109
1158
  | `NODE_CONSOLE_MESSAGES_BUFFER_SIZE` | Maximum console messages to buffer from Node.js process | `1000` |
1110
1159
  | `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` |
1111
1160
  | `PLATFORM` | Platform to use: `browser` or `node` | `browser` |
@@ -1114,6 +1163,8 @@ The server can be configured using environment variables. Configuration is divid
1114
1163
 
1115
1164
  | Variable | Description | Default |
1116
1165
  |----------|-------------|---------|
1166
+ | `BROWSER_SERVER_INSTRUCTIONS_ENABLE` | When true, include server instructions in MCP server info | `true` |
1167
+ | `BROWSER_POLICY_UI_DEBUGGING_ENABLE` | When true, include UI_DEBUGGING_POLICY in server policies | `false` |
1117
1168
  | `CONSOLE_MESSAGES_BUFFER_SIZE` | Maximum console messages to buffer | `1000` |
1118
1169
  | `HTTP_REQUESTS_BUFFER_SIZE` | Maximum HTTP requests to buffer | `1000` |
1119
1170
  | `BROWSER_HEADLESS_ENABLE` | Run browser in headless mode | `true` |
@@ -1289,11 +1340,13 @@ Once disabled, no data is sent and no network requests are made to PostHog.
1289
1340
  ### Interaction Tools
1290
1341
 
1291
1342
  <details>
1292
- <summary><code>interaction_click</code> - Clicks an element on the page.</summary>
1343
+ <summary><code>interaction_click</code> - Clicks an element. Set waitForNavigation: true when click opens a new page.</summary>
1293
1344
 
1294
1345
  **Parameters:**
1295
1346
  - `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
1296
1347
  - `timeoutMs` (number, optional): Time to wait for the element in ms (default: 10000)
1348
+ - `waitForNavigation` (boolean, optional): Wait for navigation triggered by click in parallel (race-free), then for network idle. Use when click opens a new page. Default: false
1349
+ - `waitForTimeoutMs` (number, optional): Timeout for navigation and network idle wait in ms. Only when waitForNavigation is true. Default: 30000
1297
1350
  </details>
1298
1351
 
1299
1352
  <details>
@@ -1466,92 +1519,56 @@ Once disabled, no data is sent and no network requests are made to PostHog.
1466
1519
  ### Run Tools
1467
1520
 
1468
1521
  <details>
1469
- <summary><code>run_js-in-browser</code> - Runs custom JavaScript INSIDE the active browser page using Playwright's "page.evaluate()".</summary>
1470
-
1471
- **Parameters:**
1472
- - `script` (string, required): JavaScript code to execute
1473
-
1474
- **Returns:**
1475
- - `result` (any): Result of the evaluation. This value can be of any type, including primitives, arrays, or objects. It represents the direct return value of the JavaScript expression executed in the page context.
1476
-
1477
- **Notes:**
1478
- - The code executes in the PAGE CONTEXT (real browser environment):
1479
- - Has access to window, document, DOM, Web APIs
1480
- - Can read/modify the page state
1481
- - Runs with the same permissions as the loaded web page
1482
- - The code runs in an isolated execution context, but within the page
1483
- - No direct access to Node.js APIs
1484
- - Return value must be serializable
1485
-
1486
- **Typical use cases:**
1487
- - Inspect or mutate DOM state
1488
- - Read client-side variables or framework internals
1489
- - Trigger browser-side logic
1490
- - Extract computed values directly from the page
1491
- </details>
1492
-
1493
- <details>
1494
- <summary><code>run_js-in-sandbox</code> - Runs custom JavaScript inside a Node.js VM sandbox on the MCP server (NOT in the browser).</summary>
1522
+ <summary><code>execute</code> - Batch-execute multiple tool calls in a single request via custom JavaScript. Reduces round-trips and token usage.</summary>
1495
1523
 
1496
1524
  **Parameters:**
1497
- - `code` (string, required): JavaScript code to run on the MCP server in a VM sandbox. The code is wrapped in an async IIFE, so `await` is allowed. Use `return ...` to return a value
1498
- - `timeoutMs` (number, optional): Max VM CPU time for synchronous execution in milliseconds (default: 5000, max: 30000)
1525
+ - `code` (string, required): JavaScript code to run in a sandboxed VM. Wrapped in an async IIFE, so `await` and `return` work directly. Use `callTool(name, input, returnOutput?)` to invoke any registered MCP tool.
1526
+ - `timeoutMs` (number, optional): Wall-clock timeout for the entire execution in ms, including all awaited tool calls and sleep (default: 30000, max: 120000)
1499
1527
 
1500
1528
  **Returns:**
1501
- - `result` (any): Return value of the sandboxed code (best-effort JSON-safe). If user returns undefined but logs exist, returns `{ logs }`. If error occurs, returns `{ error, logs }`
1502
-
1503
- **Available bindings:**
1504
- - `page`: Playwright Page (main interaction surface)
1505
- - `console`: captured logs (log/warn/error)
1506
- - `sleep(ms)`: async helper
1507
-
1508
- **Safe built-ins:**
1509
- - Math, JSON, Number, String, Boolean, Array, Object, Date, RegExp
1510
- - isFinite, isNaN, parseInt, parseFloat
1511
- - URL, URLSearchParams
1512
- - TextEncoder, TextDecoder
1513
- - structuredClone
1514
- - crypto.randomUUID()
1515
- - AbortController
1516
- - setTimeout / clearTimeout
1529
+ - `toolOutputs` (array): Tool outputs where `callTool` was called with `returnOutput=true`. Each entry has `name` (tool name) and `output` (tool result).
1530
+ - `logs` (array): Captured `console.log/warn/error` calls. Each entry has `level` and `message`.
1531
+ - `result` (any, optional): Return value of the code (JSON-safe). Undefined on error or when nothing is returned.
1532
+ - `error` (string, optional): Error message with stack trace on failure. Partial `toolOutputs`/`logs` are still returned.
1533
+ - `failedTool` (object, optional): Present when a `callTool` invocation caused the error. Contains `name` (tool that failed) and `error` (error message).
1534
+
1535
+ **Bindings:**
1536
+ - `await callTool(name, input, returnOutput?)`: Invoke any registered MCP tool. Always use with `await`. When `returnOutput=true`, the output is included in the response `toolOutputs` array. Throws on failure — execution stops at the first error.
1537
+ - `console.log/warn/error`: Captured in the response `logs` array (max 500 entries).
1538
+ - `sleep(ms)`: Async delay helper.
1539
+
1540
+ **Session execution context (injected into VM):**
1541
+ - **Browser platform:** `page` (Playwright Page) is available. Use the Playwright API (e.g. `page.locator()`, `page.goto()`) or `await page.evaluate(() => { ... })` / `page.evaluateHandle()` to run script in the browser.
1542
+ - **Node platform:** No extra bindings (empty object).
1543
+
1544
+ **Built-ins (isolated via VM context):**
1545
+ - Math, JSON, Date, RegExp, Number, String, Boolean, Array, Object, Promise, Map, Set, WeakMap, WeakSet, Symbol, Proxy, Reflect
1546
+ - URL, URLSearchParams, TextEncoder, TextDecoder, structuredClone
1547
+ - crypto.randomUUID(), AbortController, setTimeout, clearTimeout
1517
1548
 
1518
1549
  **NOT available:**
1519
- - require, process, fs, Buffer
1520
- - globalThis
1521
-
1522
- **Notes:**
1523
- - This runs on the MCP SERVER (not in the browser)
1524
- - This is NOT a security boundary. Intended for trusted automation logic
1525
- - The timeoutMs parameter limits synchronous execution time, but does not automatically time out awaited Promises
1526
- </details>
1527
-
1528
- <details>
1529
- <summary><code>run_js-in-node</code> - Runs custom JavaScript INSIDE the connected Node.js process (Node platform only).</summary>
1530
-
1531
- **Parameters:**
1532
- - `script` (string, required): JavaScript code to execute in the Node process. Use `return` to pass a value back (e.g. `return process.memoryUsage();`). Async/await is supported (e.g. `return await fetch(url).then(r => r.json());`).
1533
- - `timeoutMs` (number, optional): Max evaluation time in milliseconds (default: 5000, max: 30000)
1534
-
1535
- **Returns:**
1536
- - `result` (any): The result of the evaluation. Can be primitives, arrays, or objects. Must be serializable (JSON-compatible).
1550
+ - require, import, process, fs, Buffer, fetch
1551
+
1552
+ **Limits:**
1553
+ - Max 50 `callTool` invocations per execution
1554
+ - Max 500 console log entries
1555
+ - Wall-clock timeout covers all async work (tool calls, sleep, etc.)
1556
+ - Separate sync CPU guard (10s) prevents tight infinite loops
1557
+
1558
+ **Example — fill form, submit (with navigation wait), then snapshot and screenshot:**
1559
+ ```js
1560
+ await callTool('interaction_fill', { selector: '#email', value: 'user@test.com' });
1561
+ await callTool('interaction_fill', { selector: '#password', value: 'secret123' });
1562
+ await callTool('interaction_click', { selector: 'button[type="submit"]', waitForNavigation: true });
1563
+ await callTool('a11y_take-aria-snapshot', {}, true);
1564
+ await callTool('content_take-screenshot', {}, true);
1565
+ ```
1537
1566
 
1538
1567
  **Notes:**
1539
- - Requires `debug_connect` first—must be connected to a Node.js process
1540
- - **You must use `return` to get output** (e.g. `return 2+2;`, `return process.memoryUsage();`). The script runs inside an async function.
1541
- - **Async code supported:** use `await` and return a Promise; the tool waits for it and returns the resolved value.
1542
- - Executes in the NODE PROCESS CONTEXT (real Node.js environment):
1543
- - Has access to process, require, global, and all loaded modules
1544
- - Can read/modify process state
1545
- - Full Node.js APIs (fs, http, etc.)
1546
- - Execution blocks the Node event loop until the script (and any returned Promise) completes
1547
- - Long-running scripts will block the process; use short scripts
1548
- - Return value must be serializable
1549
-
1550
- **Typical use cases:**
1551
- - Inspect process state: `return process.memoryUsage();`, `return process.uptime();`
1552
- - Call loaded modules: `return require('os').loadavg();`
1553
- - Read globals or cached data
1554
- - Async: `return await someAsyncCall();`
1568
+ - This is the **recommended** way to perform multi-step interactions. Instead of separate tool calls for each fill/click/select, batch them together for fewer round-trips and lower token usage.
1569
+ - Execution runs in an isolated VM context prototype modifications inside the sandbox do not leak to the host process.
1570
+ - All timers created via `setTimeout` are automatically cleaned up when execution ends, preventing dangling callbacks.
1571
+ - Image buffers are stripped from `toolOutputs` to keep the response compact; use `screenshotFilePath` to access images.
1555
1572
  </details>
1556
1573
 
1557
1574
  ### Observability (O11Y) Tools
@@ -1586,9 +1603,12 @@ Once disabled, no data is sent and no network requests are made to PostHog.
1586
1603
  - `limit` (object, optional): Limit results (default: last 100). Omit or set `count: 0` for no limit.
1587
1604
  - `count` (number, default 100): Maximum number of requests; 0 = no limit
1588
1605
  - `from` (enum): "start" or "end" (default: "end")
1606
+ - `includeRequestHeaders` (boolean, optional): Include request headers in each item (default: false)
1607
+ - `includeResponseHeaders` (boolean, optional): Include response headers in each item (default: false)
1608
+ - `includeResponseBody` (boolean, optional): Include response body in each item (default: false)
1589
1609
 
1590
1610
  **Returns:**
1591
- - `requests` (array): Array of HTTP requests with URL, method, headers, body, response, timing, and metadata
1611
+ - `requests` (array): Array of HTTP requests with URL, method, resourceType, timing, and metadata. Request `headers`, response `headers`, and response `body` are present only when the corresponding `include*` parameter is true.
1592
1612
  </details>
1593
1613
 
1594
1614
  <details>
@@ -2168,7 +2188,7 @@ A dedicated Claude Code plugin is available with slash commands, skills, and age
2168
2188
  **Skills (6 skills):**
2169
2189
  - `browser-testing` - General browser test capabilities
2170
2190
  - `web-debugging` - Console, network, JS debugging
2171
- - `node-debugging` - Node.js backend debugging (tracepoints, logpoints, run_js-in-node)
2191
+ - `node-debugging` - Node.js backend debugging (tracepoints, logpoints)
2172
2192
  - `performance-audit` - Web Vitals and performance analysis
2173
2193
  - `visual-testing` - Visual testing and responsive design
2174
2194
  - `observability` - Distributed tracing and monitoring
package/SECURITY.md CHANGED
@@ -36,10 +36,7 @@ If you discover a security vulnerability in Browser DevTools MCP, please report
36
36
 
37
37
  Browser DevTools MCP provides powerful browser automation capabilities. Users should be aware of:
38
38
 
39
- 1. **Code Execution**: The `run_js-in-browser` and `run_js-in-sandbox` tools execute arbitrary JavaScript code
40
- - `run_js-in-browser`: Executes in the page context with full DOM access
41
- - `run_js-in-sandbox`: Executes in a Node.js VM sandbox (NOT a security boundary)
42
- - Only use with trusted code inputs
39
+ 1. **Code Execution**: The `execute` tool runs arbitrary JavaScript in a VM; on the browser platform it receives `page` (Playwright Page) so code can also run script in the browser via `page.evaluate()`. The VM is not a security boundary — only use with trusted code inputs.
43
40
 
44
41
  2. **Network Access**: The browser can make requests to any URL
45
42
  - HTTP requests from the browser inherit the page's cookies and session
@@ -106,11 +103,10 @@ When using HTTP transport (`--transport=streamable-http`):
106
103
 
107
104
  ### Sandbox Isolation
108
105
 
109
- The `run_js-in-sandbox` tool provides limited isolation:
110
- - Runs in Node.js VM context
111
- - No access to `require`, `process`, `fs`, `Buffer`
112
- - Limited built-in APIs available
113
- - **Note**: This is NOT a security boundary - treat all input as trusted
106
+ The `execute` tool runs in a Node.js VM context with limited isolation:
107
+ - No access to `require`, `process`, `fs`, `Buffer`, `fetch`
108
+ - Limited built-in APIs available; on browser platform `page` (Playwright Page) is injected
109
+ - **Note**: This is NOT a security boundary — treat all input as trusted
114
110
 
115
111
  ### OpenTelemetry Security
116
112
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{isToolEnabled,platformInfo}from"../core-BD5K3BG4.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-PF26XSPV.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(`
2
+ import{isToolEnabled,platformInfo}from"../core-FKJAPNIK.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-3M2NFQVF.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(`