mcp-accessibility-scanner 3.3.2 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +146 -11
  2. package/config.d.ts +48 -3
  3. package/lib/browserContextFactory.js +15 -313
  4. package/lib/browserContextFactory.js.map +1 -1
  5. package/lib/browserServerBackend.js +11 -0
  6. package/lib/browserServerBackend.js.map +1 -1
  7. package/lib/browserSessions.js +3 -0
  8. package/lib/browserSessions.js.map +1 -1
  9. package/lib/config.js +70 -12
  10. package/lib/config.js.map +1 -1
  11. package/lib/context.js +463 -57
  12. package/lib/context.js.map +1 -1
  13. package/lib/extension/cdpRelay.js +82 -14
  14. package/lib/extension/cdpRelay.js.map +1 -1
  15. package/lib/extension/extensionContextFactory.js +15 -8
  16. package/lib/extension/extensionContextFactory.js.map +1 -1
  17. package/lib/index.js +2 -0
  18. package/lib/index.js.map +1 -1
  19. package/lib/mcp/http.js +115 -41
  20. package/lib/mcp/http.js.map +1 -1
  21. package/lib/mcp/server.js +16 -8
  22. package/lib/mcp/server.js.map +1 -1
  23. package/lib/mcp/tool.js +11 -9
  24. package/lib/mcp/tool.js.map +1 -1
  25. package/lib/program.js +28 -8
  26. package/lib/program.js.map +1 -1
  27. package/lib/response.js +22 -5
  28. package/lib/response.js.map +1 -1
  29. package/lib/sessionLog.js +66 -20
  30. package/lib/sessionLog.js.map +1 -1
  31. package/lib/tab.js +13 -7
  32. package/lib/tab.js.map +1 -1
  33. package/lib/tools/auditKeyboard.js +27 -25
  34. package/lib/tools/auditKeyboard.js.map +1 -1
  35. package/lib/tools/auditScreenReader.js +27 -18
  36. package/lib/tools/auditScreenReader.js.map +1 -1
  37. package/lib/tools/auditSite.js +88 -56
  38. package/lib/tools/auditSite.js.map +1 -1
  39. package/lib/tools/axe.js +32 -7
  40. package/lib/tools/axe.js.map +1 -1
  41. package/lib/tools/common.js +39 -1
  42. package/lib/tools/common.js.map +1 -1
  43. package/lib/tools/files.js +60 -3
  44. package/lib/tools/files.js.map +1 -1
  45. package/lib/tools/install.js +1 -1
  46. package/lib/tools/install.js.map +1 -1
  47. package/lib/tools/pdf.js +4 -2
  48. package/lib/tools/pdf.js.map +1 -1
  49. package/lib/tools/recorder.js +52 -0
  50. package/lib/tools/recorder.js.map +1 -0
  51. package/lib/tools/report.js +13 -0
  52. package/lib/tools/report.js.map +1 -0
  53. package/lib/tools/scanPageMatrix.js +20 -33
  54. package/lib/tools/scanPageMatrix.js.map +1 -1
  55. package/lib/tools/screenshot.js +7 -3
  56. package/lib/tools/screenshot.js.map +1 -1
  57. package/lib/tools/snapshot.js +10 -3
  58. package/lib/tools/snapshot.js.map +1 -1
  59. package/lib/tools.js +5 -1
  60. package/lib/tools.js.map +1 -1
  61. package/lib/vscode/browserContextFactory.js +16 -12
  62. package/lib/vscode/browserContextFactory.js.map +1 -1
  63. package/lib/vscode/host.js +25 -2
  64. package/lib/vscode/host.js.map +1 -1
  65. package/lib/vscode/main.js +15 -0
  66. package/lib/vscode/main.js.map +1 -1
  67. package/lib/vscode/validation.js +78 -0
  68. package/lib/vscode/validation.js.map +1 -0
  69. package/package.json +9 -8
package/README.md CHANGED
@@ -137,8 +137,12 @@ Use `--extension` to connect through the current [Playwright Extension](https://
137
137
  npx mcp-accessibility-scanner --extension
138
138
  ```
139
139
 
140
- Set `PLAYWRIGHT_MCP_EXTENSION_TOKEN` to the token shown by the extension to bypass the connection approval dialog.
141
- When `--user-data-dir` contains multiple Chrome profiles, the profile with the extension installed is selected automatically, preferring Chrome's last-used profile.
140
+ Set `PLAYWRIGHT_MCP_EXTENSION_TOKEN` to the token shown by the extension to bypass the connection approval dialog. The relay's CDP WebSocket endpoint always requires a separate random token, generated per relay and appended automatically for the server's own connection. This CDP token is never passed in Chrome's launch arguments or extension URL; the extension approval token cannot authenticate a CDP client.
141
+ Token-bypass connections are not background-safe: Chrome focuses the connection tab and window, and client-created tabs remain open after disconnect ([upstream limitation](https://github.com/microsoft/playwright/issues/42343)).
142
+ With a token, the extension must connect and finish setup within 30 seconds after the connection page opens. Failed attempts release the relay so the next tool call can retry. Without a token, manual approval waits until you approve or cancel the call.
143
+ When `--user-data-dir` contains multiple Chrome profiles, the profile with the extension installed is selected automatically, preferring Chrome's last-used profile. Pass `--profile-dir-name` (or set `PLAYWRIGHT_MCP_PROFILE_DIR_NAME`) to select a profile explicitly; it requires `--user-data-dir` and accepts a Chrome profile directory name such as `Default` or `Profile 1` (see "Profile Path" at `chrome://version`). The `PLAYWRIGHT_MCP_EXTENSION_TOKEN` approval token is specific to the profile, so when selecting a profile explicitly, use the token shown in that profile.
144
+
145
+ Packed extensions require an enabled record in the profile's preferences; a leftover extension directory alone does not count as installed. Profiles whose preferences mark the extension disabled or uninstalled are excluded from automatic selection and rejected for explicit selection. Explicit selection with a custom executable skips local installation checks. A whitespace-only `PLAYWRIGHT_MCP_PROFILE_DIR_NAME` is treated as unset; other string environment variables retain their existing blank-value handling.
142
146
 
143
147
  ### Discovering available tools (`list-tools` subcommand)
144
148
 
@@ -201,11 +205,15 @@ Create a `config.json` file with the following options:
201
205
  "timeouts": {
202
206
  "navigationTimeout": 60000,
203
207
  "defaultTimeout": 5000,
204
- "settle": 500
208
+ "settle": 500,
209
+ "idle": 0
205
210
  },
206
211
  "network": {
207
212
  "allowedOrigins": ["example.com", "trusted-site.com"],
208
213
  "blockedOrigins": ["ads.example.com"]
214
+ },
215
+ "snapshot": {
216
+ "boxes": true
209
217
  }
210
218
  }
