browser-devtools-mcp 0.3.2 → 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,18 +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 `snapshotInteractiveOnly` and `snapshotCursorInteractive` to control which elements get refs (same as `a11y_take-aria-snapshot`).
81
- - **Go Back**: Navigate backward in history. Same snapshot/refs behavior as Go To when `includeSnapshot` is true.
82
- - **Go Forward**: Navigate forward in history. Same snapshot/refs behavior as Go To when `includeSnapshot` is true.
83
- - **Reload**: Reload the current page. Same snapshot/refs behavior as Go To when `includeSnapshot` is true.
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.
84
82
 
85
83
  ### Run Tools
86
- - **JS in Browser**: Execute JavaScript code inside the active browser page (page context with access to window, document, DOM, and Web APIs)
87
- - **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"`.
88
85
 
89
86
  ### Observability (O11Y) Tools
90
87
  - **Console Messages**: Capture and filter browser console logs with advanced filtering (level, search, timestamp, sequence number)
91
- - **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)
92
89
  - **Web Vitals**: Collect Core Web Vitals (LCP, INP, CLS) and supporting metrics (TTFB, FCP) with ratings and recommendations based on Google's thresholds
93
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
94
91
  - **Trace ID Management**: Get, set, and generate OpenTelemetry compatible trace IDs for distributed tracing across API calls
@@ -147,6 +144,7 @@ Non-blocking debugging tools that capture snapshots without pausing execution. I
147
144
  - Configurable limits (max snapshots, call stack depth, async segments)
148
145
  - Sequence numbers for efficient snapshot polling
149
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`.
150
148
 
151
149
  ## Prerequisites
152
150
 
@@ -160,6 +158,39 @@ like VS Code, Claude, Cursor, Windsurf, GitHub Copilot via the `browser-devtools
160
158
 
161
159
  No manual installation required! The server can be run directly using `npx`, which automatically downloads and runs the package.
162
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
+
163
194
  ### CLI Arguments
164
195
 
165
196
  Browser DevTools MCP server supports the following CLI arguments for configuration:
