cdp-toolkit 1.9.4 → 1.9.5

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/CHANGELOG.md CHANGED
@@ -5,6 +5,51 @@ All notable changes to cdp-toolkit are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.9.5] - 2026-08-11
9
+
10
+ **Documentation and stale code-comment clarifications; NO behavior changes since 1.9.4.** Two
11
+ updates. First, the Firefox `fullPage` screenshot gap that 1.9.2's docs hedged as "unmeasured" is
12
+ now resolved: measured against a real Firefox 153.0.3 through the shipped CLI in attach mode, a
13
+ 20,000px-tall page captured with `take_screenshot --fullPage true` came back as a single
14
+ 1366×20000 PNG at scale 1 — verified complete top-to-bottom (a marker at y=0, a marker at y=20000,
15
+ an unbroken ruler through the middle, no truncation). Firefox has no equivalent of Chrome's
16
+ 16384-device-px encode cap, so `browsingContext.captureScreenshot(origin:"document")` returns the
17
+ whole long page in one BiDi call and never needs the band-tiling Chrome's cap requires — tiling
18
+ exists solely to route around that cap, and Firefox doesn't have one, so `screenshot.tile` is a
19
+ non-issue for Firefox correctness rather than a gap. `screenshot.scale` remains a genuine,
20
+ unrelated Firefox gap: BiDi's `captureScreenshot` has no scale parameter at all, so Firefox
21
+ captures stay 1x only. That same measured result also replaces three stale "unmeasured"/"probably
22
+ implementable" comments in `src/bidi/driver.ts` describing `screenshot.tile` — comments only, no
23
+ logic changed, since `screenshot.tile` was already correctly left undeclared and `tile:true`
24
+ already correctly refused on Firefox; only the *reasoning* in the comments was stale. Second, this
25
+ release also gives a changelog entry to doc-only additions that landed on top of 1.9.4 without one:
26
+ Linux and remote-over-SSH attach guidance, and a `claude mcp add` one-liner, both in README.md's
27
+ setup instructions.
28
+
29
+ ### Added
30
+
31
+ - README.md: Linux and remote-over-SSH `--connect`/`CDP_FIREFOX_ENDPOINT`
32
+ attach guidance, and a `claude mcp add cdp-toolkit --scope user -- bun run
33
+ "$(pwd)/src/mcp.ts"` one-liner in the quick-start block.
34
+
35
+ ### Changed
36
+
37
+ - README.md's "Honest capability gaps" list and CONTRACT.md's `screenshot.ts`
38
+ module row: the Firefox `tile:true`/`screenshot.tile` hedge ("unmeasured
39
+ gap... probably implementable") is replaced with the measured result above.
40
+ `scale`'s status is unchanged and still called out as a genuine gap.
41
+ - `src/bidi/driver.ts`: three comment blocks (the `BIDI_CAPABILITIES` header
42
+ comment, the `capture()` method's `tile:true` refusal comment, and the
43
+ per-capability doc-comment list) rewritten from "unmeasured"/"probably
44
+ implementable" to the measured result above. No capability declarations, no
45
+ refusal logic, and no error-message text changed — comments only.
46
+
47
+ ### Notes
48
+
49
+ - Tool count unchanged at **45**; no tool added, renamed, or removed.
50
+ - Version bump only: `package.json` and `src/mcp.ts`'s `VERSION` constant move
51
+ 1.9.4 → 1.9.5. No runtime behavior changed anywhere in this release.
52
+
8
53
  ## [1.9.4] - 2026-08-11
9
54
 
10
55
  Firefox gains a second mode: **attach**. Until now `--browser firefox` always spawned a
package/CONTRACT.md CHANGED
@@ -171,7 +171,7 @@ A `Uid` **is** a CDP `backendDOMNodeId` (a number). This makes refs stateless:
171
171
  | `src/tools/evaluate.ts` | `evaluate_script`→`evaluateScript` | `Runtime.enable` + `Runtime.evaluate` (`returnByValue`, `awaitPromise`). Support optional `args` via `Runtime.callFunctionOn` on a uid/selector. Surface `exceptionDetails` as thrown error. |
172
172
  | `src/tools/snapshot.ts` | `take_snapshot`→`takeSnapshot` **+ export `resolveUid`** | a11y tree + ref scheme above. |
173
173
  | `src/tools/input.ts` | `click`→`click`, `hover`→`hover`, `drag`→`drag`, `fill`→`fill`, `fill_form`→`fillForm`, `type_text`→`typeText`, `press_key`→`pressKey`, `upload_file`→`uploadFile` | imports `resolveUid` from `./snapshot.ts`. `hover`=mouseMoved. `drag`=press→move→release. `fill`/`type_text`=focus + `Input.insertText` (fast) or `dispatchKeyEvent`. `press_key`=`Input.dispatchKeyEvent` (support modifiers + named keys). `upload_file`=`DOM.setFileInputFiles` on resolved node. `fill_form`=array of {uid|selector,value}. |
174
- | `src/tools/screenshot.ts` | `take_screenshot`→`takeScreenshot` | `Page.captureScreenshot`. Support `{ format:'png'|'jpeg', quality, fullPage, uid?/selector? (element clip via getBoxModel), savePath? }`. Write to ARTIFACT_DIR, return `{ path, bytes, format }` and optionally base64 when `{ returnBase64:true }`. **As of 1.9.2 also `scale` (Chrome-only capability `screenshot.scale`, rides `clip.scale`), `renderWidth`/`renderHeight` (BOTH backends, `screenshot.renderSize`, an `Emulation.setDeviceMetricsOverride` held for one capture and undone in a `finally` against the viewport record above), and `tile` (three-valued, `true` costs `screenshot.tile`).** Two rules the size arithmetic imposes on anyone touching this path. **(a) Guard the projection, not the knob:** output px are `ceil(css × scale × devicePixelRatio)`, Chrome cannot encode past 16384 device px per side, and past that `Page.captureScreenshot` NEVER ANSWERS while `captureBeyondViewport` (hardcoded true) leaves the renderer resized to the clip — a wedged tab a `finally` cannot undo, because the client-side timeout fires while Chrome's command is still running. So `Page.getLayoutMetrics` is fetched and the projection checked on EVERY capture, including the plain no-clip one; a guard conditioned on the feature that motivated it (`scale !== 1`) has already let two wedges through. **(b) Bands are lazy and vertical:** past the cap the driver hands the tool layer a `BandedCapture` whose Nth `Page.captureScreenshot` runs only when the Nth band is pulled (accumulating them into an array would typecheck, pass on a small page, and hold 1.4 GB on a large one), stitched by `stitchPngBandsToFile` in `src/png.ts` — a zero-dependency streaming PNG stitcher (`node:zlib`), not an MCP tool and deliberately absent from the `TOOLS` registry. Vertical only: PNG scanlines are the one axis concatenable without decoding the image, so an over-WIDE projection is refused, never split. |
174
+ | `src/tools/screenshot.ts` | `take_screenshot`→`takeScreenshot` | `Page.captureScreenshot`. Support `{ format:'png'|'jpeg', quality, fullPage, uid?/selector? (element clip via getBoxModel), savePath? }`. Write to ARTIFACT_DIR, return `{ path, bytes, format }` and optionally base64 when `{ returnBase64:true }`. **As of 1.9.2 also `scale` (Chrome-only capability `screenshot.scale`, rides `clip.scale`), `renderWidth`/`renderHeight` (BOTH backends, `screenshot.renderSize`, an `Emulation.setDeviceMetricsOverride` held for one capture and undone in a `finally` against the viewport record above), and `tile` (three-valued, `true` costs `screenshot.tile`).** Firefox declares neither `screenshot.scale` (BiDi's `captureScreenshot` has no scale parameter, so Firefox captures are always 1x) nor `screenshot.tile` — but the tile gap is moot, not a limitation: Firefox has no Chrome-style 16384-device-px encode cap to band around, so `browsingContext.captureScreenshot(origin:"document")` returns an arbitrarily long `fullPage` capture in one shot. Measured against Firefox 153.0.3: a 20,000px-tall page came back as a single 1366×20000 PNG at scale 1, verified complete top-to-bottom (no truncation). Two rules the size arithmetic imposes on anyone touching this path. **(a) Guard the projection, not the knob:** output px are `ceil(css × scale × devicePixelRatio)`, Chrome cannot encode past 16384 device px per side, and past that `Page.captureScreenshot` NEVER ANSWERS while `captureBeyondViewport` (hardcoded true) leaves the renderer resized to the clip — a wedged tab a `finally` cannot undo, because the client-side timeout fires while Chrome's command is still running. So `Page.getLayoutMetrics` is fetched and the projection checked on EVERY capture, including the plain no-clip one; a guard conditioned on the feature that motivated it (`scale !== 1`) has already let two wedges through. **(b) Bands are lazy and vertical:** past the cap the driver hands the tool layer a `BandedCapture` whose Nth `Page.captureScreenshot` runs only when the Nth band is pulled (accumulating them into an array would typecheck, pass on a small page, and hold 1.4 GB on a large one), stitched by `stitchPngBandsToFile` in `src/png.ts` — a zero-dependency streaming PNG stitcher (`node:zlib`), not an MCP tool and deliberately absent from the `TOOLS` registry. Vertical only: PNG scanlines are the one axis concatenable without decoding the image, so an over-WIDE projection is refused, never split. |
175
175
  | `src/tools/emulation.ts` | `emulate`→`emulate`, `resize_page`→`resizePage` | `Emulation.setDeviceMetricsOverride` (width,height,deviceScaleFactor,mobile), `Emulation.setUserAgentOverride`, `Emulation.setCPUThrottlingRate`, `Emulation.setEmulatedMedia`, `Network`-based throttling via `Network.emulateNetworkConditions`. `resize_page` = device metrics width/height. |
176
176
  | `src/tools/dialogs.ts` | `handle_dialog`→`handleDialog` | `Page.enable`, subscribe `Page.javascriptDialogOpening`, respond with `Page.handleJavaScriptDialog({ accept, promptText })`. Support a "wait for next dialog then handle" mode and an "auto-handle for N ms" mode. |
177
177
  | `src/tools/recorder.ts` + `src/tools/console.ts` + `src/tools/network.ts` | console: `list_console_messages`→`listConsoleMessages`, `get_console_message`→`getConsoleMessage`; network: `list_network_requests`→`listNetworkRequests`, `get_network_request`→`getNetworkRequest` | **Owned by one agent.** `recorder.ts` exports `startRecorder(target, {network,console}): Promise<{stop()}>` that opens a persistent `openPage` conn, enables `Network`+`Runtime`(+`Log`), and appends events to `rec-<targetId>.jsonl`. console/network `list_*` read that file (and support a one-shot `{ reload:true, durationMs }` mode that records fresh by reloading the page and capturing for a window, then returns). `get_*` filter by id/url. Document the recorder model at the top of `recorder.ts`. **As of 1.9.1, the same three tools also accept a `worker:<substring>` target** (Chrome only, capability `worker.targets`; MV3 service/shared workers), resolved via `resolveRecorderTarget`/`resolveWorkerSelector` in `src/workers.ts` rather than `resolveTarget`. Because `Page.enable`/`Page.reload` do not exist on a worker session (measured: `-32601`), `captureWindow()` skips the reload for a worker-typed resolution and LISTENS on the held connection for `durationMs` instead — keyed off the resolved target's type, not the selector's shape, so a bare worker id takes the same branch as a `worker:` selector. Holding that session is a deliberate side effect, the same pattern as the beacon sessions below: it SUPPRESSES the worker's MV3 idle eviction for as long as the capture runs (measured: alive and still emitting events at 60s held, evicted ~30s after detach). `wake` (default `true` on a capture) restarts an evicted worker first, but is refused on a read-only call and on a bare target id, because a restart mints a NEW target id and the buffer is keyed by the old one — see `workerWakeMisuseMessage` in `src/workers.ts`. |
package/README.md CHANGED
@@ -265,6 +265,21 @@ cdp --connect ws://127.0.0.1:9223/session take_snapshot # or the full ws:// UR
265
265
 
266
266
  `<endpoint>` accepts three spellings, all normalized to a ws URL: a bare port (`9223`), `host:port` (`127.0.0.1:9223`), or a full `ws://`/`wss://` URL. `--connect`/`CDP_FIREFOX_ENDPOINT` implies the Firefox backend on its own and errors against an explicit `--browser chrome`.
267
267
 
268
+ **On Linux, and over SSH to a remote host.** Attach is a plain loopback WebSocket, so it is byte-identical on every OS; only the *launch* half is platform-specific. On Linux the binary is `firefox` on `PATH` (or `/usr/bin/firefox`), and the same `--remote-debugging-port 9223` opens the endpoint. One catch worth stating plainly: if your everyday Firefox is already running, relaunching it with the flag just focuses the open window and opens no port (see "Attaching is not relaunching" below). To attach to your real logged-in profile, quit Firefox first and reopen it with the flag; to run beside your daily browser instead, use the separate `--no-remote -profile` instance shown above.
269
+
270
+ When Firefox runs on a different host than cdp-toolkit (a remote box, a container), its debug port binds to loopback *there*, so forward the port and attach to localhost:
271
+
272
+ ```bash
273
+ ssh -L 9223:127.0.0.1:9223 you@the-box # forward the remote debug port
274
+ cdp --connect 9223 take_snapshot # then attach as if it were local
275
+ ```
276
+
277
+ For Claude Code, register the MCP server with the endpoint in one line (the env var implies the Firefox backend):
278
+
279
+ ```bash
280
+ claude mcp add cdp-toolkit -e CDP_FIREFOX_ENDPOINT=9223 -- npx -y cdp-toolkit
281
+ ```
282
+
268
283
  **Firefox allows only one active WebDriver BiDi session at a time.** A second `session.new` against the same endpoint fails outright while a first session is open. Disposing cleanly (the normal exit path, both CLI and MCP shutdown) sends `session.end` first, which frees the slot for the next attach. A client that dies **without** disposing — a `SIGKILL`, a crash — leaves the slot occupied: Firefox does not reap an abandoned session on its own. A colliding attach against an occupied slot now fails fast with an actionable error naming the endpoint, rather than hanging forever (fixed in 1.9.4); recover by closing the other client or restarting Firefox.
269
284
 
270
285
  **Attaching is not relaunching.** The one thing that is genuinely impossible is handing a debug port to an *already-running* Firefox process after the fact: the `--remote-debugging-port` flag only takes effect on a process's original launch, so relaunching the `firefox` binary against a running instance hands off to it and exits silently, opening no port (verified against Firefox 153.0.3). That is a real, narrow limitation of the Firefox binary itself. It is not the same claim as "Firefox cannot be attached to" — a Firefox that was launched *with* the debug port open, whether by this toolkit or by your own hand, exposes a plain BiDi endpoint that any number of fresh clients can connect to later, which is exactly what `--connect`/`CDP_FIREFOX_ENDPOINT` does.
@@ -289,7 +304,7 @@ Everything else, including `mock_request`/`list_mocks`/`clear_mocks` (Firefox's
289
304
  - **`locate.text` is not available** (Firefox 153's `browsingContext.locateNodes` rejects the `innerText` locator type as unsupported), unlike Chrome, which has it via `DOM.performSearch`.
290
305
  - **No modifier-key clicks.** `click`'s `modifiers` (Alt/Control/Meta/Shift) is a Chrome-only *parameter* on an otherwise-universal tool: a non-empty `modifiers` array throws under `--browser firefox` rather than being silently dropped. A plain click still works on both backends.
291
306
  - **No real HTML5 drag-and-drop mode.** `drag`'s `mode:"html5"` requires capability `input.html5Drag` and is rejected with a clear error under Firefox; `mode:"mouse"` (the default) works on both.
292
- - **No per-capture screenshot `scale`, and no band tiling** — two *parameters* on an otherwise-universal tool, for two different reasons. `scale` (`screenshot.scale`) is a genuine protocol gap: `browsingContext.captureScreenshot` has no scale parameter at all, and the refusal points at `emulate {deviceScaleFactor}` + a scale-1 capture instead. `tile:true` (`screenshot.tile`) is an **unmeasured** gap, not a missing primitive: BiDi does take a box clip, so bands are probably implementable, but every property tiling rests on (does a box clip render past the viewport, is `clip.y` CSS px, do fixed elements repeat per band, are seams pixel-exact) was measured on Chrome and nowhere else, so it is not advertised. Auto-tiling never fires without `screenshot.tile`, so a Firefox capture behaves exactly as it did before. **`renderWidth`/`renderHeight` (`screenshot.renderSize`) are available on both backends** — that one is not a gap.
307
+ - **No per-capture screenshot `scale`; band tiling is a Chrome-only workaround Firefox has no use for.** Two *parameters* on an otherwise-universal tool, for two different reasons. `scale` (`screenshot.scale`) is a genuine protocol gap: `browsingContext.captureScreenshot` has no scale parameter at all, and the refusal points at `emulate {deviceScaleFactor}` + a scale-1 capture instead — Firefox captures are always 1x. `tile:true` (`screenshot.tile`) is **not** a Firefox gap: measured against Firefox 153.0.3, `take_screenshot --fullPage true` on a 20,000px-tall page returned one 1366×20000 PNG at scale 1 from a single BiDi `captureScreenshot(origin:"document")` call — verified complete top-to-bottom (a marker at y=0, a marker at y=20000, an unbroken ruler through the middle, no truncation). Chrome's banding exists solely to route around its 16384-device-px encode cap; Firefox has no such cap, so there is nothing for tiling to work around, and auto-tiling correctly never fires there — a Firefox `fullPage` capture is already the whole page in one shot, however long. **`renderWidth`/`renderHeight` (`screenshot.renderSize`) are available on both backends** — that one is not a gap.
293
308
 
294
309
  ## The tools (29 parity + 16 superset = 45)
295
310
 
package/dist/mcp.js CHANGED
@@ -8874,7 +8874,7 @@ var FIREFOX_TOOLS = {
8874
8874
  };
8875
8875
 
8876
8876
  // src/mcp.ts
8877
- var VERSION = "1.9.4";
8877
+ var VERSION = "1.9.5";
8878
8878
  var { kind: BROWSER, endpoint: FIREFOX_ENDPOINT } = resolveBackend(process.argv.slice(2));
8879
8879
  var AVAILABILITY = toolAvailability(BROWSER);
8880
8880
  var AVAILABLE_NAMES = new Set(AVAILABILITY.available);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-toolkit",
3
- "version": "1.9.4",
3
+ "version": "1.9.5",
4
4
  "description": "A lightweight chrome-devtools-mcp alternative: an MCP server + CLI that drives the tabs you name, one target per call over one direct WebSocket, with a bounded timeout on every call, so a stuck page can't wedge your agent or force a /mcp restart. Chrome over CDP and Firefox over WebDriver BiDi, with tools a backend can't run absent from tools/list rather than throwing mid-task. 45 tools including built-in network mocking, a cookie group, tab leases with human-activity detection, real HTML5 drag-and-drop, raw mouse/scroll dispatch, downloads, permission grants, and tab-to-video screen recording so several agents can drive one browser without stealing each other's tabs or a human's. No Puppeteer, no all-target fan-out.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -301,11 +301,13 @@ const AWAIT_SCROLL_SETTLE_SOURCE = "function(){return window.__cdpScrollSettle;}
301
301
  * above already rides, applied for one capture and undone afterwards. Refusing it would be dishonest
302
302
  * in the opposite direction — claiming a missing primitive this driver demonstrably has. Contrast
303
303
  * screenshot.scale, absent because captureScreenshot genuinely has no scale parameter.
304
- * screenshot.tile is absent for a THIRD reason, neither "has it" nor "no such parameter": the
305
- * primitive plausibly exists (clip:{type:"box"}) but nothing about band tiling has been measured on
306
- * this backend, and the Chrome declaration rests entirely on measurement. An undeclared capability
307
- * that turns out to work is a follow-up; a declared one that silently mis-stitches is a wrong image
308
- * the caller cannot tell from a right one.
304
+ * screenshot.tile stays undeclared, but no longer for an unmeasured reason: live-fired against
305
+ * Firefox 153.0.3, a 20,000px-tall page captured with take_screenshot --fullPage true came back
306
+ * as a single 1366x20000 PNG at scale 1, verified complete top-to-bottom — no clip, no bands.
307
+ * Chrome's band tiling exists solely to route around its 16384-device-px encode cap; Firefox has
308
+ * no such cap, so screenshot.tile (the capability gating explicit tile:true forced banding) has
309
+ * no gap here to close. It stays undeclared because it would advertise a forced-tiling primitive
310
+ * this backend has never needed, not because band-tiling behavior is untested.
309
311
  */
310
312
  const BIDI_CAPABILITIES: ReadonlySet<Capability> = new Set<Capability>([
311
313
  "emulate.deviceMetrics", "screenshot.fullPage", "screenshot.element", "screenshot.renderSize", "network.intercept", "locate.xpath",
@@ -882,15 +884,14 @@ class BidiPageDriver implements PageDriver {
882
884
  `deviceScaleFactor:${opts.scale} first and capture at scale 1, or use --browser chrome.`,
883
885
  );
884
886
  }
885
- // Same ADR-001 param-level shape, and the reason is HONESTY rather than a missing primitive.
886
- // browsingContext.captureScreenshot does take clip:{type:"box",x,y,width,height}, so a banded
887
- // capture is probably implementable here — but Firefox was not launched for this change, so
888
- // every property band tiling depends on is unmeasured on this backend: whether a box clip
889
- // renders content outside the viewport at all, whether clip.y is CSS px, whether fixed elements
890
- // repeat per band, whether seams are pixel-exact. Each of those was measured on Chrome before
891
- // the Chrome path was declared. Shipping an unverified twin would put a silently-wrong stitch in
892
- // front of a caller, so this refuses and says why. `tile:false` and the default (auto, which
893
- // never bands here) ask this backend for nothing and are untouched.
887
+ // Same ADR-001 param-level shape, but now a measured non-gap rather than an unmeasured one.
888
+ // Live-fired against Firefox 153.0.3: a 20,000px-tall page captured with take_screenshot
889
+ // --fullPage true came back as one 1366x20000 PNG at scale 1, verified complete top-to-bottom,
890
+ // via the single origin:"document" capture below — no clip, no bands. Chrome's band tiling
891
+ // exists only to route around its 16384-device-px encode cap; this backend has no such cap, so
892
+ // there is nothing for tile:true's forced banding to do here. The refusal below stays because
893
+ // there is no forced-tiling primitive to build, not because it is untested. `tile:false` and
894
+ // the default (auto, which never bands here) ask this backend for nothing and are untouched.
894
895
  if (opts?.tile === true) {
895
896
  throw driverError(
896
897
  "unsupported",
@@ -1408,13 +1409,14 @@ export { BidiBrowserDriver, BidiPageDriver, resolveContext };
1408
1409
  * Firefox is next driven: whether a null viewport reverts to the real window (CDP's clear does
1409
1410
  * NOT, for an override another session set), and whether setViewport reflows synchronously the
1410
1411
  * way Emulation.setDeviceMetricsOverride was measured to on Chrome.
1411
- * - screenshot.tile: not declared, and unlike screenshot.scale this is an UNMEASURED gap rather
1412
- * than a missing parameter. captureScreenshot does take clip:{type:"box"}, so bands are probably
1413
- * implementable — but every property band tiling rests on (does a box clip render past the
1414
- * viewport, is clip.y CSS px, do fixed elements repeat per band, are seams pixel-exact) was
1415
- * measured on Chrome and on nothing else, and Firefox was not launched here. `tile:true` is a
1416
- * refusal in screenshot() naming --browser chrome; the default (auto) never bands on this
1417
- * backend, so it is unchanged.
1412
+ * - screenshot.tile: not declared, but no longer an unmeasured gap. Live-fired against Firefox
1413
+ * 153.0.3: take_screenshot --fullPage true on a 20,000px-tall page returned one 1366x20000 PNG
1414
+ * at scale 1, verified complete top-to-bottom, via a single origin:"document" capture — no
1415
+ * clip, no bands, no cap hit. Chrome's band tiling exists solely to route around its
1416
+ * 16384-device-px encode cap; Firefox has no such cap, so screenshot.tile (forced tiling via
1417
+ * tile:true) has nothing to do here. `tile:true` is still a refusal in screenshot() naming
1418
+ * --browser chrome; the default (auto) still never bands on this backend — both unchanged, now
1419
+ * for a documented reason instead of an untested one.
1418
1420
  * - emulate.mediaFeatures / emulate.cpuThrottling / emulate.networkConditions: not declared.
1419
1421
  * Firefox 153 does not implement setMediaFeaturesOverride; CPU throttling and network condition
1420
1422
  * emulation have no verified-working BiDi path in this environment, so emulate() never claims
package/src/mcp.ts CHANGED
@@ -26,7 +26,7 @@ import { toolAvailability } from "./capabilities.ts";
26
26
  import { FIREFOX_TOOLS } from "./firefox-tools.ts";
27
27
  import { leaseFromArgs, markLongLivedProcess, withLeaseScope } from "./leases.ts";
28
28
 
29
- const VERSION = "1.9.4";
29
+ const VERSION = "1.9.5";
30
30
 
31
31
  // Backend + (Firefox only) attach endpoint are read once at startup (MCP has no per-call notion
32
32
  // of backend): --browser flag / CDP_BROWSER env, else "chrome" (zero behavior change for existing