211
219
  ```
@@ -213,28 +221,54 @@ Create a `config.json` file with the following options:
213
221
  **Available Options:**
214
222
 
215
223
  - `browser.browserName`: Browser to use (`chromium`, `firefox`, `webkit`)
224
+ - `browser.allowedUploadDirs`: Restrict files sent by `browser_file_upload` and `browser_drop` to regular files inside these directories, including resolved symlink targets. Restricted uploads and drops use a checked file handle and accept up to 50 MiB total per call. Unset allows any path; `[]` denies all file uploads and drops (text-only drops still work). Blank list entries are rejected. CLI: `--allowed-upload-dirs` (semicolon-separated; `""` denies all), env: `PLAYWRIGHT_MCP_ALLOWED_UPLOAD_DIRS` (empty string denies all).
225
+
226
+ The list must be an array, not `null`. Roots must exist at startup: their canonical paths are resolved once and retained for the server's lifetime, so retargeting a configured symlink does not grant access to a new tree. Non-empty upload allowlists require macOS or Linux with `/proc/self/fd` available. macOS blocks ancestor symlinks during the file open; Linux checks the opened descriptor's path. Other platforms reject restricted file uploads and drops rather than rely on race-prone pathname checks. Unrestricted uploads, deny-all lists, and text-only drops keep working on all platforms.
216
227
  - `browser.launchOptions.headless`: Run browser in headless mode (default: `true` on Linux without display, `false` otherwise)
217
228
  - `browser.launchOptions.channel`: Browser channel (`chrome`, `chrome-beta`, `msedge`, etc.)
229
+ - `browser.launchOptions.chromiumSandbox`: Defaults to `false` for downloaded Chromium builds on Linux because they lack the setuid sandbox helper, and `true` otherwise. Remote and VS Code endpoints choose on the remote host. An explicit config or `PLAYWRIGHT_MCP_SANDBOX` value wins; `--no-sandbox` always disables it.
218
230
  - `browser.cdpEndpoint`: Attach to an already-running Chromium-family app with CDP enabled
219
231
  - `browser.cdpHeaders`: Map of HTTP headers to send with the CDP connect request, e.g. `{ "Authorization": "Bearer <token>" }`, for endpoints that require header-based authentication
220
232
  - `browser.cdpTimeout`: Maximum time in milliseconds to wait when connecting to the CDP endpoint (default: `30000`)
221
233
  - `browser.cdpLaunch`: Launch a Chromium-family desktop app with CDP enabled, wait for the endpoint, and manage the child process lifecycle
222
234
  - CDP attach modes preserve the target browser's existing default-context settings instead of applying Playwright's defaults.
223
- - `browser.contextOptions.storageState`: Start each session from a recorded Playwright storage state; applied in every mode except `--extension` (fresh contexts receive it at creation, reused contexts via `setStorageState()`). Sessions that share one reused context (non-isolated CDP modes) get the state applied once per context a session joining a live context inherits its current state, not a fresh copy of the file; see [Auditing pages behind a login](#auditing-pages-behind-a-login)
235
+ - `browser.contextOptions.storageState`: Start a fresh context from a recorded Playwright storage state. Imports into existing CDP or VS Code contexts are rejected; use `--isolated` for CDP or sign in interactively. The default persistent mode uses a fresh disposable profile. See [Auditing pages behind a login](#auditing-pages-behind-a-login).
236
+ - `browser.profileDirName`: Chrome profile directory name used in extension mode, for example `Default` or `Profile 1` (CLI: `--profile-dir-name`, env: `PLAYWRIGHT_MCP_PROFILE_DIR_NAME`). Requires `--user-data-dir` and extension mode (`--extension` or `--connect-tool`); defaults to the last-used profile that has the extension installed.
224
237
  - `timeouts.navigationTimeout`: Maximum time for page navigation in milliseconds (default: `60000`)
225
238
  - `timeouts.defaultTimeout`: Default timeout for Playwright operations in milliseconds (default: `5000`)
226
239
  - `timeouts.settle`: How long to wait after every action before responding (default: `500`). An action that finishes quietly is first watched for up to 100ms (or the settle delay, whichever is shorter) so scheduled network work can still be awaited before the settle delay.
240
+ - `timeouts.idle`: Release the default browser context after this many idle milliseconds (default: `0`, disabled). Accepts integers from `0` to `2147483647`.
227
241
  - `network.allowedOrigins`: List of origins to allow (blocks all others if specified)
228
242
  - `network.blockedOrigins`: List of origins to block
243
+ - `snapshot.boxes`: Include each element's viewport-relative bounding box as `[box=x,y,width,height]` in snapshots (default: `false`; CLI: `--snapshot-boxes`, env: `PLAYWRIGHT_MCP_SNAPSHOT_BOXES=1`)
244
+ - `imageResponses`: `allow` (default) returns text and images; `omit` excludes images; `only` omits text from successful responses containing images. Errors, browser lifecycle notices, and responses without images (including full-page screenshots) keep their text. Structured results and resource links are always preserved. In `only` mode, screenshot save-path text, generated code and any accompanying text-only findings are omitted; use `allow` if you need them. Interactive mode rejects `only` because its REPL prints text only; use `allow` or `omit` there. `auto` remains a legacy alias for `allow`. CLI: `--image-responses only`; env: `PLAYWRIGHT_MCP_IMAGE_RESPONSES=only`. Precedence: CLI, then environment, then config file.
245
+ - `server.authToken`: When set, Streamable HTTP requests (`--port`) require `Authorization: Bearer <token>` or return `401` (env: `PLAYWRIGHT_MCP_AUTH_TOKEN`). Blank or malformed tokens fail at startup. The scheme is case-insensitive; the token is exact. Bearer auth does not encrypt traffic: authenticated listeners must bind to loopback, such as `--host 127.0.0.1`; use a TLS reverse proxy for remote access. The printed client config includes a header placeholder to replace locally, without logging the secret. Unset keeps unauthenticated access.
229
246
  - `outputDir`: Directory for output files — reports, screenshots, traces, and session logs (CLI: `--output-dir`, env: `PLAYWRIGHT_MCP_OUTPUT_DIR`). Defaults to a fresh directory under the system temp folder, resolved once per server run so all of a run's artifacts land together. The output location is always server configuration; the deprecated MCP roots capability (client workspace folders) is no longer consulted.
230
247
 
231
248
  CLI equivalents are also available: `--cdp-launch-command`, `--cdp-launch-args`, `--cdp-launch-cwd`, `--cdp-launch-port`, `--cdp-launch-startup-timeout`, `--cdp-endpoint`, `--cdp-header` (repeat for multiple headers, e.g. `--cdp-header "Authorization: Bearer <token>"`), and `--cdp-timeout`. The CDP headers and timeout can also be set via the `PLAYWRIGHT_MCP_CDP_HEADERS` (one `Name: Value` entry per line) and `PLAYWRIGHT_MCP_CDP_TIMEOUT` environment variables.
232
249
 
250
+ If CDP attachment times out after the WebSocket connects, an existing sleeping or unresponsive tab may be blocking Playwright's browser initialization ([upstream report](https://github.com/microsoft/playwright/issues/42730)). Use an explicit positive `--cdp-timeout` to bound the attempt. Inspect or wake the affected tabs yourself, or attach to a separate disposable browser. `noDefaults` and `--isolated` do not skip initialization of existing tabs; the server does not close your tabs or bypass Playwright's initialization to work around this.
251
+
252
+ For remote HTTP access, configure the TLS reverse proxy explicitly. For example, with the MCP server bound using `--host 127.0.0.1 --port 8931` and `PLAYWRIGHT_MCP_AUTH_TOKEN` set:
253
+
254
+ - Accept only your configured public hostname over HTTPS and forward `/mcp` to `http://127.0.0.1:8931/mcp`.
255
+ - Set the upstream `Host` header to `127.0.0.1:8931`, not the public hostname. Forward the client's `Authorization` header unchanged; do not inject a shared token for unauthenticated clients.
256
+ - Before removing `Origin`, reject any non-empty value outside your explicit trusted HTTPS origin list (for example, `https://mcp.example.com`). Allow absent `Origin` for non-browser clients. Then remove `Origin` upstream, or rewrite it to `http://127.0.0.1:8931`. Never strip arbitrary origins without checking them first.
257
+ - Disable response buffering for SSE streams. Browser clients on a different origin also need a narrowly scoped CORS policy at the proxy.
258
+
259
+ The server does not trust `Forwarded` or `X-Forwarded-*` to bypass its checks. Preserving the public `Host` or HTTPS `Origin` upstream returns `403`, even with a valid bearer token.
260
+
261
+ Caller-supplied screenshot, PDF, scan-page-matrix, and audit report filenames use a no-clobber policy: an existing file causes the tool call to fail instead of being overwritten. Windows-reserved basenames and names ending in a dot or space are rejected on every platform so configured names behave consistently across hosts.
262
+
233
263
  Use `--timeout-settle` or `PLAYWRIGHT_MCP_TIMEOUT_SETTLE` to override the post-action settle delay. It applies after every action so delayed DOM-only updates are included in the response; a short observation window also catches scheduled requests and waits for them before that delay.