@@ -201,6 +232,9 @@ Add the following configuration into the `claude_desktop_config.json` file.
201
232
  See the [Claude Desktop MCP docs](https://modelcontextprotocol.io/docs/develop/connect-local-servers) for more info.
202
233
 
203
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
+
204
238
  ```json
205
239
  {
206
240
  "mcpServers": {
@@ -242,6 +276,8 @@ Then, go to `Settings` > `Connectors` > `Add Custom Connector` in Claude Desktop
242
276
  Run the following command.
243
277
  See [Claude Code MCP docs](https://docs.anthropic.com/en/docs/claude-code/mcp) for more info.
244
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
+
245
281
  #### Local Server
246
282
  ```bash
247
283
  claude mcp add browser-devtools -- npx -y browser-devtools-mcp
@@ -268,6 +304,8 @@ Replace `<SERVER_URL>` with your server URL (e.g., `http://localhost:3000/mcp` i
268
304
  Add the following configuration into the `~/.cursor/mcp.json` file (or `.cursor/mcp.json` in your project folder).
269
305
  See the [Cursor MCP docs](https://docs.cursor.com/context/model-context-protocol) for more info.
270
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
+
271
309
  #### Local Server
272
310
  ```json
273
311
  {
@@ -307,6 +345,8 @@ Replace `<SERVER_URL>` with your server URL (e.g., `http://localhost:3000/mcp` i
307
345
  Add the following configuration into the `.vscode/mcp.json` file.
308
346
  See the [VS Code MCP docs](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) for more info.
309
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
+
310
350
  #### Local Server
311
351
  ```json
312
352
  {
@@ -352,6 +392,8 @@ Replace `<SERVER_URL>` with your server URL (e.g., `http://localhost:3000/mcp` i
352
392
  Add the following configuration into the `~/.codeium/windsurf/mcp_config.json` file.
353
393
  See the [Windsurf MCP docs](https://docs.windsurf.com/windsurf/cascade/mcp) for more info.
354
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
+
355
397
  #### Local Server
356
398
  ```json
357
399
  {
@@ -392,6 +434,8 @@ Add the following configuration to the `mcpServers` section of your Copilot Codi
392
434
  `Repository` > `Settings` > `Copilot` > `Coding agent` > `MCP configuration`.
393
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.
394
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
+
395
439
  #### Local Server
396
440
  ```json
397
441
  {
@@ -540,6 +584,8 @@ browser-devtools-cli --help
540
584
  node-devtools-cli --help
541
585
  ```
542
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
+
543
589
  ### Global Options
544
590
 
545
591
  | Option | Description | Default |
@@ -577,8 +623,6 @@ node-devtools-cli
577
623
  ├── completion # Generate shell completion scripts
578
624
  ├── interactive (repl) # Start interactive REPL mode
579
625
  ├── update # Check for updates
580
- ├── run # Script execution commands
581
- │ └── js-in-node # Run JavaScript in the connected Node process
582
626
  └── debug # Debug commands
583
627
  ├── connect # Connect to Node.js process (pid, processName, inspectorPort, containerId, etc.)
584
628
  ├── disconnect # Disconnect from current process
@@ -591,7 +635,7 @@ node-devtools-cli
591
635
 
592
636
  ### Browser CLI Commands
593
637
 
594
- `browser-devtools-cli` organizes tools into domain-based subcommands:
638
+ `browser-devtools-cli` organizes tools into domain-based subcommands. **Object parameters** (e.g. `screenshotOptions`, `snapshotOptions`) must be passed as a JSON string: `--screenshot-options '{"outputPath":"/tmp","name":"myshot"}'` or `--snapshot-options '{"interactiveOnly":false}'`. Run `browser-devtools-cli navigation go-to --help` (or the relevant subcommand) to see all options.
595
639
 
596
640
  ```
597
641
  browser-devtools-cli
@@ -649,15 +693,14 @@ browser-devtools-cli
649
693
  │ ├── get-component-for-element
650
694
  │ └── get-element-for-component
651
695
  ├── run # Script execution commands
652
- ├── js-in-browser # Run JS in browser
653
- │ └── js-in-sandbox # Run JS in sandbox
696
+ └── execute # Batch-execute tool calls via JS (VM has page on browser)
654
697
  ├── stub # HTTP stubbing commands
655
698
  │ ├── mock-http-response # Mock HTTP responses
656
699
  │ ├── intercept-http-request # Intercept requests
657
700
  │ ├── list # List stubs
658
701
  │ └── clear # Clear stubs
659
702
  ├── sync # Synchronization commands
660
- │ └── wait-for-network-idle # Wait for network idle
703
+ │ └── wait-for-network-idle # Wait for network idle (configurable idle time / threshold)
661
704
  ├── debug # Non-blocking debugging commands
662
705
  │ ├── put-tracepoint # Set a tracepoint (captures call stack)
663
706
  │ ├── remove-probe # Remove a tracepoint, logpoint, or watch by ID (type + id)
@@ -666,7 +709,7 @@ browser-devtools-cli
666
709
  │ ├── put-exceptionpoint # Enable exception catching
667
710
  │ ├── add-watch # Add a watch expression
668
711
  │ ├── clear-probes # Clear tracepoints, logpoints, and/or watches (optional types; omit to clear all)
669
- │ ├── 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)
670
713
  │ ├── clear-probe-snapshots # Clear tracepoint/logpoint/exceptionpoint snapshots (optional types; omit to clear all)
671
714
  │ └── status # Get debugging status
672
715
  └── figma # Figma integration commands
@@ -1072,6 +1115,7 @@ The CLI uses a daemon server architecture for efficient browser management:
1072
1115
  2. **Shared browser**: Multiple CLI invocations share the same browser instance
1073
1116
  3. **Session isolation**: Each session ID gets its own isolated browser context
1074
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`).
1075
1119
 
1076
1120
  The daemon listens on port 2020 by default. Use `--port` to specify a different port.
1077
1121
 
@@ -1103,10 +1147,14 @@ The server can be configured using environment variables. Configuration is divid
1103
1147
  | `TOOL_OUTPUT_SCHEMA_DISABLE` | When true, omit tool output schema from MCP tool registration (can reduce token usage for some clients) | `false` |
1104
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) |
1105
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
+
1106
1152
  ### Node Platform Configuration
1107
1153
 
1108
1154
  | Variable | Description | Default |
1109
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` |
1110
1158
  | `NODE_CONSOLE_MESSAGES_BUFFER_SIZE` | Maximum console messages to buffer from Node.js process | `1000` |
1111
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` |
1112
1160
  | `PLATFORM` | Platform to use: `browser` or `node` | `browser` |
@@ -1115,6 +1163,8 @@ The server can be configured using environment variables. Configuration is divid
1115
1163
 
1116
1164
  | Variable | Description | Default |
1117
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` |
1118
1168
  | `CONSOLE_MESSAGES_BUFFER_SIZE` | Maximum console messages to buffer | `1000` |
1119
1169
  | `HTTP_REQUESTS_BUFFER_SIZE` | Maximum HTTP requests to buffer | `1000` |
1120
1170
  | `BROWSER_HEADLESS_ENABLE` | Run browser in headless mode | `true` |
@@ -1290,11 +1340,13 @@ Once disabled, no data is sent and no network requests are made to PostHog.
1290
1340
  ### Interaction Tools
1291
1341
 
1292
1342
  <details>
1293
- <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>
1294
1344
 
1295
1345
  **Parameters:**
1296
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
1297
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
1298
1350
  </details>
1299
1351
 
1300
1352
  <details>
@@ -1430,24 +1482,24 @@ Once disabled, no data is sent and no network requests are made to PostHog.
1430
1482
  - `timeout` (number, optional): Maximum operation time in milliseconds (default: 0 - no timeout)
1431
1483
  - `waitUntil` (enum, optional): When to consider navigation succeeded - "load", "domcontentloaded", "networkidle", or "commit" (default: "load")
1432
1484
  - `includeSnapshot` (boolean, optional): When true (default), take an ARIA snapshot with refs after navigation and include `output` and `refs` in the response; when false, only url/status/ok are returned.
1433
- - `snapshotInteractiveOnly` (boolean, optional): When includeSnapshot is true, if set, only interactive elements get refs (same as a11y_take-aria-snapshot).
1434
- - `snapshotCursorInteractive` (boolean, optional): When includeSnapshot is true, if set, also include cursor-interactive elements in refs (same as a11y_take-aria-snapshot).
1485
+ - `snapshotOptions` (object, optional): When includeSnapshot is true, options for the snapshot. **interactiveOnly** (boolean, default false): only interactive elements get refs; **cursorInteractive** (boolean, default false): include cursor-interactive elements (same as a11y_take-aria-snapshot).
1486
+ - `includeScreenshot` (boolean, optional): When true, take a screenshot after navigation; saved to disk, path returned in `screenshotFilePath`. Default false.
1487
+ - `screenshotOptions` (object, optional): When includeScreenshot is true. **outputPath** (string, default: OS temp dir), **name** (string, default: "screenshot"), **fullPage** (boolean, default true), **type** ("png" | "jpeg", default "png"), **annotate** (boolean, default true), **includeBase64** (boolean, default false): include image in response as separate MCP content part — use only when file cannot be read from path (e.g. remote, container).
1435
1488
 
1436
- **Returns:**
1437
- - `url` (string): Final URL after navigation
1438
- - `status` (number): HTTP status code
1439
- - `statusText` (string): HTTP status text
1440
- - `ok` (boolean): Whether navigation was successful (2xx status)
1489
+ **Returns:** (order: url, status, statusText, ok, screenshotFilePath, output, refs, image)
1490
+ - `url`, `status`, `statusText`, `ok`: Navigation result.
1491
+ - `screenshotFilePath` (string, optional): When includeScreenshot is true, full path of the saved screenshot file.
1441
1492
  - `output` (string, optional): When includeSnapshot is true, ARIA snapshot text (page URL, title, YAML tree).
1442
1493
  - `refs` (record, optional): When includeSnapshot is true, map of ref id (e1, e2, ...) to role/name/selector; use in interaction tools (e.g. click @e1).
1494
+ - `image` (object, optional): When includeScreenshot and screenshotOptions.includeBase64 are true, image data (data, mimeType) sent as separate image content part by MCP.
1443
1495
  </details>
1444
1496
 
1445
1497
  <details>
1446
1498
  <summary><code>navigation_go-back-or-forward</code> - Navigates back or forward in browser history.</summary>
1447
1499
 
1448
- **Parameters:** `direction` (required: `"back"` or `"forward"`), `timeout`, `waitUntil`, `includeSnapshot` (default true), `snapshotInteractiveOnly`, `snapshotCursorInteractive` — same semantics as navigation_go-to for snapshot/refs.
1500
+ **Parameters:** `direction` (required: `"back"` or `"forward"`), `timeout`, `waitUntil`, `includeSnapshot` (default true), `snapshotOptions` (object: interactiveOnly, cursorInteractive), `includeScreenshot` (boolean, default false), `screenshotOptions` (object: outputPath, name, fullPage, type, annotate, includeBase64) — same semantics as navigation_go-to.
1449
1501
 
1450
- **Returns:** `url`, `status`, `statusText`, `ok`; when includeSnapshot is true also `output` and `refs` (ARIA snapshot with refs).
1502
+ **Returns:** Same shape as navigation_go-to (url, status, statusText, ok, screenshotFilePath, output, refs, image).
1451
1503
  </details>
1452
1504
 
1453
1505
  <details>
@@ -1457,107 +1509,66 @@ Once disabled, no data is sent and no network requests are made to PostHog.
1457
1509
  - `timeout` (number, optional): Maximum operation time in milliseconds (default: 0 - no timeout)
1458
1510
  - `waitUntil` (enum, optional): When to consider navigation succeeded - "load", "domcontentloaded", "networkidle", or "commit" (default: "load")
1459
1511
  - `includeSnapshot` (boolean, optional): When true (default), take an ARIA snapshot with refs after reload and include `output` and `refs`; when false, only url/status/ok.
1460
- - `snapshotInteractiveOnly` (boolean, optional): When includeSnapshot is true, control which elements get refs (same as a11y_take-aria-snapshot).
1461
- - `snapshotCursorInteractive` (boolean, optional): When includeSnapshot is true, include cursor-interactive elements in refs (same as a11y_take-aria-snapshot).
1512
+ - `snapshotOptions` (object, optional): When includeSnapshot is true. **interactiveOnly** (boolean, default false), **cursorInteractive** (boolean, default false) — same as a11y_take-aria-snapshot.
1513
+ - `includeScreenshot` (boolean, optional): When true, take a screenshot after reload; saved to disk. Default false.
1514
+ - `screenshotOptions` (object, optional): When includeScreenshot is true; same shape as navigation_go-to (outputPath, name, fullPage, type, annotate, includeBase64).
1462
1515
 
1463
- **Returns:**
1464
- - `url` (string): Final URL after reload
1465
- - `status` (number): HTTP status code
1466
- - `statusText` (string): HTTP status text
1467
- - `ok` (boolean): Whether reload was successful (2xx status)
1468
- - `output` (string, optional): When includeSnapshot is true, ARIA snapshot text.
1469
- - `refs` (record, optional): When includeSnapshot is true, map of ref id to role/name/selector for use in interaction tools.
1516
+ **Returns:** Same shape as navigation_go-to (url, status, statusText, ok, screenshotFilePath, output, refs, image).
1470
1517
  </details>
1471
1518
 
1472
1519
  ### Run Tools
1473
1520
 
1474
1521
  <details>
1475
- <summary><code>run_js-in-browser</code> - Runs custom JavaScript INSIDE the active browser page using Playwright's "page.evaluate()".</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>
1476
1523
 
1477
1524
  **Parameters:**
1478
- - `script` (string, required): JavaScript code to execute
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)
1479
1527
 
1480
1528
  **Returns:**
1481
- - `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.
1482
-
1483
- **Notes:**
1484
- - The code executes in the PAGE CONTEXT (real browser environment):
1485
- - Has access to window, document, DOM, Web APIs
1486
- - Can read/modify the page state
1487
- - Runs with the same permissions as the loaded web page
1488
- - The code runs in an isolated execution context, but within the page
1489
- - No direct access to Node.js APIs
1490
- - Return value must be serializable
1491
-
1492
- **Typical use cases:**
1493
- - Inspect or mutate DOM state
1494
- - Read client-side variables or framework internals
1495
- - Trigger browser-side logic
1496
- - Extract computed values directly from the page
1497
- </details>
1498
-
1499
- <details>
1500
- <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>
1501
-
1502
- **Parameters:**
1503
- - `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
1504
- - `timeoutMs` (number, optional): Max VM CPU time for synchronous execution in milliseconds (default: 5000, max: 30000)
1505
-
1506
- **Returns:**
1507
- - `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 }`
1508
-
1509
- **Available bindings:**
1510
- - `page`: Playwright Page (main interaction surface)
1511
- - `console`: captured logs (log/warn/error)
1512
- - `sleep(ms)`: async helper
1513
-
1514
- **Safe built-ins:**
1515
- - Math, JSON, Number, String, Boolean, Array, Object, Date, RegExp
1516
- - isFinite, isNaN, parseInt, parseFloat
1517
- - URL, URLSearchParams
1518
- - TextEncoder, TextDecoder
1519
- - structuredClone
1520
- - crypto.randomUUID()
1521
- - AbortController
1522
- - 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
1523
1548
 
1524
1549
  **NOT available:**
1525
- - require, process, fs, Buffer
1526
- - globalThis
1527
-
1528
- **Notes:**
1529
- - This runs on the MCP SERVER (not in the browser)
1530
- - This is NOT a security boundary. Intended for trusted automation logic
1531
- - The timeoutMs parameter limits synchronous execution time, but does not automatically time out awaited Promises
1532
- </details>
1533
-
1534
- <details>
1535
- <summary><code>run_js-in-node</code> - Runs custom JavaScript INSIDE the connected Node.js process (Node platform only).</summary>
1536
-
1537
- **Parameters:**
1538
- - `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());`).
1539
- - `timeoutMs` (number, optional): Max evaluation time in milliseconds (default: 5000, max: 30000)
1540
-
1541
- **Returns:**
1542
- - `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
+ ```
1543
1566
 
1544
1567
  **Notes:**
1545
- - Requires `debug_connect` first—must be connected to a Node.js process
1546
- - **You must use `return` to get output** (e.g. `return 2+2;`, `return process.memoryUsage();`). The script runs inside an async function.
1547
- - **Async code supported:** use `await` and return a Promise; the tool waits for it and returns the resolved value.
1548
- - Executes in the NODE PROCESS CONTEXT (real Node.js environment):
1549
- - Has access to process, require, global, and all loaded modules
1550
- - Can read/modify process state
1551
- - Full Node.js APIs (fs, http, etc.)
1552
- - Execution blocks the Node event loop until the script (and any returned Promise) completes
1553
- - Long-running scripts will block the process; use short scripts
1554
- - Return value must be serializable
1555
-
1556
- **Typical use cases:**
1557
- - Inspect process state: `return process.memoryUsage();`, `return process.uptime();`
1558
- - Call loaded modules: `return require('os').loadavg();`
1559
- - Read globals or cached data
1560
- - 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.
1561
1572
  </details>
1562
1573
 
1563
1574
  ### Observability (O11Y) Tools
@@ -1592,9 +1603,12 @@ Once disabled, no data is sent and no network requests are made to PostHog.
1592
1603
  - `limit` (object, optional): Limit results (default: last 100). Omit or set `count: 0` for no limit.
1593
1604
  - `count` (number, default 100): Maximum number of requests; 0 = no limit
1594
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)
1595
1609
 
1596
1610
  **Returns:**
1597
- - `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.
1598
1612
  </details>
1599
1613
 
1600
1614
  <details>
@@ -2174,7 +2188,7 @@ A dedicated Claude Code plugin is available with slash commands, skills, and age
2174
2188
  **Skills (6 skills):**
2175
2189
  - `browser-testing` - General browser test capabilities
2176
2190
  - `web-debugging` - Console, network, JS debugging
2177
- - `node-debugging` - Node.js backend debugging (tracepoints, logpoints, run_js-in-node)
2191
+ - `node-debugging` - Node.js backend debugging (tracepoints, logpoints)
2178
2192
  - `performance-audit` - Web Vitals and performance analysis
2179
2193
  - `visual-testing` - Visual testing and responsive design
2180
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-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(`
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(`