234
264
 
265
+ Use `--timeout-idle 300000`, `timeouts.idle`, or `PLAYWRIGHT_MCP_TIMEOUT_IDLE` to release the default browser after five idle minutes. Shared contexts stay open while any client is working; the idle window starts after the last tool call or download finishes. Explicit recordings prevent idle release until `browser_stop_recording` finishes; passive `--save-session` capture does not. Cleanup finalizes traces. The next browser tool call reopens the connection and includes a note to navigate again and refresh element references. Attached CDP, extension, and VS Code browsers are disconnected; their external pages remain open. Close and session-management tools do not relaunch an idle browser. Explicit `browser_session_open` handles keep their separate `PLAYWRIGHT_MCP_BROWSER_SESSION_TTL_MS` behavior. Zero disables this feature; blank environment values leave the existing configuration unchanged.
266
+
267
+ The VS Code `browser_connect` tool accepts only `playwright` or `playwright-core` libraries and loopback WebSocket URLs. Set `PLAYWRIGHT_MCP_VSCODE_ALLOW_REMOTE=1` to allow remote endpoints, which must use `wss:`. URL userinfo credentials are rejected.
268
+
235
269
  #### HTTP Heartbeat
236
270
 
237
- When the server runs with `--port`, it sends MCP heartbeat pings for Streamable HTTP sessions. Set `PLAYWRIGHT_MCP_PING_TIMEOUT_MS` to override the default `5000` ms timeout. Set it to `0` or any negative value to disable heartbeat pings for clients or proxies that do not answer server-initiated pings. A client that answers `ping` with a JSON-RPC "method not found" error (as clients on the MCP 2026-07-28 revision do) is treated as alive: the server stops heartbeating that session instead of closing it. Only an unanswered ping (timeout) or a transport failure closes the session.
271
+ When the server runs with `--port`, it sends MCP heartbeat pings after a Streamable HTTP client opens the optional event stream. POST-only clients stay connected without heartbeat because server-initiated requests cannot reach them. Set `PLAYWRIGHT_MCP_PING_TIMEOUT_MS` to override the default `5000` ms timeout, or to `0` or any negative value to disable heartbeat pings. A client that answers `ping` with a JSON-RPC "method not found" error is treated as alive: the server stops heartbeating that session instead of closing it. Only an unanswered ping (timeout) or a transport failure closes the session.
238
272
 
239
273
  #### Clients without the initialize handshake
240
274
 
@@ -262,7 +296,7 @@ This works out of the box in every mode, including the default persistent-profil
262
296
  Record a session once with Playwright's codegen, then hand the file to the server:
263
297
 
264
298
  ```bash
265
- npx playwright@1.63.0-alpha-2026-08-22 codegen --save-storage=auth.json https://example.com/login
299
+ npx playwright@1.63.0 codegen --save-storage=auth.json https://example.com/login
266
300
  ```
267
301
 
268
302
  Sign in in the opened browser, then close it — `auth.json` now holds the cookies and local storage.
@@ -290,10 +324,14 @@ PLAYWRIGHT_MCP_ISOLATED=true PLAYWRIGHT_MCP_STORAGE_STATE=./auth.json npx mcp-ac
290
324
 
291
325
  > **Every supported mode handles the state — by applying it or refusing it.**
292
326
  >
327
+ > **Playwright 1.63.0 safety restriction:** importing into an existing context is rejected before taking a rollback snapshot or resetting any storage. On this pin, snapshot capture can execute service-worker-served scripts for a previously visited origin whose tab is no longer open ([upstream fix](https://github.com/microsoft/playwright/pull/42664)). Use a fresh context, or omit `--storage-state` and sign in interactively. Service workers are not disabled. A future dependency upgrade must also pass the recorder/shared-client checks in [#218](https://github.com/JustasMonkev/mcp-accessibility-scanner/issues/218) and IndexedDB checks in [#224](https://github.com/JustasMonkev/mcp-accessibility-scanner/issues/224) before this restriction is reconsidered.
328
+ >
329
+ > **IndexedDB snapshot limitation:** on pinned Playwright 1.63.0 with Chromium 153.0.8010.12 and Firefox 155.0, `storageState({ indexedDB: true })` loses `Map` and `Set` contents. Both `newContext({ storageState })` and `setStorageState()` restore them as empty plain objects; ordinary JSON records survive. Fresh contexts protect existing browser data, but cannot recover values already lost during capture. The [upstream fix](https://github.com/microsoft/playwright/pull/42707) is merged but is not in this pin. Before allowing imports into existing contexts again, verify both restore paths preserve Map/Set types and entries on each supported engine, including after a failed import. The real-browser regression in `tests/browser-failures.integration.test.ts` checks that rejecting an import leaves the original Map/Set records intact and that isolated JSON IndexedDB imports still work.
330
+ >
293
331
  > - **Fresh-context modes** (`--isolated`, the remote-endpoint mode, or either CDP mode combined with `--isolated`): the context is created with the storage state directly.
294
332
  > - **Default persistent-profile mode with `--storage-state`**: the session runs in a fresh, disposable profile — unique to that session and removed when it closes — built from the state, so the recorded state is provably the only session data (without `--storage-state` the regular persistent profile is used and survives restarts, as before). Any page the launch opened (for example from a URL in `browser.launchOptions.args`) is parked on a blank replacement before the state lands, then the replacement is navigated to the same URL, so a still-running anonymous page cannot overwrite the recorded identity and a scan never reads its DOM. This also means `--storage-state` cannot be combined with `--user-data-dir` (a user-supplied profile carries its own session and will not be wiped; the server refuses the combination).
295
- > - **CDP modes without `--isolated`**: the state is installed into the browser's existing context with Playwright's `setStorageState()`. Cookies are fully reset; origin storage (localStorage/IndexedDB) is reset for the origins recorded in the state *plus* any origins the Playwright connection has already seen — including pages open in the attached browser at connect time, whose storage can therefore be cleared even when the state omits them. Only origins from the profile's earlier history that this connection never saw survive untouched — cut in both directions, so treat an attached browser's storage as neither fully preserved nor fully reset, and add `--isolated` when you need a clean, fully-defined session. Pages already open in the attached browser are replaced with fresh tabs navigated to the same URLs so a scan never sees the previous identity's UI — and the old pages close *before* the state is installed, because a still-running page could otherwise persist the previous identity back into the freshly applied cookies or localStorage, which no later tab replacement could undo. A fresh tab also starts with empty per-tab `sessionStorage` (which sits outside Playwright storage states and would survive an in-place reload, where the old page's own scripts could even write the previous identity back between a clear and the reload), a replacement that fails to load is left blank or closed rather than left on a stale document, and these navigations run under a configured `--allowed-origins`/`--blocked-origins` policy just like every later navigation. The state is applied once per shared context: concurrent MCP sessions attached without `--isolated` share the browser's context, so a session joining while another is active inherits that context's live state (including anything the first session changed or cleared) rather than a fresh copy of the recorded file — add `--isolated` when every session must start from the recorded baseline.
296
- > - **`--extension`** (with or without `--isolated`) is the one exception: it works through the browser you are already running, where wiping cookies to install a recorded state is not an acceptable side effect, so the server refuses to start rather than doing that silently. There, sign in interactively instead — the persistent profile also keeps the session across restarts.
333
+ > - **CDP modes without `--isolated` and the VS Code provider**: `--storage-state` is rejected when the browser already has a context. Add `--isolated` in CDP mode to create a fresh context; otherwise omit the state and sign in interactively. If the browser exposes no context, the server creates one with the state. CDP sessions joining that same server-created context inherit its live state without resetting it.
334
+ > - **`--extension`** (with or without `--isolated`) refuses storage imports entirely: it works through the browser you are already running, where wiping cookies to install a recorded state is not an acceptable side effect, so the server refuses to start rather than doing that silently. There, sign in interactively instead — the persistent profile also keeps the session across restarts.
297
335
 
298
336
  ### Keep the crawl from destroying its own session
299
337
 
@@ -313,6 +351,8 @@ The check compares which cookies the crawled URLs carry, not their values, so a
313
351
 
314
352
  ## Available Tools
315
353
 
354
+ Page-registered WebMCP tools are not currently exposed. See the [WebMCP adoption decision](https://github.com/JustasMonkev/mcp-accessibility-scanner/blob/main/docs/decisions/001-webmcp-adoption.md) for the deferral and conditions for revisiting an opt-in capability.
355
+
316
356
  The MCP server provides comprehensive browser automation and accessibility scanning tools:
317
357
 
318
358
  ### Core Accessibility Tool
@@ -331,7 +371,7 @@ Performs a comprehensive accessibility scan on the current page using Axe-core.
331
371
  **Annotated screenshots:**
332
372
  When `annotateScreenshot` is `true`, each violating element is outlined and labelled with the rule ids it failed, a full-page PNG is written to the MCP output directory (`scan-page-annotated-{timestamp}-{token}.png`) and returned as a resource link, and the markers are then removed so the page is left exactly as it was. The markers are drawn in an out-of-flow overlay clipped to each element's own box, so they never reflow the page. The overlay uses a fresh id per scan, is placed in the browser's top layer so it stays visible over an open dialog, popover or fullscreen element, and compensates for a CSS `zoom` or a scaled ancestor so markers line up with what is rendered.
333
373
  An element that fails several rules gets one box listing every rule id, and elements inside open shadow roots are marked by walking the shadow path Axe reports.
334
- Running animations are paused before the elements are measured and resumed after the capture, so a moving target keeps its marker. The markers themselves live in a shadow root under an overlay whose own styles are `!important`, so page CSS cannot restyle or hide what the report counts, and each rule label sits outside the clipped box so it stays readable on an element smaller than its own label.
374
+ Running animations are frozen at their current time before the elements are measured and resumed after the capture, so a moving target keeps its marker. The markers themselves live in a shadow root under an overlay whose own styles are `!important`, so page CSS cannot restyle or hide what the report counts, and each rule label sits outside the clipped box so it stays readable on an element smaller than its own label.
335
375
  At most 50 elements are annotated per scan. The result text always reports how many nodes were marked out of the total, plus how many were left out because they exceeded the limit, were hidden, zero-size or off-canvas (a full-page screenshot is clipped to the document box), or were inside an iframe (cross-frame selectors cannot be resolved from the top document).
336
376
 
337
377
  **Supported Violation Tags:**
@@ -380,6 +420,7 @@ A frame you scoped out yourself is not reported: with `excludeSelectors: ["ifram
380
420
  Crawls and scans multiple internal pages, then aggregates violations across the site.
381
421
  - Default strategy: link-based BFS from the current URL
382
422
  - Supports `links`, `nav`, `sitemap`, and `provided` URL strategies
423
+ - Sitemap URLs and every redirect must pass the server network policy and crawl scope. Fetches run on the MCP host, use HTTP(S) without browser cookies or auth headers, and have a 15-second total timeout, 20-redirect cap, and 10 MiB response limit. Browser proxy settings, `browser.remoteEndpoint`, `browser.cdpEndpoint` (including loopback endpoints, which may tunnel to remote browsers), and switched `browser_connect` providers are rejected for this strategy; use `provided` URLs in these modes. Sitemap TLS certificates must be valid even when browser HTTPS errors are ignored.
383
424
  - Always writes a JSON report (default filename: `audit-site-{timestamp}-{token}.json`)
384
425
  - Warns and records `sessionLosses` if the crawl loses cookies it started with — see [Auditing pages behind a login](#auditing-pages-behind-a-login)
385
426
 
@@ -497,9 +538,10 @@ Set default operation timeout for existing tabs.
497
538
  Capture accessibility snapshot of the current page (better than screenshot for analysis).
498
539
  Large `data:` URL payloads in snapshot output are truncated to their media type prefix.
499
540
  AI snapshots mark a visually present subtree excluded from accessibility queries with `[aria-hidden]` on its boundary element. Descendants are not marked again.
500
- - Parameters: `compress` (optional boolean, default false)
501
- - When true, repeated non-interactive ARIA snapshot nodes are collapsed in the rendered response when a repeated structural pattern appears more than 100 times. The first 10 examples of each collapsed pattern are kept.
541
+ - Parameters: `compress` (optional boolean, default false), `boxes` (optional boolean; overrides `snapshot.boxes` for this call)
542
+ - When `compress` is true, repeated non-interactive ARIA snapshot nodes are collapsed in the rendered response when a repeated structural pattern appears more than 100 times. The first 10 examples of each collapsed pattern are kept.
502
543
  - Use `browser_evaluate()` to retrieve the full uncompressed list when needed.
544
+ - When `boxes` is true, each element includes `[box=x,y,width,height]` in viewport-relative CSS pixels.
503
545
 
504
546
  #### `browser_find`
505
547
  Search the current page accessibility snapshot without returning the full snapshot.
@@ -541,6 +583,14 @@ Fill multiple fields with one call.
541
583
  Press a key on the keyboard.
542
584
  - Parameters: `key` (e.g., 'ArrowLeft' or 'a')
543
585
 
586
+ #### `browser_start_recording` / `browser_stop_recording`
587
+ Record browser actions and return them as Playwright JavaScript. Start the server with `--caps devtools`, call `browser_start_recording`, perform the flow, then call `browser_stop_recording`.
588
+
589
+ Multi-tab recordings include the `context.newPage()` declarations needed by generated page aliases.
590
+ Recorded assertions include the `playwright/test` `expect` setup they need to run.
591
+
592
+ Handshake-free HTTP clients must first call `browser_session_open`, then pass its `browserSessionId` to both recording tools so the recording survives across requests. Modes that cannot open separate browser sessions, such as `--extension` and non-isolated CDP attach, need a stateful MCP connection for recording.
593
+
544
594
  #### `browser_evaluate`
545
595
  Evaluate a JavaScript expression on the page, or on a specific element when a `ref` is provided. The function's return value is serialized back as the result.
546
596
  - Parameters: `function` (e.g., `() => document.title` or `(element) => element.textContent`), `element` (optional), `ref` (optional)
@@ -554,6 +604,7 @@ Evaluate a JavaScript expression on the page, or on a specific element when a `r
554
604
  Take a screenshot of the current page.
555
605
  - Parameters: `filename` (optional), `type` (`png`, `jpeg`, or `webp`), `scale` (`css` or `device`, default `css`), `fullPage` (optional), `element`/`ref` pair (for element screenshots)
556
606
  - `scale: device` captures a high-resolution screenshot using device pixels (accounts for the device pixel ratio); `scale: css` keeps the image sized in CSS pixels.
607
+ - An empty capture is an error, and its output file is removed, including automatically named files. The requested format is never silently changed. If a WebP capture is empty, reduce its dimensions or explicitly request PNG/JPEG.
557
608
 
558
609
  #### `browser_pdf_save`
559
610
  Save page as PDF.
@@ -565,6 +616,12 @@ This tool requires `--caps pdf` in the CLI.
565
616
  Install the configured browser engine (use when browser executable is missing).
566
617
  - Parameters: none
567
618
 
619
+ Disabled by default. Enable it at server startup with `--caps install`, `PLAYWRIGHT_MCP_CAPS=install`, or `"capabilities": ["install"]` in the config file. Explicit `core-install` settings remain supported as a deprecated alias; use `install` in new configurations. Without this opt-in, the tool is neither listed nor callable; existing browser installations can still be used.
620
+
621
+ This tool invokes Playwright's installer, which downloads executable code. In [Playwright 1.63.0](https://github.com/microsoft/playwright/blob/v1.63.0/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts), browser archives have no checksum or signature verification before extraction; the default download hosts use HTTPS. Only enable installation when you trust the download source and TLS configuration, including any custom `PLAYWRIGHT_DOWNLOAD_HOST`, browser-specific host overrides, or TLS-inspecting proxy. Do not disable TLS certificate validation.
622
+
623
+ For deployments that require independently verified binaries, provision the browser through your trusted deployment process and use `--executable-path` or an existing browser connection. The capability opt-in limits MCP-triggered installation; it does not add archive verification or change manual, CI, or Docker build downloads.
624
+
568
625
  ### Browser Management
569
626
 
570
627
  #### `browser_close`
@@ -574,6 +631,10 @@ Close the page.
574
631
  Resize the browser window.
575
632
  - Parameters: `width`, `height`
576
633
 
634
+ #### `browser_emulate_media`
635
+ Emulate CSS media features on the current page without resetting omitted features.
636
+ - Parameters: `colorScheme` (`light` or `dark`), `reducedMotion` (`reduce` or `no-preference`), `forcedColors` (`active` or `none`), `contrast` (`more` or `no-preference`), and `media` (`screen` or `print`); provide at least one.
637
+
577
638
  ### Tab Management
578
639
 
579
640
  #### `browser_tabs`
@@ -637,6 +698,7 @@ Handle browser dialogs (alerts, confirms, prompts).
637
698
  #### `browser_file_upload`
638
699
  Upload files to the page.
639
700
  - Parameters: `paths` (array of absolute file paths)
701
+ - If `setFiles` fails, the chooser stays available for another upload attempt; `paths: []` clears the selection and completes the chooser. A successful upload clears only that chooser and waits for page activity and the configured settle delay.
640
702
 
641
703
  #### `browser_verify_element_visible`
642
704
  Verify an element by ARIA role/name.
@@ -733,6 +795,79 @@ cd mcp-accessibility-scanner
733
795
  npm install
734
796
  ```
735
797
 
798
+ ### Playwright upgrade gate
799
+
800
+ The September 16, 2026 review keeps `playwright` and `playwright-core` paired at
801
+ **1.63.0**, the [latest stable release](https://github.com/microsoft/playwright/releases/tag/v1.63.0)
802
+ on that date. Keep the local `InputRecorder` hub and the existing factory reference
803
+ counts: multiple MCP clients share one client-side browser context, while the hub
804
+ multiplexes session logs and explicit recordings and excludes sibling tool actions.
805
+ A dependency bump alone must not change that ownership model.
806
+
807
+ Before adopting a stable release containing [upstream #42627](https://github.com/microsoft/playwright/pull/42627),
808
+ adapt the hub from `_enableRecorder` / `_disableRecorder` to
809
+ `_startRecording({ language: 'javascript' }, sink)` / `_stopRecording()` and verify
810
+ the per-client event contract against the installed runtime. Do not ship a
811
+ prerelease bump or an untested method-name fallback. Migrating to the separate
812
+ connections in [#42622](https://github.com/microsoft/playwright/pull/42622) is a
813
+ separate ownership change requiring the same lifecycle checks.
814
+
815
+ Install the pinned Chromium browser, then run the real recorder gate alongside
816
+ its failure and concurrency tests:
817
+
818
+ ```bash
819
+ npx playwright install chromium
820
+ npx vitest run tests/recorder.integration.test.ts tests/context.test.ts tests/browserSessions.test.ts tests/browserContextFactory.test.ts tests/tools-recorder.test.ts tests/sessionLog.test.ts
821
+ ```
822
+
823
+ The real-browser tests cover concurrent starts, duplicate-start rejection,
824
+ shared CDP clients, sibling-action attribution, stop/disconnect/restart,
825
+ `--save-session`, and recording across stateless explicit sessions. Existing unit
826
+ tests also cover failed-start recovery and overlapping start/stop. The recorder
827
+ must receive the final input event before stop; unbuffered input delivered after
828
+ stop begins is excluded, while buffered clicks/navigation get a 500 ms drain.
829
+
830
+ Also recheck the dependency fixes motivating the upgrade. On 1.63.0 Chromium,
831
+ both full-page and oversized element screenshots changed `navigator.maxTouchPoints`
832
+ from 1 to 0 and `(pointer: coarse)` from true to false; navigation restored the
833
+ properties ([#42617](https://github.com/microsoft/playwright/pull/42617)).
834
+ The fixed-header/smooth-scroll retry fixture clicked successfully but emitted 19
835
+ scroll events rather than instant jumps ([#42626](https://github.com/microsoft/playwright/pull/42626)).
836
+ A candidate upgrade must preserve touch properties after full-page and element
837
+ screenshots and navigation, and complete retry scrolling without smooth animation.
838
+ These are dependency limitations; the recorder gate alone does not verify them.
839
+
840
+ ### MCP harnesses
841
+
842
+ The npm wrappers build first, then the direct harness calls every exposed MCP
843
+ tool with prepared fixtures:
844
+
845
+ ```bash
846
+ npm run test:mcp
847
+ npm run test:mcp:install
848
+ ```
849
+
850
+ The Luna wrapper runs each prompt through Codex `gpt-5.6-luna` with xhigh
851
+ reasoning. It uses a repo-scoped scanner MCP server, read-only Codex sandbox,
852
+ structured PASS/FAIL evidence, and a per-prompt timeout:
853
+
854
+ ```bash
855
+ npm run test:mcp:luna
856
+ npm run test:mcp:luna -- --only browser_snapshot
857
+ npm run test:mcp:luna -- --skip-optional --limit 1
858
+ ```
859
+
860
+ Results are written under `test-results/mcp-direct-harness-results/` or
861
+ `test-results/mcp-tool-loop-results/`. Set `MCP_HARNESS_RESULTS_DIR` to use another output root.
862
+ The Luna harness checks Codex login before starting any prompts. Its site-audit
863
+ prompt uses a local HTTP fixture, not a public website.
864
+ PASS requires a completed target MCP call, no failed or unfinished MCP calls,
865
+ and a successful structured result. Invalid logs, failed turns, and timeouts
866
+ fail the run. Non-fatal startup notices about ignored malformed local agent
867
+ roles remain in the logs; they do not count as tool failures. Recovered Codex
868
+ connection retries are accepted only when the turn completes successfully.
869
+ Canceling the run exits with status 130.
870
+
736
871
  ### Benchmarking tool latency
737
872
 
738
873
  `bench/mcp-bench.mjs` measures what a client actually waits for: it serves a fixed
package/config.d.ts CHANGED
@@ -25,8 +25,9 @@ export type ToolCapability =
25
25
  | 'files'
26
26
  | 'install'
27
27
  | 'testing'
28
- | 'core-install'
28
+ | 'core-install' // Deprecated alias for 'install'; requires explicit opt-in.
29
29
  | 'core-tabs'
30
+ | 'devtools'
30
31
  | 'vision'
31
32
  | 'verify';
32
33
 
@@ -56,6 +57,13 @@ export type Config = {
56
57
  */
57
58
  userDataDir?: string;
58
59
 
60
+ /**
61
+ * Chrome profile directory name used in extension mode (for example
62
+ * "Default" or "Profile 1"); defaults to the last-used profile that
63
+ * has the extension installed.
64
+ */
65
+ profileDirName?: string;
66
+
59
67
  /**
60
68
  * Launch options passed to
61
69
  * @see https://playwright.dev/docs/api/class-browsertype#browser-type-launch-persistent-context
@@ -104,6 +112,18 @@ export type Config = {
104
112
  * Remote endpoint to connect to an existing Playwright server.
105
113
  */
106
114
  remoteEndpoint?: string;
115
+
116
+ /**
117
+ * Directories that browser_file_upload and browser_drop may read files from.
118
+ * When unset (default), any absolute path is allowed; when set, upload
119
+ * canonical file paths must stay inside these directories. Restricted
120
+ * uploads accept regular files up to 50 MiB total per call. [] denies all;
121
+ * non-empty lists require macOS or Linux with /proc/self/fd available.
122
+ * Roots must exist and are canonicalized once at startup. null is invalid.
123
+ * blank entries are invalid. Also PLAYWRIGHT_MCP_ALLOWED_UPLOAD_DIRS or
124
+ * --allowed-upload-dirs (semicolon-separated; empty string means []).
125
+ */
126
+ allowedUploadDirs?: string[];
107
127
  },
108
128
 
109
129
  server?: {
@@ -116,6 +136,14 @@ export type Config = {
116
136
  * The host to bind the server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.
117
137
  */
118
138
  host?: string;
139
+
140
+ /**
141
+ * When set, HTTP transport requests must carry `Authorization: Bearer <token>`.
142
+ * Blank or malformed tokens are rejected. Requires a loopback listener;
143
+ * remote access must use a TLS reverse proxy. Also configurable through
144
+ * PLAYWRIGHT_MCP_AUTH_TOKEN.
145
+ */
146
+ authToken?: string;
119
147
  },
120
148
 
121
149
  /**
@@ -127,6 +155,7 @@ export type Config = {
127
155
  * - 'wait': Wait and timing utilities.
128
156
  * - 'files': File upload/download support.
129
157
  * - 'install': Browser installation utilities.
158
+ * - 'devtools': Browser recording utilities.
130
159
  */
131
160
  capabilities?: ToolCapability[];
132
161
 
@@ -163,9 +192,19 @@ export type Config = {
163
192
  };
164
193
 
165
194
  /**
166
- * Whether to send image responses to the client. Can be "allow", "omit", or "auto". Defaults to "auto", which sends images if the client can display them.
195
+ * Image response policy. Defaults to "allow"; "auto" is a legacy alias for "allow".
196
+ * "omit" excludes images. "only" omits text from successful responses containing images,
197
+ * but preserves errors, browser lifecycle notices, structured content and resource links. Responses without images keep text.
167
198
  */
168
- imageResponses?: 'allow' | 'omit' | 'auto';
199
+ imageResponses?: 'allow' | 'omit' | 'auto' | 'only';
200
+
201
+ snapshot?: {
202
+ /**
203
+ * Include each element's bounding box as [box=x,y,width,height] in snapshots.
204
+ * Coordinates are viewport-relative CSS pixels.
205
+ */
206
+ boxes?: boolean;
207
+ };
169
208
 
170
209
  /**
171
210
  * Timeout settings for Playwright operations.
@@ -185,5 +224,11 @@ export type Config = {
185
224
  * How long to wait after each action for triggered work to settle before responding. Defaults to 500ms.
186
225
  */
187
226
  settle?: number;
227
+
228
+ /**
229
+ * Release the default browser context after this many idle milliseconds. Zero (the default) disables it.
230
+ * Explicit browser sessions retain their separate idle TTL.
231
+ */
232
+ idle?: number;
188
233
  };
189
234
  };